<commit_before>import React from 'react';
import autoBind from 'react-autobind';
import alertify from 'alertifyjs';
import Dropzone from 'react-dropzone';
import {bem} from 'js/bem';
import {dataInterface} from 'js/dataInterface';
import {
  t,
} from 'js/utils';
import {
} from 'js/constants';

/*
Modal for uploading form media, part of #2487.
*/
class FormMedia extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      uploadedAssets: null
    };

    autoBind(this);
  }

  /*
   * setup
   */

  componentDidMount() {
    dataInterface.getFormMedia(this.props.asset.uid).done((uploadedAssets) => {
      this.setState({uploadedAssets: uploadedAssets.results});
    });
  }

  toBase64(file) {
    return new Promise(function(resolve, reject) {
      var reader = new FileReader();
      reader.readAsDataURL(file);
      reader.onload = function() {
          return resolve(reader.result);
      };
      reader.onerror = function(error) {
          return reject(error);
      };
    });
  }

  onFileDrop(files) {
    if (files.length >= 1) {
      files.forEach(async (file) => {
        var base64File = await this.toBase64(file);
	    var formMediaJSON = {
	      description: 'default',
          file_type: 'form_media',
          metadata: JSON.stringify({filename: file.name}),
	      base64Encoded: base64File
	    };
	    dataInterface.postFormMedia(this.props.asset.uid, formMediaJSON).done((res) => {
          dataInterface.getFormMedia(this.props.asset.uid).done((uploadedAssets) => {
            this.setState({uploadedAssets: uploadedAssets.results});
          });
	    }).fail((err) => {
	      alertify.error(err.responseText);
	    });
      });
    }
  }

//TODO: Figure out a way to import from URL
/* async onSubmitURL() {
 *   var url = $(document).find('input.form-media__url-input').eq(0).val();
 *   var formMediaJSON = {
 *     description: 'default',
 *     file_type: 'form_media',
 *     metadata: JSON.stringify({filename: 'default'}),
 *     content: url
 *   };
 *   dataInterface.postFormMedia(this.props.asset.uid, formMediaJSON).done(() => {
 *     dataInterface.getFormMedia(this.props.asset.uid).done((uploadedAssets) => {
 *       this.setState({uploadedAssets: uploadedAssets.results});
 *     });
 *   }).fail((err) => {
 *     alertify.error(err.responseText);
 *   });
 * }
 */

  removeMedia(url) {
      dataInterface.deleteFormMedia(url).done((res) => {
        dataInterface.getFormMedia(this.props.asset.uid).done((uploadedAssets) => {
          this.setState({uploadedAssets: uploadedAssets.results});
        });
      }).fail((err) => {
        alertify.error(err.responseText);
      });
  }

  /*
   * rendering
   */

  renderLoading(message = t('loading…')) {
    return (
      <bem.Loading>
        <bem.Loading__inner>

          {message}
        </bem.Loading__inner>
      </bem.Loading>
    );
  }

  render() {
    return (
      <bem.FormModal__form className='project-settings project-settings--upload-file media-settings--upload-file' onSubmit={this.onSubmitURL}>

          {!this.state.isUploadFilePending &&
            <Dropzone
                onDrop={this.onFileDrop.bind(this)}
                className='dropzone'
            >

              {t(' Drag and drop files here')}

                {t('or')} {t('click here to browse')}

            </Dropzone>
          }

            {t('You can also add files using a URL')}
            <input className='form-media__url-input' placeholder={t('Paste URL here')}/><button className='mdl-button mdl-button--raised mdl-button--colored form-media__url-button'>{t('ADD')}</button>

          {t('File(s) uploaded to this project')}

              {this.state.uploadedAssets !== null && this.state.uploadedAssets.map((item, n) => {
                return (

                    {item.metadata.filename}
                     this.removeMedia(item.url)}/>

                );
              })}
              {(this.state.uploadedAssets === null || this.state.uploadedAssets.length == 0) &&

                    {t('No files uploaded yet')}

              }

      </bem.FormModal__form>
    );
  }
}

export default FormMedia;
<commit_msg>Refactored getting form media<commit_after>import React from 'react';
import autoBind from 'react-autobind';
import alertify from 'alertifyjs';
import Dropzone from 'react-dropzone';
import {bem} from 'js/bem';
import {dataInterface} from 'js/dataInterface';
import {
  t,
} from 'js/utils';
import {
} from 'js/constants';

/*
Modal for uploading form media, part of #2487.
*/
class FormMedia extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      uploadedAssets: null
    };

    autoBind(this);
  }

  /*
   * setup
   */

  componentDidMount() {
    this.refreshFormMedia();
  }

  refreshFormMedia() {
    dataInterface.getFormMedia(this.props.asset.uid).done((uploadedAssets) => {
      this.setState({uploadedAssets: uploadedAssets.results});
    });
  }

  toBase64(file) {
    return new Promise(function(resolve, reject) {
      var reader = new FileReader();
      reader.readAsDataURL(file);
      reader.onload = function() {
          return resolve(reader.result);
      };
      reader.onerror = function(error) {
          return reject(error);
      };
    });
  }

  onFileDrop(files) {
    if (files.length >= 1) {
      files.forEach(async (file) => {
        var base64File = await this.toBase64(file);
	    var formMediaJSON = {
	      description: 'default',
          file_type: 'form_media',
          metadata: JSON.stringify({filename: file.name}),
	      base64Encoded: base64File
	    };
	    dataInterface.postFormMedia(this.props.asset.uid, formMediaJSON).done((res) => {
          this.refreshFormMedia();
	    }).fail((err) => {
	      alertify.error(err.responseText);
	    });
      });
    }
  }

//TODO: Figure out a way to import from URL
/* async onSubmitURL() {
 *   var url = $(document).find('input.form-media__url-input').eq(0).val();
 *   var formMediaJSON = {
 *     description: 'default',
 *     file_type: 'form_media',
 *     metadata: JSON.stringify({filename: 'default'}),
 *     content: url
 *   };
 *   dataInterface.postFormMedia(this.props.asset.uid, formMediaJSON).done(() => {
 *     dataInterface.getFormMedia(this.props.asset.uid).done((uploadedAssets) => {
 *       this.setState({uploadedAssets: uploadedAssets.results});
 *     });
 *   }).fail((err) => {
 *     alertify.error(err.responseText);
 *   });
 * }
 */

  removeMedia(url) {
      dataInterface.deleteFormMedia(url).done((res) => {
        this.refreshFormMedia();
      }).fail((err) => {
        alertify.error(err.responseText);
      });
  }

  /*
   * rendering
   */

  renderLoading(message = t('loading…')) {
    return (
      <bem.Loading>
        <bem.Loading__inner>

          {message}
        </bem.Loading__inner>
      </bem.Loading>
    );
  }

  render() {
    return (
      <bem.FormModal__form className='project-settings project-settings--upload-file media-settings--upload-file' onSubmit={this.onSubmitURL}>

          {!this.state.isUploadFilePending &&
            <Dropzone
                onDrop={this.onFileDrop.bind(this)}
                className='dropzone'
            >

              {t(' Drag and drop files here')}

                {t('or')} {t('click here to browse')}

            </Dropzone>
          }

            {t('You can also add files using a URL')}
            <input className='form-media__url-input' placeholder={t('Paste URL here')}/><button className='mdl-button mdl-button--raised mdl-button--colored form-media__url-button'>{t('ADD')}</button>

          {t('File(s) uploaded to this project')}

              {this.state.uploadedAssets !== null && this.state.uploadedAssets.map((item, n) => {
                return (

                    {item.metadata.filename}
                     this.removeMedia(item.url)}/>

                );
              })}
              {(this.state.uploadedAssets === null || this.state.uploadedAssets.length == 0) &&

                    {t('No files uploaded yet')}

              }

      </bem.FormModal__form>
    );
  }
}

export default FormMedia;
<|endoftext|>


<commit_before>

// STUN, TURN Servers
var iceServer = {
	iceServers : [
	{
		urls : "stun:stun.l.google.com:19302"
	}, {
		urls : "stun:stun1.l.google.com:19302"
	}, {
		urls		: "turn:dsl.nplab.de:3478",
		credential	: "nplab",
		username	: "nplab"
	}]
};

// find and return an IPv4 Address from a given string
// taken from: https://gist.github.com/syzdek/6086792
function extractIpFromString(string) {
	var match;
	var pattern4 = /\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b/
	var pattern6 = /(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))/

	if(match = string.match(pattern4)) {
		return match[0];
	} else if(match = string.match(pattern6)) {
		return match[0];
	} else {
		return false;
	}
}

// generate a unique-ish string for storage in firebase
function generateSignalingId() {
	return (Math.random() * 10000|0).toString();
}

// check WebRTC capabilities of the browser
function browsercheck() {
	try {
		var pc = new RTCPeerConnection();
		var dc = pc.createDataChannel('control');
		return true;
	} catch(err) {
		console.log('Browser does not support WebRTC!');
		$('div.main').html('
Browser does not support WebRTC!
');
		return false;
	}
}
<commit_msg>check for permission<commit_after>

// STUN, TURN Servers
var iceServer = [
	{
		urls : "stun:stun.l.google.com:19302"
	}, {
		urls			: "turn:dsl.nplab.de:3478",
		username		: "nplab",
		credential		: "nplab",
		credentialType	: "password"
	}
];

// find and return an IPv4 Address from a given string
// taken from: https://gist.github.com/syzdek/6086792
function extractIpFromString(string) {
	var match;
	var pattern4 = /\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b/
	var pattern6 = /(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))/

	if(match = string.match(pattern4)) {
		return match[0];
	} else if(match = string.match(pattern6)) {
		return match[0];
	} else {
		return false;
	}
}

// generate a unique-ish string for storage in firebase
function generateSignalingId() {
	return (Math.random() * 10000|0).toString();
}

// check WebRTC capabilities of the browser
function browsercheck() {
	try {
		var pc = new RTCPeerConnection();
		var dc = pc.createDataChannel('control');
		return true;
	} catch(err) {
		console.log('Browser does not support WebRTC!');
		$('div.main').html('
Browser does not support WebRTC!
');
		return false;
	}
}
<|endoftext|>


<commit_before>/**
 * Developer: Stepan Burguchev
 * Date: 1/26/2015
 * Copyright: 2009-2015 Comindware®
 *       All Rights Reserved
 *
 * THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF Comindware
 *       The copyright notice above does not evidence any
 *       actual or intended publication of such source code.
 */

/* global define, require, Handlebars, Backbone, Marionette, $, _, Localizer */

define(['module/lib'], function () {
    'use strict';

    Backbone.Form.validators.errMessages.required = Localizer.get('PROJECT.COMMON.FORM.VALIDATION.REQUIRED');

    Backbone.Form.validators.required = function (options) {
        options = _.extend({
            type: 'required',
            message: this.errMessages.required
        }, options);

        return function required(value) {
            var val = _.isObject(value) ? value.value : value;
            options.value = val;

            var err = {
                type: options.type,
                message: _.isFunction(options.message) ? options.message(options) : options.message
            };
            if (val === null || val === undefined || val === false || val === '') {
                return err;
            }
            if (_.isArray(val) && val.length === 0) {
                return err;
            }
        };
    };
});<commit_msg>fix form required for reference<commit_after>/**
 * Developer: Stepan Burguchev
 * Date: 1/26/2015
 * Copyright: 2009-2015 Comindware®
 *       All Rights Reserved
 *
 * THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF Comindware
 *       The copyright notice above does not evidence any
 *       actual or intended publication of such source code.
 */

/* global define, require, Handlebars, Backbone, Marionette, $, _, Localizer */

define(['module/lib'], function () {
    'use strict';

    Backbone.Form.validators.errMessages.required = Localizer.get('PROJECT.COMMON.FORM.VALIDATION.REQUIRED');

    Backbone.Form.validators.required = function (options) {
        options = _.extend({
            type: 'required',
            message: this.errMessages.required
        }, options);

        return function required(value) {
            var val = _.isObject(value) && _.has(value, 'value') ? value.value : value;
            options.value = val;

            var err = {
                type: options.type,
                message: _.isFunction(options.message) ? options.message(options) : options.message
            };
            if (val === null || val === undefined || val === false || val === '') {
                return err;
            }
            if (_.isArray(val) && val.length === 0) {
                return err;
            }
        };
    };
});<|endoftext|>


<commit_before>/* globals $:false, gettext:false, k:false */
(function() {

  'use strict';

  function init() {
    makeKPIGraph($('#kpi-questions'), true, [
      {
        name: gettext('Questions'),
        slug: 'questions',
        func: k.Graph.identity('questions'),
        color: '#5d84b2',
        axisGroup: 'questions',
        area: true
      },
      {
        name: gettext('Solved'),
        slug: 'num_solved',
        func: k.Graph.identity('solved'),
        color: '#aa4643',
        axisGroup: 'questions',
        area: true
      },
      {
        name: gettext('% Solved'),
        slug: 'solved',
        func: k.Graph.fraction('solved', 'questions'),
        color: '#aa4643',
        axisGroup: 'percent',
        type: 'percent'
      },
      {
        name: gettext('Responded in 24 hours'),
        slug: 'num_responded_24',
        func: k.Graph.identity('responded_24'),
        color: '#89a54e',
        axisGroup: 'questions',
        area: true
      },
      {
        name: gettext('% Responded in 24 hours'),
        slug: 'responded_24',
        func: k.Graph.fraction('responded_24', 'questions'),
        color: '#89a54e',
        axisGroup: 'percent',
        type: 'percent'
      },
      {
        name: gettext('Responded in 72 hours'),
        slug: 'num_responded_72',
        func: k.Graph.identity('responded_72'),
        color: '#80699b',
        axisGroup: 'questions',
        area: true
      },
      {
        name: gettext('% Responded in 72 hours'),
        slug: 'responded_72',
        func: k.Graph.fraction('responded_72', 'questions'),
        color: '#80699b',
        axisGroup: 'percent',
        type: 'percent'
      },
      {
        name: gettext('Not responded in 24 hours'),
        slug: 'not_responded_24',
        color: '#C98531',
        func: k.Graph.difference('questions', 'responded_24'),
        area: true
      },
      {
        name: gettext('Not responded in 72 hours'),
        slug: 'not_responded_72',
        color: '#DB75C2',
        func: k.Graph.difference('questions', 'responded_72'),
        area: true
      }
    ]);

    makeKPIGraph($('#kpi-vote'), true, [
      {
        name: gettext('Article Votes: % Helpful'),
        slug: 'wiki_percent',
        func: k.Graph.fraction('kb_helpful', 'kb_votes'),
        type: 'percent'
      },
      {
        name: gettext('Answer Votes: % Helpful'),
        slug: 'ans_percent',
        func: k.Graph.fraction('ans_helpful', 'ans_votes'),
        type: 'percent'
      }
    ]);

    makeKPIGraph($('#kpi-active-contributors'), false, [
      {
        name: gettext('en-US KB'),
        slug: 'en_us',
        func: k.Graph.identity('en_us')
      },
      {
        name: gettext('non en-US KB'),
        slug: 'non_en_us',
        func: k.Graph.identity('non_en_us')
      },
      {
        name: gettext('Support Forum'),
        slug: 'support_forum',
        func: k.Graph.identity('support_forum')
      },
      {
        name: gettext('Army of Awesome'),
        slug: 'aoa',
        func: k.Graph.identity('aoa')
      }
    ]);

    makeKPIGraph($('#kpi-ctr'), true, [
      {
        name: gettext('Click Through Rate %'),
        slug: 'ctr',
        func: k.Graph.fraction('clicks', 'searches'),
        type: 'percent'
      }
    ]);

    makeKPIGraph($('#kpi-visitors'), true, [
      {
        name: gettext('Visitors'),
        slug: 'visitors',
        func: k.Graph.identity('visitors')
      }
    ]);

    makeKPIGraph($('#kpi-l10n'), true, [
      {
        name: gettext('L10n Coverage'),
        slug: 'l10n',
        // the api returns 0 to 100, we want 0.0 to 1.0.
        func: function(d) { return d.coverage / 100; },
        type: 'percent'
      }
    ]);

    makeKPIGraph($('#exit-survey'), true, [
      {
        name: gettext('Percent Yes'),
        slug: 'percent_yes',
        func: k.Graph.percentage('yes', 'no', 'dont_know'),
        axisGroup: 'percent',
        type: 'percent'
      },
      {
        name: gettext('Yes'),
        slug: 'yes',
        func: k.Graph.identity('yes'),
        axisGroup: 'response'
      },
      {
        name: gettext('No'),
        slug: 'no',
        func: k.Graph.identity('no'),
        axisGroup: 'response'
      },
      {
        name: gettext("I don't know"),
        slug: 'dont_know',
        func: k.Graph.identity('dont_know'),
        axisGroup: 'response'
      }
    ]);

  }

  function makeKPIGraph($container, bucket, descriptors) {
    $.getJSON($container.data('url'), function(data) {
      new k.Graph($container, {
        data: {
          datums: data.objects,
          seriesSpec: descriptors
        },
        options: {
          legend: 'mini',
          slider: true,
          bucket: bucket
        },
        graph: {
          width: 880,
          height: 300
        },
      }).render();
    });
  }

  $(init);

})();
<commit_msg>Update logic for fetching chart data<commit_after>/* globals $:false, gettext:false, k:false */
(function() {

  'use strict';

  function init() {
    makeKPIGraph($('#kpi-questions'), true, [
      {
        name: gettext('Questions'),
        slug: 'questions',
        func: k.Graph.identity('questions'),
        color: '#5d84b2',
        axisGroup: 'questions',
        area: true
      },
      {
        name: gettext('Solved'),
        slug: 'num_solved',
        func: k.Graph.identity('solved'),
        color: '#aa4643',
        axisGroup: 'questions',
        area: true
      },
      {
        name: gettext('% Solved'),
        slug: 'solved',
        func: k.Graph.fraction('solved', 'questions'),
        color: '#aa4643',
        axisGroup: 'percent',
        type: 'percent'
      },
      {
        name: gettext('Responded in 24 hours'),
        slug: 'num_responded_24',
        func: k.Graph.identity('responded_24'),
        color: '#89a54e',
        axisGroup: 'questions',
        area: true
      },
      {
        name: gettext('% Responded in 24 hours'),
        slug: 'responded_24',
        func: k.Graph.fraction('responded_24', 'questions'),
        color: '#89a54e',
        axisGroup: 'percent',
        type: 'percent'
      },
      {
        name: gettext('Responded in 72 hours'),
        slug: 'num_responded_72',
        func: k.Graph.identity('responded_72'),
        color: '#80699b',
        axisGroup: 'questions',
        area: true
      },
      {
        name: gettext('% Responded in 72 hours'),
        slug: 'responded_72',
        func: k.Graph.fraction('responded_72', 'questions'),
        color: '#80699b',
        axisGroup: 'percent',
        type: 'percent'
      },
      {
        name: gettext('Not responded in 24 hours'),
        slug: 'not_responded_24',
        color: '#C98531',
        func: k.Graph.difference('questions', 'responded_24'),
        area: true
      },
      {
        name: gettext('Not responded in 72 hours'),
        slug: 'not_responded_72',
        color: '#DB75C2',
        func: k.Graph.difference('questions', 'responded_72'),
        area: true
      }
    ]);

    makeKPIGraph($('#kpi-vote'), true, [
      {
        name: gettext('Article Votes: % Helpful'),
        slug: 'wiki_percent',
        func: k.Graph.fraction('kb_helpful', 'kb_votes'),
        type: 'percent'
      },
      {
        name: gettext('Answer Votes: % Helpful'),
        slug: 'ans_percent',
        func: k.Graph.fraction('ans_helpful', 'ans_votes'),
        type: 'percent'
      }
    ]);

    makeKPIGraph($('#kpi-active-contributors'), false, [
      {
        name: gettext('en-US KB'),
        slug: 'en_us',
        func: k.Graph.identity('en_us')
      },
      {
        name: gettext('non en-US KB'),
        slug: 'non_en_us',
        func: k.Graph.identity('non_en_us')
      },
      {
        name: gettext('Support Forum'),
        slug: 'support_forum',
        func: k.Graph.identity('support_forum')
      },
      {
        name: gettext('Army of Awesome'),
        slug: 'aoa',
        func: k.Graph.identity('aoa')
      }
    ]);

    makeKPIGraph($('#kpi-ctr'), true, [
      {
        name: gettext('Click Through Rate %'),
        slug: 'ctr',
        func: k.Graph.fraction('clicks', 'searches'),
        type: 'percent'
      }
    ]);

    makeKPIGraph($('#kpi-visitors'), true, [
      {
        name: gettext('Visitors'),
        slug: 'visitors',
        func: k.Graph.identity('visitors')
      }
    ]);

    makeKPIGraph($('#kpi-l10n'), true, [
      {
        name: gettext('L10n Coverage'),
        slug: 'l10n',
        // the api returns 0 to 100, we want 0.0 to 1.0.
        func: function(d) { return d.coverage / 100; },
        type: 'percent'
      }
    ]);

    makeKPIGraph($('#exit-survey'), true, [
      {
        name: gettext('Percent Yes'),
        slug: 'percent_yes',
        func: k.Graph.percentage('yes', 'no', 'dont_know'),
        axisGroup: 'percent',
        type: 'percent'
      },
      {
        name: gettext('Yes'),
        slug: 'yes',
        func: k.Graph.identity('yes'),
        axisGroup: 'response'
      },
      {
        name: gettext('No'),
        slug: 'no',
        func: k.Graph.identity('no'),
        axisGroup: 'response'
      },
      {
        name: gettext("I don't know"),
        slug: 'dont_know',
        func: k.Graph.identity('dont_know'),
        axisGroup: 'response'
      }
    ]);

  }

  function getChartData(url, propertyKey) {
    let dataReady = $.Deferred();
    let datumsToCollect = propertyKey;
    let fetchData = function(url, existingData) {
      $.getJSON(url, function(data) {
        existingData = existingData.concat(data[datumsToCollect] || data);
        if (data.next) {
          return fetchData(data.next, existingData);
        } else {
          dataReady.resolve(existingData);
        }
      }).fail(function(error) {
        dataReady.reject(error);
      });
    }

    fetchData(url, []);
    return dataReady;
  }

  function makeKPIGraph($container, bucket, descriptors) {
    let fetchDataset = getChartData($container.data('url'), 'objects');
    fetchDataset.done(function(data) {
      new k.Graph($container, {
        data: {
          datums: data,
          seriesSpec: descriptors
        },
        options: {
          legend: 'mini',
          slider: true,
          bucket: bucket
        },
        graph: {
          width: 880,
          height: 300
        },
      }).render();
    }).fail(function(error) {
      console.log('There was an error retrieving the data: ', error);
    });
  }

  $(init);

})();
<|endoftext|>


<commit_before>var desktop = {
  'mac-chrome-latest'       : ['OS X 10.12', 'chrome', '60.0'],
  'mac-firefox-latest'      : ['OS X 10.12', 'firefox', '54.0'],
  'mac-safari-latest'       : ['OS X 10.12', 'safari', '10.0'],
  'mac-chrome-previous'     : ['OS X 10.11', 'chrome', '59.0'],
  'mac-firefox-previous'    : ['OS X 10.11', 'firefox', '53.0'],
  'mac-safari-previous'     : ['OS X 10.11', 'safari', '9.0'],

  'windows-chrome-latest'   : ['Windows 10', 'chrome', '60.0'],
  'windows-firefox-latest'  : ['Windows 10', 'firefox', '54.0'],
  'windows-edge-latest'     : ['Windows 10', 'microsoftedge', '15.15063'],
  'windows-ie-latest'       : ['Windows 8.1', 'internet explorer', '11.0'],
  'windows-chrome-previous' : ['Windows 8.1', 'chrome', '59.0'],
  'windows-firefox-previous': ['Windows 8.1', 'firefox', '53.0'],
  'windows-edge-previous'   : ['Windows 10', 'microsoftedge', '14.14393'],

  'linux-chrome-latest'     : ['Linux', 'chrome', '48.0'],
  'linux-firefox-latest'    : ['Linux', 'firefox', '45.0'],
  'linux-chrome-previous'   : ['Linux', 'chrome', '47.0'],
  'linux-firefox-previous'  : ['Linux', 'firefox', '44.0']
};

var mobile = {
  'ios-latest'        : ['iPhone 7 Plus', 'iOS', '10.3', 'Safari'],
  'ios-previous'      : ['iPhone 6 Plus', 'iOS', '9.3', 'Safari'],

  'android-latest'    : ['Android GoogleAPI Emulator', 'Android', '7.1', 'Chrome'],
  'android-previous'  : ['Android GoogleAPI Emulator', 'Android', '6.0', 'Chrome']
};

Object.keys(desktop).forEach(function(key) {
  module.exports[key] = {
    base: 'SauceLabs',
    browserName: desktop[key][1],
    version: desktop[key][2],
    platform: desktop[key][0]
  };
});

Object.keys(mobile).forEach(function(key) {
  module.exports[key] = {
    base: 'SauceLabs',
    browserName: mobile[key][3],
    appiumVersion: '1.6.4',
    deviceName: mobile[key][0],
    deviceOrientation: 'portrait',
    platformVersion: mobile[key][2],
    platformName: mobile[key][1]
  };
});
<commit_msg>update safari test platform<commit_after>var desktop = {
  'mac-chrome-latest'       : ['OS X 10.12', 'chrome', '60.0'],
  'mac-firefox-latest'      : ['OS X 10.12', 'firefox', '54.0'],
  'mac-safari-latest'       : ['OS X 10.12', 'safari', '11.0'],
  'mac-chrome-previous'     : ['OS X 10.11', 'chrome', '59.0'],
  'mac-firefox-previous'    : ['OS X 10.11', 'firefox', '53.0'],
  'mac-safari-previous'     : ['OS X 10.11', 'safari', '10.0'],

  'windows-chrome-latest'   : ['Windows 10', 'chrome', '60.0'],
  'windows-firefox-latest'  : ['Windows 10', 'firefox', '54.0'],
  'windows-edge-latest'     : ['Windows 10', 'microsoftedge', '15.15063'],
  'windows-ie-latest'       : ['Windows 8.1', 'internet explorer', '11.0'],
  'windows-chrome-previous' : ['Windows 8.1', 'chrome', '59.0'],
  'windows-firefox-previous': ['Windows 8.1', 'firefox', '53.0'],
  'windows-edge-previous'   : ['Windows 10', 'microsoftedge', '14.14393'],

  'linux-chrome-latest'     : ['Linux', 'chrome', '48.0'],
  'linux-firefox-latest'    : ['Linux', 'firefox', '45.0'],
  'linux-chrome-previous'   : ['Linux', 'chrome', '47.0'],
  'linux-firefox-previous'  : ['Linux', 'firefox', '44.0']
};

var mobile = {
  'ios-latest'        : ['iPhone 7 Plus', 'iOS', '10.3', 'Safari'],
  'ios-previous'      : ['iPhone 6 Plus', 'iOS', '9.3', 'Safari'],

  'android-latest'    : ['Android GoogleAPI Emulator', 'Android', '7.1', 'Chrome'],
  'android-previous'  : ['Android GoogleAPI Emulator', 'Android', '6.0', 'Chrome']
};

Object.keys(desktop).forEach(function(key) {
  module.exports[key] = {
    base: 'SauceLabs',
    browserName: desktop[key][1],
    version: desktop[key][2],
    platform: desktop[key][0]
  };
});

Object.keys(mobile).forEach(function(key) {
  module.exports[key] = {
    base: 'SauceLabs',
    browserName: mobile[key][3],
    appiumVersion: '1.6.4',
    deviceName: mobile[key][0],
    deviceOrientation: 'portrait',
    platformVersion: mobile[key][2],
    platformName: mobile[key][1]
  };
});
<|endoftext|>


<commit_before>var http = require("http");
var url = require("url");
var request = require('request');
var async = require('async');

var port = process.env['PORT'] || process.argv[2] || 8899;
var clientPort = process.env['CLIENT_PORT'] || process.argv[3] || 8888;

var clients = {};

http.createServer(function(req, res) {
  var uri = url.parse(req.url,true);
  var pathname = uri.pathname;
  var query = uri.query;
  if(/^\/?client-ping$/.test(pathname)){
    clients[getClientIp(req)] = true
    response.writeHead(200, {"Content-Type":"application/json"});
    response.write(JSON.stringify({response : "client recognized."}));
    response.end();
  } else if(/^\/?skip$/.test(pathname)){
    var ips = Object.keys(clients);
    async.map(ips,function(client,done){
      request({
        url : client + ":" + clientPort,
        qs : query
      },function(err,res,body){
        try {
          var response = JSON.parse(body)
        } catch(e){
          var response = {error : e}
        } finally {
          done(null,response);
        }
      })
    },function(err,responses){
      if(err){
        response.writeHead(500, {"Content-Type": "text/plain"});
        response.write("Error encountered:\n",err);
        response.end();
      } else {
        response.writeHead(200, {"Content-Type":"application/json"});
        var response = {};
        ips.forEach(function(ip,idx){
          response[ip] = responses[idx];
        })
        response.write(JSON.stringify({responses : responses}));
        response.end();
      }
    })
  } else {
      response.writeHead(404, {"Content-Type": "text/plain"});
      response.write("404 Not Found\n");
      response.end();
  }
}).listen(parseInt(port, 10));

console.log("Skip server running at\n  => http://localhost:" + port + "/\nCTRL + C to shutdown");

process.on('uncaughtException',function(err){
  console.error("ERROR: uncaught exception",err);
})

// snippet taken from http://catapulty.tumblr.com/post/8303749793/heroku-and-node-js-how-to-get-the-client-ip-address
function getClientIp(req) {
  var ipAddress;
  // The request may be forwarded from local web server.
  var forwardedIpsStr = req.header('x-forwarded-for');
  if (forwardedIpsStr) {
    // 'x-forwarded-for' header may return multiple IP addresses in
    // the format: "client IP, proxy 1 IP, proxy 2 IP" so take the
    // the first one
    var forwardedIps = forwardedIpsStr.split(',');
    ipAddress = forwardedIps[0];
  }
  if (!ipAddress) {
    // If request was not forwarded
    ipAddress = req.connection.remoteAddress;
  }
  return ipAddress;
};<commit_msg>work on heroku server<commit_after>var http = require("http");
var url = require("url");
var request = require('request');
var async = require('async');

var port = process.env['PORT'] || process.argv[2] || 8899;
var clientPort = process.env['CLIENT_PORT'] || process.argv[3] || 8888;

var clients = {};

http.createServer(function(req, res) {
  var uri = url.parse(req.url,true);
  var pathname = uri.pathname;
  var query = uri.query;
  console.log(clients)
  if(/^\/?client-ping$/.test(pathname)){
    var ip = getClientIp(req);
    clients[ip] = true
    res.writeHead(200, {"Content-Type":"application/json"});
    res.write(JSON.stringify({response : "client ip recognized : " + ip}));
    res.end();
  } else if(/^\/?skip$/.test(pathname)){
    var ips = Object.keys(clients);
    async.map(ips,function(client,done){
      var address = client + ":" + clientPort;
      console.log("dispatching skip request to: ",address);
      request({
        url : address,
        qs : query
      },function(err,res,body){
        try {
          var response = JSON.parse(body)
        } catch(e){
          var response = {error : "error parsing response : " + e.message}
        } finally {
          done(null,response);
        }
      })
    },function(err,responses){
      if(err){
        res.writeHead(500, {"Content-Type": "text/plain"});
        res.write("Error encountered:\n",err);
        res.end();
      } else {
        res.writeHead(200, {"Content-Type":"application/json"});
        var response = {};
        ips.forEach(function(ip,idx){
          response[ip] = responses[idx];
        })
        res.write(JSON.stringify({responses : response}));
        res.end();
      }
    })
  } else {
      res.writeHead(404, {"Content-Type": "text/plain"});
      res.write("404 Not Found\n");
      res.end();
  }
}).listen(parseInt(port, 10));

console.log("Skip server running at\n  => http://localhost:" + port + "/\nCTRL + C to shutdown");

process.on('uncaughtException',function(err){
  console.error("ERROR: uncaught exception",err);
})

// snippet taken from http://catapulty.tumblr.com/post/8303749793/heroku-and-node-js-how-to-get-the-client-ip-address
function getClientIp(req) {
  var ipAddress;
  // The request may be forwarded from local web server.
  var forwardedIpsStr = req.headers['x-forwarded-for'];
  if (forwardedIpsStr) {
    // 'x-forwarded-for' header may return multiple IP addresses in
    // the format: "client IP, proxy 1 IP, proxy 2 IP" so take the
    // the first one
    var forwardedIps = forwardedIpsStr.split(',');
    ipAddress = forwardedIps[0];
  }
  if (!ipAddress) {
    // If request was not forwarded
    ipAddress = req.connection.remoteAddress;
  }
  return ipAddress;
};<|endoftext|>


<commit_before>'use strict';
//Displays name (from input) on each genre page
if(localStorage.length > 0) {
  console.log('Retrieving from localStorage: ' + localStorage.getItem('name'));
  var nameContainerEl = document.getElementById('name-container');
  console.log('Name.html location for p element: ' + nameContainerEl );
  var paraEl = document.createElement('p');
  //Create text node
  var text = document.createTextNode(localStorage.getItem('name'));
  paraEl.appendChild(text);
  nameContainerEl.appendChild(paraEl);
}
<commit_msg>Sets attribute on p element for name<commit_after>'use strict';
//Displays name (from input) on each genre page
if(localStorage.length > 0) {
  console.log('Retrieving from localStorage: ' + localStorage.getItem('name'));
  var nameContainerEl = document.getElementById('name-container');
  console.log('Name.html location for p element: ' + nameContainerEl );
  var paraEl = document.createElement('p');
  //Adds id for styling purposes
  p.setAttribute('id','persistent-name');
  //Create text node
  var text = document.createTextNode(localStorage.getItem('name'));
  paraEl.appendChild(text);
  nameContainerEl.appendChild(paraEl);
}
<|endoftext|>


<commit_before>import Immutable from 'immutable';

import {
  Models,
  Types
} from 'lattice';

import * as PermissionsActionTypes from '../../permissions/PermissionsActionTypes';
import * as PrincipalsActionTypes from '../../principals/PrincipalsActionTypes';

import * as OrgActionTypes from '../actions/OrganizationActionTypes';
import * as OrgsActionTypes from '../actions/OrganizationsActionTypes';

import { AUTHENTICATED_USER } from '../../../utils/Consts/UserRoleConsts';

const {
  PrincipalBuilder,
  RoleBuilder
} = Models;

const {
  ActionTypes,
  PermissionTypes,
  PrincipalTypes
} = Types;

/*
 * TODO: we probably need a better pattern than "isSearchingUsers". as an example, this reducer will execute the
 * search users success case since it is possible to dispatch the search users action from anywhere in the app.
 * I think we need a more intelligent approach to dispatching generic actions so that 1) they are specifically tied to
 * the container that is dispatching them, and 2) we avoid having the reducer handle a generic action.
 */

const INITIAL_STATE = Immutable.fromJS({
  isCreatingOrg: false,
  isFetchingOrg: false,
  isFetchingOrgs: false,
  isSearchingOrgs: false,
  isSearchingUsers: false,
  organizations: Immutable.Map(),
  visibleOrganizationIds: Immutable.Set(),
  usersSearchResults: Immutable.Map(),
  members: Immutable.List(),
  roles: Immutable.List()
});

export default function organizationsReducer(state = INITIAL_STATE, action :Object) {

  switch (action.type) {

    case OrgActionTypes.CREATE_ORG_REQUEST:
      return state.set('isCreatingOrg', true);

    case OrgActionTypes.CREATE_ORG_FAILURE:
      return state.set('isCreatingOrg', false);

    case OrgsActionTypes.FETCH_ORG_REQUEST:
      return state.set('isFetchingOrg', true);

    case OrgsActionTypes.FETCH_ORG_FAILURE: {
      // TODO: not sure if clearing the Organization data isright. re-evaluate this decision,
      return state
        .setIn(['organizations', action.orgId], Immutable.Map())
        .set('isFetchingOrg', false);
    }

    case OrgsActionTypes.FETCH_ORGS_REQUEST:
      return state.set('isFetchingOrgs', true);

    case OrgsActionTypes.FETCH_ORGS_FAILURE:
      return state.set('isFetchingOrgs', false);

    case OrgsActionTypes.SEARCH_ORGS_REQUEST:
      return state.set('isSearchingOrgs', true);

    case OrgsActionTypes.SEARCH_ORGS_FAILURE:
      return state.set('isSearchingOrgs', false);

    case OrgsActionTypes.FETCH_ORG_SUCCESS: {

      // TODO: do merge if organization exists

      const organization = action.organization;
      return state
        .setIn(['organizations', organization.id], Immutable.fromJS(organization))
        .set('isCreatingOrg', false)
        .set('isFetchingOrg', false);
    }

    case OrgsActionTypes.FETCH_ORGS_SUCCESS: {

      const orgs = {};
      action.organizations.forEach((org) => {
        orgs[org.id] = org;
        orgs[org.id].isOwner = false;
        orgs[org.id].isPublic = false;
        orgs[org.id].permissions = {};
        Object.keys(PermissionTypes).forEach((permissionType) => {
          orgs[org.id].permissions[permissionType] = false;
        });
      });

      const organizations = Immutable.fromJS(orgs);

      return state
        .set('organizations', organizations)
        .set('visibleOrganizationIds', Immutable.Set(organizations.keys()))
        .set('isFetchingOrgs', false);
    }

    case OrgsActionTypes.FETCH_ORGS_AUTHORIZATIONS_SUCCESS: {

      let newState = state;

      action.authorizations.forEach((authorization) => {

        if (!authorization.aclKey || authorization.aclKey.length !== 1) {
          return;
        }

        const orgId = authorization.aclKey[0];
        const organization = state.getIn(['organizations', orgId], Immutable.Map());

        if (organization.isEmpty()) {
          return;
        }

        const decoratedOrganization = organization
          .set('isOwner', authorization.permissions[PermissionTypes.OWNER] === true)
          .set('permissions', Immutable.fromJS(authorization.permissions));

        newState = newState.setIn(['organizations', orgId], decoratedOrganization);
      });

      return newState;
    }

    case OrgActionTypes.CREATE_ORG_SUCCESS: {

      /*
       * we are *not* setting "isCreatingOrg" to false here because creating an organization involves 2 steps:
       *
       *   1. make the HTTP request to create the organization and get back the org UUID
       *   2. once we actually have the org UUID, we route from /org/new to /org/{orgId}, which does a fetch
       *
       * once we've successfully routed to /org/{orgId} and finished the fetch is when we're done with the process of
       * creating an organization. as such, we set "isCreatingOrg" to false in the FETCH_ORG_SUCCESS case.
       */

      const organization = action.organization;
      return state.setIn(['organizations', organization.id], Immutable.Map(organization));
    }

    case OrgActionTypes.DELETE_ORG_SUCCESS: {

      const orgId = action.orgId;
      return state.deleteIn(['organizations', orgId]);
    }

    case OrgActionTypes.UPDATE_ORG_TITLE_SUCCESS: {

      return state.setIn(['organizations', action.orgId, 'title'], action.title);
    }

    case OrgActionTypes.ADD_DOMAIN_TO_ORG_SUCCESS: {

      const orgId = action.orgId;
      const currentEmails = state.getIn(['organizations', orgId, 'emails'], Immutable.List());
      const updatedEmails = currentEmails.push(action.emailDomain);
      return state.setIn(['organizations', orgId, 'emails'], updatedEmails);
    }

    case OrgActionTypes.REMOVE_DOMAIN_FROM_ORG_SUCCESS: {

      const orgId = action.orgId;
      const currentEmails = state.getIn(['organizations', orgId, 'emails'], Immutable.List());

      const index = currentEmails.findIndex((email) => {
        return email === action.emailDomain;
      });

      if (index < 0) {
        return state;
      }

      const updatedEmails = currentEmails.delete(index);
      return state.setIn(['organizations', orgId, 'emails'], updatedEmails);
    }

    case OrgActionTypes.ADD_ROLE_TO_ORG_SUCCESS: {

      const newRole = (new RoleBuilder())
        .setId(action.roleId)
        .setOrganizationId(action.role.organizationId)
        .setTitle(action.role.title)
        .setPrincipal(action.role.principal)
        .build();

      const orgId = action.role.organizationId;
      const currentRoles = state.getIn(['organizations', orgId, 'roles'], Immutable.List());
      const updatedRoles = currentRoles.push(Immutable.Map(newRole));
      return state.setIn(['organizations', orgId, 'roles'], updatedRoles);
    }

    case OrgActionTypes.REMOVE_ROLE_FROM_ORG_SUCCESS: {

      const currentRoles = state.getIn(['organizations', action.orgId, 'roles'], Immutable.List());

      const index = currentRoles.findIndex((role) => {
        return role.get('id') === action.roleId;
      });

      if (index < 0) {
        return state;
      }

      const updatedRoles = currentRoles.delete(index);
      return state.setIn(['organizations', action.orgId, 'roles'], updatedRoles);
    }

    case OrgActionTypes.ADD_MEMBER_TO_ORG_SUCCESS: {

      const orgId = action.orgId;
      const newMemberPrincipal = (new PrincipalBuilder())
        .setType(PrincipalTypes.USER)
        .setId(action.memberId)
        .build();

      const currentMembers = state.getIn(['organizations', orgId, 'members'], Immutable.List());
      const updatedMembers = currentMembers.push(Immutable.Map(newMemberPrincipal));

      // update users search results to remove the member just added
      const usersSearchResults = state.get('usersSearchResults');
      let updatedUserSearchResults = usersSearchResults;
      if (!usersSearchResults.isEmpty()) {
        updatedUserSearchResults = usersSearchResults.filter((user) => {
          return user.get('user_id') !== action.memberId;
        });
      }

      return state
        .setIn(['organizations', orgId, 'members'], updatedMembers)
        .set('usersSearchResults', updatedUserSearchResults);
    }

    case OrgActionTypes.REMOVE_MEMBER_FROM_ORG_SUCCESS: {

      const orgId = action.orgId;
      const currentMembers = state.getIn(
        ['organizations', orgId, 'members'],
        Immutable.List()
      );

      const index = currentMembers.findIndex((member) => {
        return member.get('id') === action.memberId;
      });

      if (index < 0) {
        return state;
      }

      const updatedMembers = currentMembers.delete(index);
      return state.setIn(['organizations', orgId, 'members'], updatedMembers);
    }

    case OrgsActionTypes.SHOW_ALL_ORGS: {

      const organizations = state.get('organizations');
      return state.set('visibleOrganizationIds', Immutable.Set(organizations.keys()));
    }

    case OrgActionTypes.CLEAR_USER_SEARCH_RESULTS:
      return state
        .set('isSearchingUsers', false)
        .set('usersSearchResults', Immutable.Map());

    case OrgsActionTypes.SEARCH_ORGS_SUCCESS: {

      const orgIds = Immutable.Set().withMutations((set) => {
        action.searchResults.hits.forEach((result) => {
          set.add(result.id);
        });
      });

      return state
        .set('visibleOrganizationIds', orgIds)
        .set('isSearchingOrgs', false);
    }

    case PrincipalsActionTypes.SEARCH_ALL_USERS_REQUEST:
      return state
        .set('isSearchingUsers', true)
        .set('usersSearchResults', Immutable.Map());

    case PrincipalsActionTypes.SEARCH_ALL_USERS_FAILURE:
      return state
        .set('isSearchingUsers', false)
        .set('usersSearchResults', Immutable.Map());

    // TODO: probably need to break this out into its own reducer, along with the organizations earch
    case PrincipalsActionTypes.SEARCH_ALL_USERS_SUCCESS: {

      // only update state if the users search request was dispatched by us
      if (state.get('isSearchingUsers') === false) {
        return state;
      }

      // TODO: filter out search results that include members already part of the organization being viewed

      return state
        .set('usersSearchResults', Immutable.fromJS(action.searchResults))
        .set('isSearchingUsers', false);
    }

    // it is possible to dispatch GET_ACL_REQUEST from anywhere in the app
    // TODO: consider refactoring, as it is very similar to the PermissionsActionTypes.UPDATE_ACL_SUCCESS case
    case PermissionsActionTypes.GET_ACL_SUCCESS: {

      if (!action.aclKey || action.aclKey.length !== 1) {
        return state;
      }

      const orgId = action.aclKey[0];
      const organization = state.getIn(['organizations', orgId], Immutable.Map());

      if (organization.isEmpty()) {
        return state;
      }

      // an Organization is considered to be public if it has READ permissions for AUTHENTICATED_USER principals
      // TODO: yuck!
      let isPublic= false;
      action.acl.aces.forEach((ace) => {
        if (ace.principal.id === AUTHENTICATED_USER) {
          ace.permissions.forEach((permission) => {
            if (permission === PermissionTypes.READ) {
              isPublic = true;
            }
          });
        }
      });

      const decoratedOrganization = organization.set('isPublic', isPublic);
      return state.setIn(['organizations', orgId], decoratedOrganization);
    }

    // it is possible to dispatch UPDATE_ACL_REQUEST from anywhere in the app
    // TODO: consider refactoring, as it is very similar to the PermissionsActionTypes.GET_ACL_SUCCESS case
    case PermissionsActionTypes.UPDATE_ACL_SUCCESS: {

      if (!action.aclData
          || !action.aclData.acl
          || !action.aclData.acl.aclKey
          || action.aclData.acl.aclKey.length !== 1) {
        return state;
      }

      const orgId = action.aclData.acl.aclKey[0];
      const organization = state.getIn(['organizations', orgId], Immutable.Map());

      if (organization.isEmpty()) {
        return state;
      }

      // an Organization is considered to be public if it has READ permissions for AUTHENTICATED_USER principals
      // TODO: yuck!
      let isPublic = organization.get('isPublic');
      action.aclData.acl.aces.forEach((ace) => {
        if (ace.principal.id === AUTHENTICATED_USER) {
          ace.permissions.forEach((permission) => {
            if (permission === PermissionTypes.READ && action.aclData.action === ActionTypes.SET) {
              isPublic = true;
            }
            else if (permission === PermissionTypes.READ && action.aclData.action === ActionTypes.REMOVE) {
              isPublic = false;
            }
          });
        }
      });

      const decoratedOrganization = organization.set('isPublic', isPublic);
      return state.setIn(['organizations', orgId], decoratedOrganization);
    }

    case OrgActionTypes.FETCH_MEMBERS_SUCCESS:
      return state.set('members', Immutable.fromJS(action.members));

    case OrgActionTypes.FETCH_ROLES_SUCCESS:
      return state.set('roles', Immutable.fromJS(action.roles));

    default:
      return state;
  }
}
<commit_msg>Added error spacing.<commit_after>import Immutable from 'immutable';

import {
  Models,
  Types
} from 'lattice';

import * as PermissionsActionTypes from '../../permissions/PermissionsActionTypes';
import * as PrincipalsActionTypes from '../../principals/PrincipalsActionTypes';

import * as OrgActionTypes from '../actions/OrganizationActionTypes';
import * as OrgsActionTypes from '../actions/OrganizationsActionTypes';

import { AUTHENTICATED_USER } from '../../../utils/Consts/UserRoleConsts';

const {
  PrincipalBuilder,
  RoleBuilder
} = Models;

const {
  ActionTypes,
  PermissionTypes,
  PrincipalTypes
} = Types;

/*
 * TODO: we probably need a better pattern than "isSearchingUsers". as an example, this reducer will execute the
 * search users success case since it is possible to dispatch the search users action from anywhere in the app.
 * I think we need a more intelligent approach to dispatching generic actions so that 1) they are specifically tied to
 * the container that is dispatching them, and 2) we avoid having the reducer handle a generic action.
 */

const INITIAL_STATE = Immutable.fromJS({
  isCreatingOrg: false,
  isFetchingOrg: false,
  isFetchingOrgs: false,
  isSearchingOrgs: false,
  isSearchingUsers: false,
  organizations: Immutable.Map(),
  visibleOrganizationIds: Immutable.Set(),
  usersSearchResults: Immutable.Map(),
  members: Immutable.List(),
  roles: Immutable.List()
});

export default function organizationsReducer(state = INITIAL_STATE, action :Object) {

  switch (action.type) {

    case OrgActionTypes.CREATE_ORG_REQUEST:
      return state.set('isCreatingOrg', true);

    case OrgActionTypes.CREATE_ORG_FAILURE:
      return state.set('isCreatingOrg', false);

    case OrgsActionTypes.FETCH_ORG_REQUEST:
      return state.set('isFetchingOrg', true);

    case OrgsActionTypes.FETCH_ORG_FAILURE: {
      // TODO: not sure if clearing the Organization data isright. re-evaluate this decision,
      return state
        .setIn(['organizations', action.orgId], Immutable.Map())
        .set('isFetchingOrg', false);
    }

    case OrgsActionTypes.FETCH_ORGS_REQUEST:
      return state.set('isFetchingOrgs', true);

    case OrgsActionTypes.FETCH_ORGS_FAILURE:
      return state.set('isFetchingOrgs', false);

    case OrgsActionTypes.SEARCH_ORGS_REQUEST:
      return state.set('isSearchingOrgs', true);

    case OrgsActionTypes.SEARCH_ORGS_FAILURE:
      return state.set('isSearchingOrgs', false);

    case OrgsActionTypes.FETCH_ORG_SUCCESS: {

      // TODO: do merge if organization exists

      const organization = action.organization;
      return state
        .setIn(['organizations', organization.id], Immutable.fromJS(organization))
        .set('isCreatingOrg', false)
        .set('isFetchingOrg', false);
    }

    case OrgsActionTypes.FETCH_ORGS_SUCCESS: {

      const orgs = {};
      action.organizations.forEach((org) => {
        orgs[org.id] = org;
        orgs[org.id].isOwner = false;
        orgs[org.id].isPublic = false;
        orgs[org.id].permissions = {};
        Object.keys(PermissionTypes).forEach((permissionType) => {
          orgs[org.id].permissions[permissionType] = false;
        });
      });

      const organizations = Immutable.fromJS(orgs);

      return state
        .set('organizations', organizations)
        .set('visibleOrganizationIds', Immutable.Set(organizations.keys()))
        .set('isFetchingOrgs', false);
    }

    case OrgsActionTypes.FETCH_ORGS_AUTHORIZATIONS_SUCCESS: {

      let newState = state;

      action.authorizations.forEach((authorization) => {

        if (!authorization.aclKey || authorization.aclKey.length !== 1) {
          return;
        }

        const orgId = authorization.aclKey[0];
        const organization = state.getIn(['organizations', orgId], Immutable.Map());

        if (organization.isEmpty()) {
          return;
        }

        const decoratedOrganization = organization
          .set('isOwner', authorization.permissions[PermissionTypes.OWNER] === true)
          .set('permissions', Immutable.fromJS(authorization.permissions));

        newState = newState.setIn(['organizations', orgId], decoratedOrganization);
      });

      return newState;
    }

    case OrgActionTypes.CREATE_ORG_SUCCESS: {

      /*
       * we are *not* setting "isCreatingOrg" to false here because creating an organization involves 2 steps:
       *
       *   1. make the HTTP request to create the organization and get back the org UUID
       *   2. once we actually have the org UUID, we route from /org/new to /org/{orgId}, which does a fetch
       *
       * once we've successfully routed to /org/{orgId} and finished the fetch is when we're done with the process of
       * creating an organization. as such, we set "isCreatingOrg" to false in the FETCH_ORG_SUCCESS case.
       */

      const organization = action.organization;
      return state.setIn(['organizations', organization.id], Immutable.Map(organization));
    }

    case OrgActionTypes.DELETE_ORG_SUCCESS: {

      const orgId = action.orgId;
      return state.deleteIn(['organizations', orgId]);
    }

    case OrgActionTypes.UPDATE_ORG_TITLE_SUCCESS: {

      return state.setIn(['organizations', action.orgId, 'title'], action.title);
    }

    case OrgActionTypes.ADD_DOMAIN_TO_ORG_SUCCESS: {

      const orgId = action.orgId;
      const currentEmails = state.getIn(['organizations', orgId, 'emails'], Immutable.List());
      const updatedEmails = currentEmails.push(action.emailDomain);
      return state.setIn(['organizations', orgId, 'emails'], updatedEmails);
    }

    case OrgActionTypes.REMOVE_DOMAIN_FROM_ORG_SUCCESS: {

      const orgId = action.orgId;
      const currentEmails = state.getIn(['organizations', orgId, 'emails'], Immutable.List());

      const index = currentEmails.findIndex((email) => {
        return email === action.emailDomain;
      });

      if (index < 0) {
        return state;
      }

      const updatedEmails = currentEmails.delete(index);
      return state.setIn(['organizations', orgId, 'emails'], updatedEmails);
    }

    case OrgActionTypes.ADD_ROLE_TO_ORG_SUCCESS: {

      const newRole = (new RoleBuilder())
        .setId(action.roleId)
        .setOrganizationId(action.role.organizationId)
        .setTitle(action.role.title)
        .setPrincipal(action.role.principal)
        .build();

      const orgId = action.role.organizationId;
      const currentRoles = state.getIn(['organizations', orgId, 'roles'], Immutable.List());
      const updatedRoles = currentRoles.push(Immutable.Map(newRole));
      return state.setIn(['organizations', orgId, 'roles'], updatedRoles);
    }

    case OrgActionTypes.REMOVE_ROLE_FROM_ORG_SUCCESS: {

      const currentRoles = state.getIn(['organizations', action.orgId, 'roles'], Immutable.List());

      const index = currentRoles.findIndex((role) => {
        return role.get('id') === action.roleId;
      });

      if (index < 0) {
        return state;
      }

      const updatedRoles = currentRoles.delete(index);
      return state.setIn(['organizations', action.orgId, 'roles'], updatedRoles);
    }

    case OrgActionTypes.ADD_MEMBER_TO_ORG_SUCCESS: {

      const orgId = action.orgId;
      const newMemberPrincipal = (new PrincipalBuilder())
        .setType(PrincipalTypes.USER)
        .setId(action.memberId)
        .build();

      const currentMembers = state.getIn(['organizations', orgId, 'members'], Immutable.List());
      const updatedMembers = currentMembers.push(Immutable.Map(newMemberPrincipal));

      // update users search results to remove the member just added
      const usersSearchResults = state.get('usersSearchResults');
      let updatedUserSearchResults = usersSearchResults;
      if (!usersSearchResults.isEmpty()) {
        updatedUserSearchResults = usersSearchResults.filter((user) => {
          return user.get('user_id') !== action.memberId;
        });
      }

      return state
        .setIn(['organizations', orgId, 'members'], updatedMembers)
        .set('usersSearchResults', updatedUserSearchResults);
    }

    case OrgActionTypes.REMOVE_MEMBER_FROM_ORG_SUCCESS: {

      const orgId = action.orgId;
      const currentMembers = state.getIn(
        ['organizations', orgId, 'members'],
        Immutable.List()
      );

      const index = currentMembers.findIndex((member) => {
        return member.get('id') === action.memberId;
      });

      if (index < 0) {
        return state;
      }

      const updatedMembers = currentMembers.delete(index);
      return state.setIn(['organizations', orgId, 'members'], updatedMembers);
    }

    case OrgsActionTypes.SHOW_ALL_ORGS: {

      const organizations = state.get('organizations');
      return state.set('visibleOrganizationIds', Immutable.Set(organizations.keys()));
    }

    case OrgActionTypes.CLEAR_USER_SEARCH_RESULTS:
      return state
        .set('isSearchingUsers', false)
        .set('usersSearchResults', Immutable.Map());

    case OrgsActionTypes.SEARCH_ORGS_SUCCESS: {

      const orgIds = Immutable.Set().withMutations((set) => {
        action.searchResults.hits.forEach((result) => {
          set.add(result.id);
        });
      });

      return state
        .set('visibleOrganizationIds', orgIds)
        .set('isSearchingOrgs', false);
    }

    case PrincipalsActionTypes.SEARCH_ALL_USERS_REQUEST:
      return state
        .set('isSearchingUsers', true)
        .set('usersSearchResults', Immutable.Map());

    case PrincipalsActionTypes.SEARCH_ALL_USERS_FAILURE:
      return state
        .set('isSearchingUsers', false)
        .set('usersSearchResults', Immutable.Map());

    // TODO: probably need to break this out into its own reducer, along with the organizations earch
    case PrincipalsActionTypes.SEARCH_ALL_USERS_SUCCESS: {

      // only update state if the users search request was dispatched by us
      if (state.get('isSearchingUsers') === false) {
        return state;
      }

      // TODO: filter out search results that include members already part of the organization being viewed

      return state
        .set('usersSearchResults', Immutable.fromJS(action.searchResults))
        .set('isSearchingUsers', false);
    }

    // it is possible to dispatch GET_ACL_REQUEST from anywhere in the app
    // TODO: consider refactoring, as it is very similar to the PermissionsActionTypes.UPDATE_ACL_SUCCESS case
    case PermissionsActionTypes.GET_ACL_SUCCESS: {

      if (!action.aclKey || action.aclKey.length !== 1) {
        return state;
      }

      const orgId = action.aclKey[0];
      const organization = state.getIn(['organizations', orgId], Immutable.Map());

      if (organization.isEmpty()) {
        return state;
      }

      // an Organization is considered to be public if it has READ permissions for AUTHENTICATED_USER principals
      // TODO: yuck!
      let isPublic = false;
      action.acl.aces.forEach((ace) => {
        if (ace.principal.id === AUTHENTICATED_USER) {
          ace.permissions.forEach((permission) => {
            if (permission === PermissionTypes.READ) {
              isPublic = true;
            }
          });
        }
      });

      const decoratedOrganization = organization.set('isPublic', isPublic);
      return state.setIn(['organizations', orgId], decoratedOrganization);
    }

    // it is possible to dispatch UPDATE_ACL_REQUEST from anywhere in the app
    // TODO: consider refactoring, as it is very similar to the PermissionsActionTypes.GET_ACL_SUCCESS case
    case PermissionsActionTypes.UPDATE_ACL_SUCCESS: {

      if (!action.aclData
          || !action.aclData.acl
          || !action.aclData.acl.aclKey
          || action.aclData.acl.aclKey.length !== 1) {
        return state;
      }

      const orgId = action.aclData.acl.aclKey[0];
      const organization = state.getIn(['organizations', orgId], Immutable.Map());

      if (organization.isEmpty()) {
        return state;
      }

      // an Organization is considered to be public if it has READ permissions for AUTHENTICATED_USER principals
      // TODO: yuck!
      let isPublic = organization.get('isPublic');
      action.aclData.acl.aces.forEach((ace) => {
        if (ace.principal.id === AUTHENTICATED_USER) {
          ace.permissions.forEach((permission) => {
            if (permission === PermissionTypes.READ && action.aclData.action === ActionTypes.SET) {
              isPublic = true;
            }
            else if (permission === PermissionTypes.READ && action.aclData.action === ActionTypes.REMOVE) {
              isPublic = false;
            }
          });
        }
      });

      const decoratedOrganization = organization.set('isPublic', isPublic);
      return state.setIn(['organizations', orgId], decoratedOrganization);
    }

    case OrgActionTypes.FETCH_MEMBERS_SUCCESS:
      return state.set('members', Immutable.fromJS(action.members));

    case OrgActionTypes.FETCH_ROLES_SUCCESS:
      return state.set('roles', Immutable.fromJS(action.roles));

    default:
      return state;
  }
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2010 Andrew Fresh <andrew@afresh1.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

function TalksAssistant() {
	this.talks = new Talks();
	this.talks.favorite = new Favorites();
}

TalksAssistant.prototype = {
	compressors: [],
	currentListLength: 0,
    lastWhen: '',
	setup: function() {
		Mojo.Log.info("Setup TalksAssistant");

		this.talks.registerWatcher(this.updateList.bind(this));
		this.talks.setup();

		this.controller.setupWidget(
		Mojo.Menu.appMenu, {
			omitDefaultItems: true
		},
		{
			visible: true,
			items: [
			Mojo.Menu.editItem, {
				label: "Refresh talks",
				command: "do-refresh"
			},
			//Mojo.Menu.prefsItem,
			Mojo.Menu.helpItem]
		});

		this.controller.setupWidget(Mojo.Menu.commandMenu, {
			menuClass: 'no-fade',
		},
		{
			visible: true,
			items: [{
				toggleCmd: "filter-favorites-all",
				items: [{
					iconPath: "images/fav_filter.png",
					command: "filter-favorites"
				}]
			},
			{
				label: "Locations",
				submenu: "locations-menu"
			},
			{},
			{
				label: "Notice",
				command: "do-notice"
			}]
		});

		this.controller.setupWidget('locations-menu', null, this.locationsMenuModel = {
			items: [],
			toggleCmd: "filter-locations-all"
		});

		this.controller.setupWidget("refresh-spinner", {
			spinnerSize: Mojo.Widget.spinnerLarge
		},
		{
			spinning: true
		});

		this.controller.setupWidget("TalksList", this.talksListAttributes = {
			itemTemplate: "talks/talks-item-template",
			//listTemplate: "talks/talks-list-template",
			dividerTemplate: 'talks/talks-divider-template',
			dividerFunction: function(modelItem) {
				return modelItem.day
			},
			swipeToDelete: false,
			reordarable: false,
			filterFunction: this.talks.search.bind(this),
			renderLimit: 200,
			onItemRendered: this.renderTalk.bind(this)
		},
		this.filterListModel = {
			items: this.talks.list
		});

		this.controller.listen(
		this.controller.get("TalksList"), Mojo.Event.listTap, this.listTapHandler.bind(this));

	},

	cleanup: function() {
		Mojo.Log.info("Cleanup talks-assistant");
		this.controller.stopListening(
		this.controller.get("TalksList"), Mojo.Event.listTap, this.listTapHandler.bind(this));

		this.talks.list.invoke('cleanup');
	},

	activate: function() {
		// XXX for some reason, items that are collapsed away don't keep
		// XXX their favorite icon if we do this.
		//this.talks.setFilter();
	},

	renderTalk: function(listWidget, itemModel, itemNode) {
		//Mojo.Log.info("rendered:", itemModel.id);
        if (this.lastWhen === itemModel.when) {
            this.controller.get("separator" + itemModel.id).hide();
        }
        this.lastWhen = itemModel.when;
		itemModel.setup(this.controller);
	},

	listTapHandler: function(event) {
		Mojo.Log.info("got list tap for item:", event.item.id);
		if (!event.originalEvent.target.id) {
			this.controller.stageController.pushScene("talk", event.item);
		}
	},

	handleCommand: function(event) {
		Mojo.Log.info("Got event " + event.type);
		var command;

		if (event.type === Mojo.Event.command) {
			var modelName, command = event.command.split("-", 3);
			if (command[0] === "filter") {
				Mojo.Log.info("Filter command " + event.command);
				this.talks.setFilter(command[1], command[2]);

				modelName = command[1] + "MenuModel";
				if (this[modelName]) {
					this[modelName].toggleCmd = event.command;
				}
			}
			else {
				switch (event.command) {
				case "do-refresh":
					{
						this.controller.get("refresh-spinner").mojo.start();
						this.controller.get("refresh-scrim").show();
						this.talks.refresh();
						break
					};
				case "do-notice":
					{
						this.talks.showNotice(this.controller);
						break
					}
				default:
					Mojo.Log.info("Event command " + event.command);
				}
			}
		}
	},

	updateList: function() {
		Mojo.Log.info("Updating filterListModel");
		var available, modelName, command, menus = ["locations"];

		var widget = this.controller.get("TalksList");

		if (this.talks.list.length < widget.mojo.getLength()) {
			widget.mojo.noticeRemovedItems(this.talks.list.length, widget.mojo.getLength() - 1);
		}
		else if (this.talksListAttributes.renderLimit < this.talks.list.length + 1) {
			this.talksListAttributes.renderLimit = this.talks.list.length + 1;;
		}

		widget.mojo.noticeUpdatedItems(0, this.talks.list);

		menus.each(function(menu) {
			modelName = menu + "MenuModel";
			command = "filter-" + menu + "-all";
			available = this.talks[menu]();

			this[modelName].items = [{
				label: "All",
				command: command
			}]

			if (available) {
				available.each(function(item) {
					this[modelName].items.push({
						label: item,
						command: "filter-" + menu + "-" + item.toLowerCase()
					});
				}.bind(this));
			}
		}.bind(this));

		this.talks.days().each(this.attachCompressorHandler.bind(this));

		this.controller.get("refresh-scrim").hide();
		this.controller.get("refresh-spinner").mojo.stop();
	},

	attachCompressorHandler: function(item) {
		Mojo.Log.info("attachCompressorHandler:", item);

		var compress = this.controller.get('compress' + item);
		var compressable = this.controller.get('compressable' + item)
		if (compress && compressable) {
			compressable.hide();

			if (!compress.hasClassName('compressor')) {
				compress.addClassName('compressor')
				compress.compressorID = item.day;
				this.controller.listen(compress, Mojo.Event.tap, this._handleDrawerSelection.bind(this, compressable, item));
				this.compressors.push(compress);
			}
			var categoryItems = this.getElementsOfCategory(item.category);
			categoryItems.each(this.moveElementsIntoDividers.bind(this));
		}

	},

	moveElementsIntoDividers: function(item, index) {
		//mv elements into their appropriate collapsable dividers element#{id}
		var compressable = this.controller.get('compressable' + item.day);
		compressable.insert(this.controller.get('element' + item.id));
		this.controller.get('element' + item.id).show();
	},

	moveElementsOutOfDividers: function(item, index) {
		//mv elements back into their original holders element_holder#{id}
		this.controller.get('element_holder' + item.id).insert(this.controller.get('element' + item.id));
	},

	getElementsOfCategory: function(day) {
		return this.talks.list.findAll(function(example) {
			return example.day === day;
		});
	},

	/* code below from stylematters
 *
 * BSD Licensed, see:
 * http://github.com/palm/stylematters/blob/master/app/assistants/collapsibledividers-example-assistant.js
 */

	_handleDrawerSelection: function(drawer, category, event) {
		Mojo.Log.info("handleDrawerSelection");
		var targetRow = this.controller.get(event.target);
		if (!targetRow.hasClassName("selection_target")) {
			Mojo.Log.info("handleSoftwareSelection !selection_target");
			targetRow = targetRow.up('.selection_target');
		}

		if (targetRow) {
			var toggleButton = targetRow.down("div.arrow_button");
			if (!toggleButton.hasClassName('palm-arrow-expanded') && ! toggleButton.hasClassName('palm-arrow-closed')) {
				return;
			}
			var show = toggleButton.className;
			Mojo.Log.info("handleSoftwareSelection open/close " + show);
			this._toggleShowHideFolders(targetRow, this.controller.window.innerHeight, null, category);
		}
	},

	_toggleShowHideFolders: function(rowElement, viewPortMidway, noScroll, category) {
		if (!rowElement.hasClassName("details")) {
			return;
		}

		var toggleButton = rowElement.down("div.arrow_button");
		if (!toggleButton.hasClassName('palm-arrow-expanded') && ! toggleButton.hasClassName('palm-arrow-closed')) {
			return;
		}

		var categoryItems = this.getElementsOfCategory(category);
		//Mojo.Log.info(category);
		//console.dir(categoryItems);
		var showFavorites = toggleButton.hasClassName('palm-arrow-closed');
		var folderContainer = rowElement.down('.collapsor');
		if (showFavorites) {
			var maxHeight = folderContainer.getHeight();
			toggleButton.addClassName('palm-arrow-expanded');
			toggleButton.removeClassName('palm-arrow-closed');
			folderContainer.setStyle({
				height: '1px'
			});
			folderContainer.show();

			// See if the div should scroll up a little to show the contents
			var elementTop = folderContainer.viewportOffset().top;
			var scroller = Mojo.View.getScrollerForElement(folderContainer);
			if (elementTop > viewPortMidway && scroller && ! noScroll) {
				//Using setTimeout to give the animation time enough to give the div enough height to scroll to
				var scrollToPos = scroller.mojo.getScrollPosition().top - (elementTop - viewPortMidway);
				setTimeout(function() {
					scroller.mojo.scrollTo(undefined, scrollToPos, true);
				},
				200);
			}
		} else {
			folderContainer.setStyle({
				height: maxHeight + 'px'
			});
			toggleButton.addClassName('palm-arrow-closed');
			toggleButton.removeClassName('palm-arrow-expanded');
			categoryItems.each(this.moveElementsIntoDividers.bind(this));
			var maxHeight = folderContainer.getHeight();
		}

		var options = {
			reverse: ! showFavorites,
			onComplete: this._animationComplete.bind(this, showFavorites, categoryItems, folderContainer),
			curve: 'over-easy',
			from: 1,
			to: maxHeight,
			duration: 0.4
		};
		Mojo.Animation.animateStyle(folderContainer, 'height', 'bezier', options);
	},

	_animationComplete: function(showFavorites, categoryItems, folderContainer) {
		if (!showFavorites) {
			folderContainer.hide();
		} else {
			categoryItems.each(this.moveElementsOutOfDividers.bind(this));
		}
		folderContainer.setStyle({
			height: 'auto'
		});
	}
};

<commit_msg>have to redo the headers whenever the list changes.<commit_after>/*
 * Copyright (c) 2010 Andrew Fresh <andrew@afresh1.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

function TalksAssistant() {
	this.talks = new Talks();
	this.talks.favorite = new Favorites();
}

TalksAssistant.prototype = {
	compressors: [],
	currentListLength: 0,
    lastWhen: '',
	setup: function() {
		Mojo.Log.info("Setup TalksAssistant");

		this.talks.registerWatcher(this.updateList.bind(this));
		this.talks.setup();

		this.controller.setupWidget(
		Mojo.Menu.appMenu, {
			omitDefaultItems: true
		},
		{
			visible: true,
			items: [
			Mojo.Menu.editItem, {
				label: "Refresh talks",
				command: "do-refresh"
			},
			//Mojo.Menu.prefsItem,
			Mojo.Menu.helpItem]
		});

		this.controller.setupWidget(Mojo.Menu.commandMenu, {
			menuClass: 'no-fade',
		},
		{
			visible: true,
			items: [{
				toggleCmd: "filter-favorites-all",
				items: [{
					iconPath: "images/fav_filter.png",
					command: "filter-favorites"
				}]
			},
			{
				label: "Locations",
				submenu: "locations-menu"
			},
			{},
			{
				label: "Notice",
				command: "do-notice"
			}]
		});

		this.controller.setupWidget('locations-menu', null, this.locationsMenuModel = {
			items: [],
			toggleCmd: "filter-locations-all"
		});

		this.controller.setupWidget("refresh-spinner", {
			spinnerSize: Mojo.Widget.spinnerLarge
		},
		{
			spinning: true
		});

		this.controller.setupWidget("TalksList", this.talksListAttributes = {
			itemTemplate: "talks/talks-item-template",
			//listTemplate: "talks/talks-list-template",
			dividerTemplate: 'talks/talks-divider-template',
			dividerFunction: function(modelItem) {
				return modelItem.day
			},
			swipeToDelete: false,
			reordarable: false,
			filterFunction: this.talks.search.bind(this),
			renderLimit: 200,
			onItemRendered: this.renderTalk.bind(this)
		},
		this.filterListModel = {
			items: this.talks.list
		});

		this.controller.listen(
		this.controller.get("TalksList"), Mojo.Event.listTap, this.listTapHandler.bind(this));

	},

	cleanup: function() {
		Mojo.Log.info("Cleanup talks-assistant");
		this.controller.stopListening(
		this.controller.get("TalksList"), Mojo.Event.listTap, this.listTapHandler.bind(this));

		this.talks.list.invoke('cleanup');
	},

	activate: function() {
		// XXX for some reason, items that are collapsed away don't keep
		// XXX their favorite icon if we do this.
		//this.talks.setFilter();
	},

	renderTalk: function(listWidget, itemModel, itemNode) {
		//Mojo.Log.info("rendered:", itemModel.id);
		itemModel.setup(this.controller);
	},

	listTapHandler: function(event) {
		Mojo.Log.info("got list tap for item:", event.item.id);
		if (!event.originalEvent.target.id) {
			this.controller.stageController.pushScene("talk", event.item);
		}
	},

	handleCommand: function(event) {
		Mojo.Log.info("Got event " + event.type);
		var command;

		if (event.type === Mojo.Event.command) {
			var modelName, command = event.command.split("-", 3);
			if (command[0] === "filter") {
				Mojo.Log.info("Filter command " + event.command);
				this.talks.setFilter(command[1], command[2]);

				modelName = command[1] + "MenuModel";
				if (this[modelName]) {
					this[modelName].toggleCmd = event.command;
				}
			}
			else {
				switch (event.command) {
				case "do-refresh":
					{
						this.controller.get("refresh-spinner").mojo.start();
						this.controller.get("refresh-scrim").show();
						this.talks.refresh();
						break
					};
				case "do-notice":
					{
						this.talks.showNotice(this.controller);
						break
					}
				default:
					Mojo.Log.info("Event command " + event.command);
				}
			}
		}
	},

	updateList: function() {
		Mojo.Log.info("Updating filterListModel");
		var available, modelName, command, menus = ["locations"];

		var widget = this.controller.get("TalksList");

		if (this.talks.list.length < widget.mojo.getLength()) {
			widget.mojo.noticeRemovedItems(this.talks.list.length, widget.mojo.getLength() - 1);
		}
		else if (this.talksListAttributes.renderLimit < this.talks.list.length + 1) {
			this.talksListAttributes.renderLimit = this.talks.list.length + 1;;
		}

		widget.mojo.noticeUpdatedItems(0, this.talks.list);

		menus.each(function(menu) {
			modelName = menu + "MenuModel";
			command = "filter-" + menu + "-all";
			available = this.talks[menu]();

			this[modelName].items = [{
				label: "All",
				command: command
			}]

			if (available) {
				available.each(function(item) {
					this[modelName].items.push({
						label: item,
						command: "filter-" + menu + "-" + item.toLowerCase()
					});
				}.bind(this));
			}
		}.bind(this));

        this.talks.list.each(function(itemModel) {
            if (this.lastWhen === itemModel.when) {
                this.controller.get("separator" + itemModel.id).hide();
            }
            else {
                this.controller.get("separator" + itemModel.id).show();
            }
            this.lastWhen = itemModel.when;
        }.bind(this));

		this.talks.days().each(this.attachCompressorHandler.bind(this));

		this.controller.get("refresh-scrim").hide();
		this.controller.get("refresh-spinner").mojo.stop();
	},

	attachCompressorHandler: function(item) {
		Mojo.Log.info("attachCompressorHandler:", item);

		var compress = this.controller.get('compress' + item);
		var compressable = this.controller.get('compressable' + item)
		if (compress && compressable) {
			compressable.hide();

			if (!compress.hasClassName('compressor')) {
				compress.addClassName('compressor')
				compress.compressorID = item.day;
				this.controller.listen(compress, Mojo.Event.tap, this._handleDrawerSelection.bind(this, compressable, item));
				this.compressors.push(compress);
			}
			var categoryItems = this.getElementsOfCategory(item.category);
			categoryItems.each(this.moveElementsIntoDividers.bind(this));
		}

	},

	moveElementsIntoDividers: function(item, index) {
		//mv elements into their appropriate collapsable dividers element#{id}
		var compressable = this.controller.get('compressable' + item.day);
		compressable.insert(this.controller.get('element' + item.id));
		this.controller.get('element' + item.id).show();
	},

	moveElementsOutOfDividers: function(item, index) {
		//mv elements back into their original holders element_holder#{id}
		this.controller.get('element_holder' + item.id).insert(this.controller.get('element' + item.id));
	},

	getElementsOfCategory: function(day) {
		return this.talks.list.findAll(function(example) {
			return example.day === day;
		});
	},

	/* code below from stylematters
 *
 * BSD Licensed, see:
 * http://github.com/palm/stylematters/blob/master/app/assistants/collapsibledividers-example-assistant.js
 */

	_handleDrawerSelection: function(drawer, category, event) {
		Mojo.Log.info("handleDrawerSelection");
		var targetRow = this.controller.get(event.target);
		if (!targetRow.hasClassName("selection_target")) {
			Mojo.Log.info("handleSoftwareSelection !selection_target");
			targetRow = targetRow.up('.selection_target');
		}

		if (targetRow) {
			var toggleButton = targetRow.down("div.arrow_button");
			if (!toggleButton.hasClassName('palm-arrow-expanded') && ! toggleButton.hasClassName('palm-arrow-closed')) {
				return;
			}
			var show = toggleButton.className;
			Mojo.Log.info("handleSoftwareSelection open/close " + show);
			this._toggleShowHideFolders(targetRow, this.controller.window.innerHeight, null, category);
		}
	},

	_toggleShowHideFolders: function(rowElement, viewPortMidway, noScroll, category) {
		if (!rowElement.hasClassName("details")) {
			return;
		}

		var toggleButton = rowElement.down("div.arrow_button");
		if (!toggleButton.hasClassName('palm-arrow-expanded') && ! toggleButton.hasClassName('palm-arrow-closed')) {
			return;
		}

		var categoryItems = this.getElementsOfCategory(category);
		//Mojo.Log.info(category);
		//console.dir(categoryItems);
		var showFavorites = toggleButton.hasClassName('palm-arrow-closed');
		var folderContainer = rowElement.down('.collapsor');
		if (showFavorites) {
			var maxHeight = folderContainer.getHeight();
			toggleButton.addClassName('palm-arrow-expanded');
			toggleButton.removeClassName('palm-arrow-closed');
			folderContainer.setStyle({
				height: '1px'
			});
			folderContainer.show();

			// See if the div should scroll up a little to show the contents
			var elementTop = folderContainer.viewportOffset().top;
			var scroller = Mojo.View.getScrollerForElement(folderContainer);
			if (elementTop > viewPortMidway && scroller && ! noScroll) {
				//Using setTimeout to give the animation time enough to give the div enough height to scroll to
				var scrollToPos = scroller.mojo.getScrollPosition().top - (elementTop - viewPortMidway);
				setTimeout(function() {
					scroller.mojo.scrollTo(undefined, scrollToPos, true);
				},
				200);
			}
		} else {
			folderContainer.setStyle({
				height: maxHeight + 'px'
			});
			toggleButton.addClassName('palm-arrow-closed');
			toggleButton.removeClassName('palm-arrow-expanded');
			categoryItems.each(this.moveElementsIntoDividers.bind(this));
			var maxHeight = folderContainer.getHeight();
		}

		var options = {
			reverse: ! showFavorites,
			onComplete: this._animationComplete.bind(this, showFavorites, categoryItems, folderContainer),
			curve: 'over-easy',
			from: 1,
			to: maxHeight,
			duration: 0.4
		};
		Mojo.Animation.animateStyle(folderContainer, 'height', 'bezier', options);
	},

	_animationComplete: function(showFavorites, categoryItems, folderContainer) {
		if (!showFavorites) {
			folderContainer.hide();
		} else {
			categoryItems.each(this.moveElementsOutOfDividers.bind(this));
		}
		folderContainer.setStyle({
			height: 'auto'
		});
	}
};

<|endoftext|>


<commit_before>
const _ = require('lodash'); // client-side
import { ObjectId } from 'mongodb';

import Tyr        from '../tyr';
import Type       from '../core/type';
import Collection from '../core/collection';

const LinkType = new Type({
  name: 'link',

  compile(compiler, field) {
    const relate = field.def.relate;

    switch (relate) {
    case undefined:
    case 'associate':
    case 'owns':
    case 'ownedBy':
      field.relate = relate || 'associate';
      break;
    default:
      throw compiler.err(field.path, '"relate" must be one of "associate", "owns", or "ownedBy" if present');
    }
  },

  fromClient(field, value) {
    const link = field.link,
          linkField = link.fields[field.link.def.primaryKey.field];

    try {
      return linkField.type.def.fromClient(linkField, value);
    } catch (err) {
      if (_.isString(value) && link.isStatic() && link.labelField) {
        // integer and ObjectId parse errors
        if (err.toString().match(/Invalid integer|24 hex/)) {
          const v = link.byLabel(value);

          if (v) {
            return v[link.def.primaryKey.field];
          }
        }
      }

      throw err;
    }
  },

  toClient(field, value) {
    return value ? (ObjectId.isValid(value.toString()) ? value.toString() : value) : value;
  },

  format(field, value) {
    return field.link.idToLabel(value);
  }
});

Collection.prototype.links = function(search) {
  search = search || {};

  const relate = search.relate;
  switch (relate) {
  case 'associate':
  case 'owns':
  case 'ownedBy':
  case undefined:
  case null:
    break;
  default:
    throw new Error(`Unknown links() relate option "${relate}"`);
  }

  function matchesRelate(field) {
    return !relate || field.relate === relate;
  }

  const direction = search.direction;
  switch (direction) {
  case 'incoming':
  case 'outgoing':
  case undefined:
  case null:
    break;
  default:
    throw new Error(`Unknown links() direction option "${direction}"`);
  }

  const links = [];

  if (!direction || direction === 'outgoing') {
    _.each(this.paths, field => {
      if (field.link && matchesRelate(field)) {
        links.push(field);
      }
    });
  }

  if (!direction || direction === 'incoming') {
    for (const col of Tyr.collections) {
      if (col !== this) {
        _.each(col.paths, field => {
          if (field.link === this && matchesRelate(field)) {
            links.push(field);
          }
        });
      }
    }
  }

  return links;
};

Collection.prototype.references = async function(opts) {
  const thisCol = this,
        refs = [];

  let ids = opts.ids || opts.id;
  if (!Array.isArray(ids)) {
    ids = [ ids ];
  }

  const fields = opts.idsOnly ? { _id: 1 } : undefined;

  let exclude = opts.exclude || [];
  if (!Array.isArray(exclude)) {
    exclude = [ exclude ];
  }

  const makeMatch = values => values.length > 1 ? { $in: values } : values[0];

  const idMatch = makeMatch(ids);
  let uidMatch;

  for (const col of Tyr.collections) {
    if (exclude.indexOf(col) >= 0) {
      continue;
    }

    const queries = [];

    _.each(col.paths, field => {
      if (field.link === this) {
        queries.push({ [field.spath]: idMatch });
      } else if (field.type.name === 'uid') {
        if (!uidMatch) {
          uidMatch = makeMatch(ids.map(id => thisCol.idToUid(id)));
        }

        queries.push({ [field.spath]: uidMatch });
      }
    });

    if (queries.length) {
      const query = queries.length > 1 ? { $or: queries } : queries[0];
      //console.log(col.name, '=', query);
      refs.push(...await col.findAll({ query, fields }));
    }
  }

  return refs;
};

export default LinkType;
<commit_msg>minor fix for references<commit_after>
const _ = require('lodash'); // client-side
import { ObjectId } from 'mongodb';

import Tyr        from '../tyr';
import Type       from '../core/type';
import Collection from '../core/collection';

const LinkType = new Type({
  name: 'link',

  compile(compiler, field) {
    const relate = field.def.relate;

    switch (relate) {
    case undefined:
    case 'associate':
    case 'owns':
    case 'ownedBy':
      field.relate = relate || 'associate';
      break;
    default:
      throw compiler.err(field.path, '"relate" must be one of "associate", "owns", or "ownedBy" if present');
    }
  },

  fromClient(field, value) {
    const link = field.link,
          linkField = link.fields[field.link.def.primaryKey.field];

    try {
      return linkField.type.def.fromClient(linkField, value);
    } catch (err) {
      if (_.isString(value) && link.isStatic() && link.labelField) {
        // integer and ObjectId parse errors
        if (err.toString().match(/Invalid integer|24 hex/)) {
          const v = link.byLabel(value);

          if (v) {
            return v[link.def.primaryKey.field];
          }
        }
      }

      throw err;
    }
  },

  toClient(field, value) {
    return value ? (ObjectId.isValid(value.toString()) ? value.toString() : value) : value;
  },

  format(field, value) {
    return field.link.idToLabel(value);
  }
});

Collection.prototype.links = function(search) {
  search = search || {};

  const relate = search.relate;
  switch (relate) {
  case 'associate':
  case 'owns':
  case 'ownedBy':
  case undefined:
  case null:
    break;
  default:
    throw new Error(`Unknown links() relate option "${relate}"`);
  }

  function matchesRelate(field) {
    return !relate || field.relate === relate;
  }

  const direction = search.direction;
  switch (direction) {
  case 'incoming':
  case 'outgoing':
  case undefined:
  case null:
    break;
  default:
    throw new Error(`Unknown links() direction option "${direction}"`);
  }

  const links = [];

  if (!direction || direction === 'outgoing') {
    _.each(this.paths, field => {
      if (field.link && matchesRelate(field)) {
        links.push(field);
      }
    });
  }

  if (!direction || direction === 'incoming') {
    for (const col of Tyr.collections) {
      if (col !== this) {
        _.each(col.paths, field => {
          if (field.link === this && matchesRelate(field)) {
            links.push(field);
          }
        });
      }
    }
  }

  return links;
};

Collection.prototype.references = async function(opts) {
  const thisCol = this,
        refs = [];

  let ids = opts.ids || opts.id;
  if (!Array.isArray(ids)) {
    ids = ids ? [ ids ] : [];
  }

  if (!ids.length) {
    return refs;
  }

  const fields = opts.idsOnly ? { _id: 1 } : undefined;

  let exclude = opts.exclude || [];
  if (!Array.isArray(exclude)) {
    exclude = [ exclude ];
  }

  const makeMatch = values => values.length > 1 ? { $in: values } : values[0];

  const idMatch = makeMatch(ids);
  let uidMatch;

  for (const col of Tyr.collections) {
    if (exclude.indexOf(col) >= 0) {
      continue;
    }

    const queries = [];

    _.each(col.paths, field => {
      if (field.link === this) {
        queries.push({ [field.spath]: idMatch });
      } else if (field.type.name === 'uid') {
        if (!uidMatch) {
          uidMatch = makeMatch(ids.map(id => thisCol.idToUid(id)));
        }

        queries.push({ [field.spath]: uidMatch });
      }
    });

    if (queries.length) {
      const query = queries.length > 1 ? { $or: queries } : queries[0];
      //console.log(col.name, '=', query);
      refs.push(...await col.findAll({ query, fields }));
    }
  }

  return refs;
};

export default LinkType;
<|endoftext|>