<commit_before>import ClientProject from "../client-project";
import OSProject from "../os-project";
import Contributor from "../contributor";

import set from "can-set";
import DefineMap from "can-define/map/";
import DefineList from "can-define/list/";
import superModel from '../../lib/super-model';
import feathersClient from '../feathers-client';

import moment from "moment";
import MonthlyOSProject from "./monthly-os-project";
import MonthlyClientProject from "./monthly-client-project";
import MonthlyContributions from "./monthly-contributions";

import algebra from '../algebras';
import idMerge from "can-connect/helpers/id-merge";

// Issue #152: every change causes browser to repaint.
// Reason: `can-define/map/map`'s `setProps`:
//     .attr is called on deep replace
//     .replace is called w/o letting it go through the setter.
//     As a result the list gets completely replaced, when its needed to be merged with the updated items.
// Could be removed or moved to the setter depending on a fix for https://github.com/canjs/can-define/issues/96.
[ MonthlyOSProject, MonthlyClientProject ].forEach( MapObj => {
  var attrOrig = MapObj.List.prototype.attr;
  MapObj.List.prototype.attr = function( items, replace ) {
    if ( replace === false && typeof items === "object" ) {
      idMerge( this, items, function( obj ){ return obj._id }, function( x ){
        if ( x instanceof MapObj ) {
          return x;
        }
        return new MapObj( x );
      });
      return this;
    } else {
      return attrOrig.apply( this, arguments );
    }
  };
} );

var ContributionMonth = DefineMap.extend("ContributionMonth",{
  _id: "string",
  __v:"number",
  date: "date",
  monthlyOSProjects: {
    Type: MonthlyOSProject.List,
    set: function(monthlyOSProjects){
      monthlyOSProjects.contributionMonth = this;
      return monthlyOSProjects;
    }
  },
  monthlyClientProjects: MonthlyClientProject.List,
  monthlyContributions: MonthlyContributions.List,
  calculations: {
    get: function() {
      var calculations = {
          clientProjects: {},
          totalDollarForAllClientProjects: 0,
          osProjects: {}
      };

      var clientProjectsUsingOSProject = {};
      var monthlyOSProjectMap = {};
      var totalCommissionedSignificance = 0;
      this.monthlyOSProjects.forEach( osProject => {
        monthlyOSProjectMap[osProject.osProjectRef._id] = osProject;
        if(osProject.commissioned) {
          totalCommissionedSignificance += osProject.significance;
        }
      });
      // for each client project, calculate out:
      // - rate (based on how many commissioned projects it uses) = 4 - 2 * (usedCommissionedSignificance / totalCommissionedSignificance)
      // - total = (rate * hours)
      // - totalSignificance - the total significance for this project
      // - osProjectsUsed - a map of the OS projects used
      this.monthlyClientProjects.forEach((monthlyClientProject) => {

        let totalSignificance = 0;
        let usedCommissionedSignificance = 0;
        let commissionedMonthlyOSProjects = [];
        let uncommissionedMonthlyOSProjects = [];

        monthlyClientProject.monthlyClientProjectsOSProjects.forEach( usedOSProjectRef => {
          var monthlyOSProject = monthlyOSProjectMap[usedOSProjectRef._id];
          if(monthlyOSProject) {
            // calculate needed significances
            if(monthlyOSProject.commissioned) {
              usedCommissionedSignificance += monthlyOSProject.significance;
              commissionedMonthlyOSProjects.push(monthlyOSProject);
            } else {
              uncommissionedMonthlyOSProjects.push(monthlyOSProject);
            }
            totalSignificance += monthlyOSProject.significance;

            // for an OS project, make it possible to get the clients using it
            if(!clientProjectsUsingOSProject[usedOSProjectRef._id]) {
              clientProjectsUsingOSProject[usedOSProjectRef._id] = [];
            }
            clientProjectsUsingOSProject[usedOSProjectRef._id].push(monthlyClientProject);
          }
        });

        // Don't divide by 0 if there are no commissioned projects
        if (totalCommissionedSignificance === 0) {
          totalCommissionedSignificance = 1;
        }

        let rate = 4 - 2 * (usedCommissionedSignificance / totalCommissionedSignificance);
        rate = isNaN(rate) ? 0 : rate; //handle the situation where there is not significance
        let totalAmount = parseFloat(Math.round((rate * monthlyClientProject.hours) * 100) / 100);

        calculations.totalDollarForAllClientProjects += totalAmount;

        calculations.clientProjects[monthlyClientProject.clientProjectRef._id] = {
          rate: parseFloat(Math.round(rate * 100) / 100),
          totalAmount,
          totalSignificance,
          commissionedMonthlyOSProjects,
          uncommissionedMonthlyOSProjects
        };

      });

      // once the rates are calculated, calculates for each OS project:
      // - total - for each clientProject using this project, take it's share
      this.monthlyOSProjects.forEach(function(osProject) {

        var clientProjects = clientProjectsUsingOSProject[osProject.osProjectRef._id];
        if(clientProjects) {
          calculations.osProjects[osProject.osProjectRef._id] = clientProjects.reduce(function(prev, monthlyClientProject){
            var clientProjectCalc = calculations.clientProjects[monthlyClientProject.clientProjectRef._id];
            return prev + (clientProjectCalc.totalAmount * osProject.significance / clientProjectCalc.totalSignificance);
          },0);
        } else {
          calculations.osProjects[osProject.osProjectRef._id] = 0;
        }
      });

      return calculations;
    }
  },

  // Can add using an osProject or monthlyOSProject
  addNewMonthlyOSProject( project ) {
    let monthlyOSProject;
    if (project instanceof MonthlyOSProject) {
      monthlyOSProject = project;
    }
    else {
      monthlyOSProject = new MonthlyOSProject({
        significance: 0,
        commissioned: false,
        osProjectRef: project,
        osProjectID: project._id
      });
    }
    this.monthlyOSProjects.push(monthlyOSProject);
    this.save().catch(err => {
      console.error("Failed saving the contributionMonth obj: ", err);
    });
    return monthlyOSProject;
  },
  removeMonthlyOSProject( monthlyOSProject ) {
    this.monthlyOSProjects.splice(this.monthlyOSProjects.indexOf(monthlyOSProject), 1);
    this.monthlyClientProjects.forEach( clientProject => {
      clientProject.monthlyClientProjectsOSProjects.splice(clientProject.monthlyClientProjectsOSProjects.indexOf(monthlyOSProject.osProjectRef), 1);
    });
    this.save().catch(err => {
      console.error("Failed saving the contributionMonth obj: ", err);
    });
  },

  commissionedMonthlyOSProjectsCountFor: function(monthlyClientProject) {
    if(this.calculations.clientProjects.hasOwnProperty(monthlyClientProject.clientProjectRef._id)) {
      return this.calculations.clientProjects[monthlyClientProject.clientProjectRef._id].commissionedMonthlyOSProjects.length;
    }
    return 0;
  },
  uncommissionedMonthlyOSProjectsCountFor: function(monthlyClientProject) {
    if(this.calculations.clientProjects.hasOwnProperty(monthlyClientProject.clientProjectRef._id)) {
      return this.calculations.clientProjects[monthlyClientProject.clientProjectRef._id].uncommissionedMonthlyOSProjects.length;
    }
    return 0;
  },
  removeClientProject: function(clientProject) {
    this.monthlyClientProjects.splice(this.monthlyClientProjects.indexOf(clientProject), 1);
  },
  getRate: function(monthlyClientProject) {

    if(this.calculations.clientProjects[monthlyClientProject.clientProjectRef._id]) {
        return this.calculations.clientProjects[monthlyClientProject.clientProjectRef._id].rate;
    }
    return 0;

  },
  getTotal: function(monthlyClientProject) {
    if(this.calculations.clientProjects[monthlyClientProject.clientProjectRef._id]) {
        return this.calculations.clientProjects[monthlyClientProject.clientProjectRef._id].totalAmount;
    }
    return 0;

  },

  addContribution(newContribution) {
    this.monthlyContributions.push(newContribution);
    this.save().then(function() {}, function() {
      console.error("Failed saving the contributionMonth obj: ", arguments);
    });
  },

  removeContribution(contribution) {
    const index = this.monthlyContributions.indexOf(contribution);
    this.monthlyContributions.splice(index, 1);
    this.save().then(function() {}, function() {
      console.error("Failed saving the contributionMonth obj: ", arguments);
    });
  }
});

ContributionMonth.List = DefineList.extend({
  "#": ContributionMonth,
  OSProjectContributionsMap: function(currentContributionMonth) {

    var OSProjectContributionsMap = {};
    this.forEach(contributionMonth => {
      if(moment(contributionMonth.date).isBefore(moment(currentContributionMonth.date).add(1, 'day'))) {
        var monthlyContributions = contributionMonth.monthlyContributions;
        monthlyContributions && monthlyContributions.length && monthlyContributions.forEach( monthlyContribution => {
          if( ! OSProjectContributionsMap[monthlyContribution.osProjectRef._id] ) {
            OSProjectContributionsMap[monthlyContribution.osProjectRef._id] = {
              contributors: {},
              totalPoints: 0
            };
          }

          if(! OSProjectContributionsMap[monthlyContribution.osProjectRef._id].contributors[monthlyContribution.contributorRef._id] ) {
            OSProjectContributionsMap[monthlyContribution.osProjectRef._id].contributors[monthlyContribution.contributorRef._id] = {
              points: monthlyContribution.points
            };
          }
          else {
            OSProjectContributionsMap[monthlyContribution.osProjectRef._id].contributors[monthlyContribution.contributorRef._id].points = OSProjectContributionsMap[monthlyContribution.osProjectRef._id].contributors[monthlyContribution.contributorRef._id].points + monthlyContribution.points;
          }

          OSProjectContributionsMap[monthlyContribution.osProjectRef._id].totalPoints = OSProjectContributionsMap[monthlyContribution.osProjectRef._id].totalPoints + monthlyContribution.points;

        });
      }
    });

    return OSProjectContributionsMap;
  }
});

var dataMassage = function(oType) {
  return function(item) {
    if (typeof item[oType + 'Id'] === 'object') {
      item[oType] = item[oType + 'Id'];
      item[oType + 'Id'] = item[oType]._id;
    }
  };
};

ContributionMonth.connection = superModel({
  parseInstanceProp: "data",
  Map: ContributionMonth,
  List: ContributionMonth.List,
  feathersService: feathersClient.service("/api/contribution_months"),
  name: "contributionMonth",
  algebra
});

ContributionMonth.algebra = algebra;

export default ContributionMonth;
<commit_msg>Fix issue with merging contributions during update<commit_after>import ClientProject from "../client-project";
import OSProject from "../os-project";
import Contributor from "../contributor";

import set from "can-set";
import DefineMap from "can-define/map/";
import DefineList from "can-define/list/";
import superModel from '../../lib/super-model';
import feathersClient from '../feathers-client';

import moment from "moment";
import MonthlyOSProject from "./monthly-os-project";
import MonthlyClientProject from "./monthly-client-project";
import MonthlyContributions from "./monthly-contributions";

import algebra from '../algebras';
import idMerge from "can-connect/helpers/id-merge";

// Issue #152: every change causes browser to repaint.
// Reason: `can-define/map/map`'s `setProps`:
//     .attr is called on deep replace
//     .replace is called w/o letting it go through the setter.
//     As a result the list gets completely replaced, when its needed to be merged with the updated items.
// Could be removed or moved to the setter depending on a fix for https://github.com/canjs/can-define/issues/96.
[ MonthlyOSProject, MonthlyClientProject, MonthlyContributions ].forEach( MapObj => {
  var attrOrig = MapObj.List.prototype.attr;
  MapObj.List.prototype.attr = function( items, replace ) {
    if ( replace === false && typeof items === "object" ) {
      idMerge( this, items, function( obj ){ return obj._id }, function( x ){
        if ( x instanceof MapObj ) {
          return x;
        }
        return new MapObj( x );
      });
      return this;
    } else {
      return attrOrig.apply( this, arguments );
    }
  };
} );

var ContributionMonth = DefineMap.extend("ContributionMonth",{
  _id: "string",
  __v:"number",
  date: "date",
  monthlyOSProjects: {
    Type: MonthlyOSProject.List,
    set: function(monthlyOSProjects){
      monthlyOSProjects.contributionMonth = this;
      return monthlyOSProjects;
    }
  },
  monthlyClientProjects: MonthlyClientProject.List,
  monthlyContributions: MonthlyContributions.List,
  calculations: {
    get: function() {
      var calculations = {
          clientProjects: {},
          totalDollarForAllClientProjects: 0,
          osProjects: {}
      };

      var clientProjectsUsingOSProject = {};
      var monthlyOSProjectMap = {};
      var totalCommissionedSignificance = 0;
      this.monthlyOSProjects.forEach( osProject => {
        monthlyOSProjectMap[osProject.osProjectRef._id] = osProject;
        if(osProject.commissioned) {
          totalCommissionedSignificance += osProject.significance;
        }
      });
      // for each client project, calculate out:
      // - rate (based on how many commissioned projects it uses) = 4 - 2 * (usedCommissionedSignificance / totalCommissionedSignificance)
      // - total = (rate * hours)
      // - totalSignificance - the total significance for this project
      // - osProjectsUsed - a map of the OS projects used
      this.monthlyClientProjects.forEach((monthlyClientProject) => {

        let totalSignificance = 0;
        let usedCommissionedSignificance = 0;
        let commissionedMonthlyOSProjects = [];
        let uncommissionedMonthlyOSProjects = [];

        monthlyClientProject.monthlyClientProjectsOSProjects.forEach( usedOSProjectRef => {
          var monthlyOSProject = monthlyOSProjectMap[usedOSProjectRef._id];
          if(monthlyOSProject) {
            // calculate needed significances
            if(monthlyOSProject.commissioned) {
              usedCommissionedSignificance += monthlyOSProject.significance;
              commissionedMonthlyOSProjects.push(monthlyOSProject);
            } else {
              uncommissionedMonthlyOSProjects.push(monthlyOSProject);
            }
            totalSignificance += monthlyOSProject.significance;

            // for an OS project, make it possible to get the clients using it
            if(!clientProjectsUsingOSProject[usedOSProjectRef._id]) {
              clientProjectsUsingOSProject[usedOSProjectRef._id] = [];
            }
            clientProjectsUsingOSProject[usedOSProjectRef._id].push(monthlyClientProject);
          }
        });

        // Don't divide by 0 if there are no commissioned projects
        if (totalCommissionedSignificance === 0) {
          totalCommissionedSignificance = 1;
        }

        let rate = 4 - 2 * (usedCommissionedSignificance / totalCommissionedSignificance);
        rate = isNaN(rate) ? 0 : rate; //handle the situation where there is not significance
        let totalAmount = parseFloat(Math.round((rate * monthlyClientProject.hours) * 100) / 100);

        calculations.totalDollarForAllClientProjects += totalAmount;

        calculations.clientProjects[monthlyClientProject.clientProjectRef._id] = {
          rate: parseFloat(Math.round(rate * 100) / 100),
          totalAmount,
          totalSignificance,
          commissionedMonthlyOSProjects,
          uncommissionedMonthlyOSProjects
        };

      });

      // once the rates are calculated, calculates for each OS project:
      // - total - for each clientProject using this project, take it's share
      this.monthlyOSProjects.forEach(function(osProject) {

        var clientProjects = clientProjectsUsingOSProject[osProject.osProjectRef._id];
        if(clientProjects) {
          calculations.osProjects[osProject.osProjectRef._id] = clientProjects.reduce(function(prev, monthlyClientProject){
            var clientProjectCalc = calculations.clientProjects[monthlyClientProject.clientProjectRef._id];
            return prev + (clientProjectCalc.totalAmount * osProject.significance / clientProjectCalc.totalSignificance);
          },0);
        } else {
          calculations.osProjects[osProject.osProjectRef._id] = 0;
        }
      });

      return calculations;
    }
  },

  // Can add using an osProject or monthlyOSProject
  addNewMonthlyOSProject( project ) {
    let monthlyOSProject;
    if (project instanceof MonthlyOSProject) {
      monthlyOSProject = project;
    }
    else {
      monthlyOSProject = new MonthlyOSProject({
        significance: 0,
        commissioned: false,
        osProjectRef: project,
        osProjectID: project._id
      });
    }
    this.monthlyOSProjects.push(monthlyOSProject);
    this.save().catch(err => {
      console.error("Failed saving the contributionMonth obj: ", err);
    });
    return monthlyOSProject;
  },
  removeMonthlyOSProject( monthlyOSProject ) {
    this.monthlyOSProjects.splice(this.monthlyOSProjects.indexOf(monthlyOSProject), 1);
    this.monthlyClientProjects.forEach( clientProject => {
      clientProject.monthlyClientProjectsOSProjects.splice(clientProject.monthlyClientProjectsOSProjects.indexOf(monthlyOSProject.osProjectRef), 1);
    });
    this.save().catch(err => {
      console.error("Failed saving the contributionMonth obj: ", err);
    });
  },

  commissionedMonthlyOSProjectsCountFor: function(monthlyClientProject) {
    if(this.calculations.clientProjects.hasOwnProperty(monthlyClientProject.clientProjectRef._id)) {
      return this.calculations.clientProjects[monthlyClientProject.clientProjectRef._id].commissionedMonthlyOSProjects.length;
    }
    return 0;
  },
  uncommissionedMonthlyOSProjectsCountFor: function(monthlyClientProject) {
    if(this.calculations.clientProjects.hasOwnProperty(monthlyClientProject.clientProjectRef._id)) {
      return this.calculations.clientProjects[monthlyClientProject.clientProjectRef._id].uncommissionedMonthlyOSProjects.length;
    }
    return 0;
  },
  removeClientProject: function(clientProject) {
    this.monthlyClientProjects.splice(this.monthlyClientProjects.indexOf(clientProject), 1);
  },
  getRate: function(monthlyClientProject) {

    if(this.calculations.clientProjects[monthlyClientProject.clientProjectRef._id]) {
        return this.calculations.clientProjects[monthlyClientProject.clientProjectRef._id].rate;
    }
    return 0;

  },
  getTotal: function(monthlyClientProject) {
    if(this.calculations.clientProjects[monthlyClientProject.clientProjectRef._id]) {
        return this.calculations.clientProjects[monthlyClientProject.clientProjectRef._id].totalAmount;
    }
    return 0;

  },

  addContribution(newContribution) {
    this.monthlyContributions.push(newContribution);
    this.save().then(function() {}, function() {
      console.error("Failed saving the contributionMonth obj: ", arguments);
    });
  },

  removeContribution(contribution) {
    const index = this.monthlyContributions.indexOf(contribution);
    this.monthlyContributions.splice(index, 1);
    this.save().then(function() {}, function() {
      console.error("Failed saving the contributionMonth obj: ", arguments);
    });
  }
});

ContributionMonth.List = DefineList.extend({
  "#": ContributionMonth,
  OSProjectContributionsMap: function(currentContributionMonth) {

    var OSProjectContributionsMap = {};
    this.forEach(contributionMonth => {
      if(moment(contributionMonth.date).isBefore(moment(currentContributionMonth.date).add(1, 'day'))) {
        var monthlyContributions = contributionMonth.monthlyContributions;
        monthlyContributions && monthlyContributions.length && monthlyContributions.forEach( monthlyContribution => {
          if( ! OSProjectContributionsMap[monthlyContribution.osProjectRef._id] ) {
            OSProjectContributionsMap[monthlyContribution.osProjectRef._id] = {
              contributors: {},
              totalPoints: 0
            };
          }

          if(! OSProjectContributionsMap[monthlyContribution.osProjectRef._id].contributors[monthlyContribution.contributorRef._id] ) {
            OSProjectContributionsMap[monthlyContribution.osProjectRef._id].contributors[monthlyContribution.contributorRef._id] = {
              points: monthlyContribution.points
            };
          }
          else {
            OSProjectContributionsMap[monthlyContribution.osProjectRef._id].contributors[monthlyContribution.contributorRef._id].points = OSProjectContributionsMap[monthlyContribution.osProjectRef._id].contributors[monthlyContribution.contributorRef._id].points + monthlyContribution.points;
          }

          OSProjectContributionsMap[monthlyContribution.osProjectRef._id].totalPoints = OSProjectContributionsMap[monthlyContribution.osProjectRef._id].totalPoints + monthlyContribution.points;

        });
      }
    });

    return OSProjectContributionsMap;
  }
});

var dataMassage = function(oType) {
  return function(item) {
    if (typeof item[oType + 'Id'] === 'object') {
      item[oType] = item[oType + 'Id'];
      item[oType + 'Id'] = item[oType]._id;
    }
  };
};

ContributionMonth.connection = superModel({
  parseInstanceProp: "data",
  Map: ContributionMonth,
  List: ContributionMonth.List,
  feathersService: feathersClient.service("/api/contribution_months"),
  name: "contributionMonth",
  algebra
});

ContributionMonth.algebra = algebra;

export default ContributionMonth;
<|endoftext|>


<commit_before>/**
 * StopFlash
 *
 * https://github.com/JWhile/StopFlash
 *
 * stopflash.js
 */

// class StopFlashUI extends Builder
function StopFlashUI(elements)
{
    this.super('div');

    this.tabs = new StopFlashTabs();

    this.append(new Builder('div')
            .className('head')
            .text('StopFlash'))
        .append(new Builder('div')
            .className('menu')
            .append(new Builder('a')
                .text('Home'))
            .append(new Builder('a')
                .text('Whitelist'))
            .append(new Builder('a')
                .text('Options')))
        .append(this.tabs)
        .append(new Builder('div')
            .className('foot')
            .html('https://github.com/JWhile/StopFlash'))
}
fus.extend(StopFlashUI, Builder);

// class StopFlashTabs extends Builder
function StopFlashTabs()
{
    this.super('div');
}
fus.extend(StopFlashTabs, Builder);

// main
var main = function(elements)
{
    new StopFlashUI(elements)
        .insert(document.body);
};

chrome.tabs.query({'highlighted': true, 'currentWindow': true}, function(tabs)
{
    chrome.tabs.sendMessage(tabs[0].id, {'getElements': 'stopflash'}, main);
});
<commit_msg>Remove StopFlashTabs class & build tabs system<commit_after>/**
 * StopFlash
 *
 * https://github.com/JWhile/StopFlash
 *
 * stopflash.js
 */

// class StopFlashUI extends Builder
function StopFlashUI(elements)
{
    this.super('div');

    this.content = new Builder('div')
        .append(new Builder('div')
            .className('tab')
            .text('Home tab'))
        .append(new Builder('div')
            .className('tab')
            .text('Whitelist tab'))
        .append(new Builder('div')
            .className('tab')
            .text('Options tab'));

    this.append(new Builder('div')
            .className('head')
            .text('StopFlash'))
        .append(new Builder('div')
            .className('menu')
            .append(new Builder('a')
                .text('Home')
                .event('click', function()
                {
                    self.content.css('left', '-300px');
                }))
            .append(new Builder('a')
                .text('Whitelist')
                .event('click', function()
                {
                    self.content.css('left', '-600px');
                }))
            .append(new Builder('a')
                .text('Options')
                .event('click', function()
                {
                    self.content.css('left', '-900px');
                })))
        .append(this.content)
        .append(new Builder('div')
            .className('foot')
            .html('https://github.com/JWhile/StopFlash'))
}
fus.extend(StopFlashUI, Builder);

// main
var main = function(elements)
{
    new StopFlashUI(elements)
        .insert(document.body);
};

chrome.tabs.query({'highlighted': true, 'currentWindow': true}, function(tabs)
{
    chrome.tabs.sendMessage(tabs[0].id, {'getElements': 'stopflash'}, main);
});
<|endoftext|>


<commit_before>// @flow

import React, { PureComponent } from 'react';
import {
    FlatList,
    TouchableWithoutFeedback,
    View
} from 'react-native';
import type { Dispatch } from 'redux';

import { getLocalParticipant, getParticipantCountWithFake } from '../../../base/participants';
import { connect } from '../../../base/redux';
import { shouldHideSelfView } from '../../../base/settings/functions.any';
import { setVisibleRemoteParticipants } from '../../actions.web';

import Thumbnail from './Thumbnail';
import styles from './styles';

/**
 * The type of the React {@link Component} props of {@link TileView}.
 */
type Props = {

    /**
     * Application's aspect ratio.
     */
    _aspectRatio: Symbol,

    /**
     * The number of columns.
     */
    _columns: number,

    /**
     * Whether or not to hide the self view.
     */
    _disableSelfView: boolean,

    /**
     * Application's viewport height.
     */
    _height: number,

    /**
     * The local participant.
     */
    _localParticipant: Object,

    /**
     * The number of participants in the conference.
     */
    _participantCount: number,

    /**
     * An array with the IDs of the remote participants in the conference.
     */
    _remoteParticipants: Array<string>,

    /**
     * The thumbnail height.
     */
    _thumbnailHeight: number,

    /**
     * Application's viewport height.
     */
    _width: number,

    /**
     * Invoked to update the receiver video quality.
     */
    dispatch: Dispatch<any>,

    /**
     * Callback to invoke when tile view is tapped.
     */
    onClick: Function
};

/**
 * An empty array. The purpose of the constant is to use the same reference every time we need an empty array.
 * This will prevent unnecessary re-renders.
 */
const EMPTY_ARRAY = [];

/**
 * Implements a React {@link PureComponent} which displays thumbnails in a two
 * dimensional grid.
 *
 * @augments PureComponent
 */
class TileView extends PureComponent<Props> {

    /**
     * The styles for the content container of the FlatList.
     */
    _contentContainerStyles: Object;

    /**
     * The styles for the FlatList.
     */
    _flatListStyles: Object;

    /**
     * The FlatList's viewabilityConfig.
     */
    _viewabilityConfig: Object;

    /**
     * Creates new TileView component.
     *
     * @param {Props} props - The props of the component.
     */
    constructor(props: Props) {
        super(props);

        this._keyExtractor = this._keyExtractor.bind(this);
        this._onViewableItemsChanged = this._onViewableItemsChanged.bind(this);
        this._renderThumbnail = this._renderThumbnail.bind(this);

        this._viewabilityConfig = {
            itemVisiblePercentThreshold: 30,
            minimumViewTime: 500
        };
        this._flatListStyles = {
            ...styles.flatListTileView
        };
        this._contentContainerStyles = {
            ...styles.contentContainer
        };
    }

    _keyExtractor: string => string;

    /**
     * Returns a key for a passed item of the list.
     *
     * @param {string} item - The user ID.
     * @returns {string} - The user ID.
     */
    _keyExtractor(item) {
        return item;
    }

    _onViewableItemsChanged: Object => void;

    /**
     * A handler for visible items changes.
     *
     * @param {Object} data - The visible items data.
     * @param {Array<Object>} data.viewableItems - The visible items array.
     * @returns {void}
     */
    _onViewableItemsChanged({ viewableItems = [] }: { viewableItems: Array<Object> }) {
        const { _disableSelfView } = this.props;

        if (viewableItems[0]?.index === 0 && !_disableSelfView) {
            // Skip the local thumbnail.
            viewableItems.shift();
        }

        if (viewableItems.length === 0) {
            // User might be fast-scrolling, it will stabilize.
            return;
        }

        // We are off by one in the remote participants array.
        const startIndex = viewableItems[0].index - (_disableSelfView ? 0 : 1);
        const endIndex = viewableItems[viewableItems.length - 1].index - (_disableSelfView ? 0 : 1);

        this.props.dispatch(setVisibleRemoteParticipants(startIndex, endIndex));
    }

    /**
     * Implements React's {@link Component#render()}.
     *
     * @inheritdoc
     * @returns {ReactElement}
     */
    render() {
        const { _columns, _height, _thumbnailHeight, _width, onClick } = this.props;
        const participants = this._getSortedParticipants();
        const initialRowsToRender = Math.ceil(_height / (_thumbnailHeight + (2 * styles.thumbnail.margin)));

        if (this._flatListStyles.minHeight !== _height || this._flatListStyles.minWidth !== _width) {
            this._flatListStyles = {
                ...styles.flatListTileView,
                minHeight: _height,
                minWidth: _width
            };
        }

        if (this._contentContainerStyles.minHeight !== _height || this._contentContainerStyles.minWidth !== _width) {
            this._contentContainerStyles = {
                ...styles.contentContainer,
                minHeight: _height,
                minWidth: _width
            };
        }

        return (
            <TouchableWithoutFeedback onPress = { onClick }>
                <View style = { styles.flatListContainer }>
                    <FlatList
                        bounces = { false }
                        contentContainerStyle = { this._contentContainerStyles }
                        data = { participants }
                        horizontal = { false }
                        initialNumToRender = { initialRowsToRender }
                        key = { _columns }
                        keyExtractor = { this._keyExtractor }
                        numColumns = { _columns }
                        onViewableItemsChanged = { this._onViewableItemsChanged }
                        renderItem = { this._renderThumbnail }
                        showsHorizontalScrollIndicator = { false }
                        showsVerticalScrollIndicator = { false }
                        style = { this._flatListStyles }
                        viewabilityConfig = { this._viewabilityConfig }
                        windowSize = { 2 } />
                </View>
            </TouchableWithoutFeedback>
        );
    }

    /**
     * Returns all participants with the local participant at the end.
     *
     * @private
     * @returns {Participant[]}
     */
    _getSortedParticipants() {
        const { _localParticipant, _remoteParticipants, _disableSelfView } = this.props;

        if (!_localParticipant) {
            return EMPTY_ARRAY;
        }

        if (_disableSelfView) {
            return _remoteParticipants;
        }

        return [ _localParticipant?.id, ..._remoteParticipants ];
    }

    _renderThumbnail: Object => Object;

    /**
     * Creates React Element to display each participant in a thumbnail.
     *
     * @private
     * @returns {ReactElement}
     */
    _renderThumbnail({ item/* , index , separators */ }) {
        const { _thumbnailHeight } = this.props;

        return (
            <Thumbnail
                disableTint = { true }
                height = { _thumbnailHeight }
                key = { item }
                participantID = { item }
                renderDisplayName = { true }
                tileView = { true } />)
        ;
    }
}

/**
 * Maps (parts of) the redux state to the associated {@code TileView}'s props.
 *
 * @param {Object} state - The redux state.
 * @private
 * @returns {Props}
 */
function _mapStateToProps(state) {
    const responsiveUi = state['features/base/responsive-ui'];
    const { remoteParticipants, tileViewDimensions } = state['features/filmstrip'];
    const disableSelfView = shouldHideSelfView(state);
    const { height } = tileViewDimensions.thumbnailSize;
    const { columns } = tileViewDimensions;

    return {
        _aspectRatio: responsiveUi.aspectRatio,
        _columns: columns,
        _disableSelfView: disableSelfView,
        _height: responsiveUi.clientHeight,
        _localParticipant: getLocalParticipant(state),
        _participantCount: getParticipantCountWithFake(state),
        _remoteParticipants: remoteParticipants,
        _thumbnailHeight: height,
        _width: responsiveUi.clientWidth
    };
}

export default connect(_mapStateToProps)(TileView);
<commit_msg>fix(rn, tileview) Add SafeAreaView to Tile View (#10642)<commit_after>// @flow

import React, { PureComponent } from 'react';
import {
    FlatList,
    SafeAreaView,
    TouchableWithoutFeedback
} from 'react-native';
import { withSafeAreaInsets } from 'react-native-safe-area-context';
import type { Dispatch } from 'redux';

import { getLocalParticipant, getParticipantCountWithFake } from '../../../base/participants';
import { connect } from '../../../base/redux';
import { shouldHideSelfView } from '../../../base/settings/functions.any';
import { setVisibleRemoteParticipants } from '../../actions.web';

import Thumbnail from './Thumbnail';
import styles from './styles';

/**
 * The type of the React {@link Component} props of {@link TileView}.
 */
type Props = {

    /**
     * Application's aspect ratio.
     */
    _aspectRatio: Symbol,

    /**
     * The number of columns.
     */
    _columns: number,

    /**
     * Whether or not to hide the self view.
     */
    _disableSelfView: boolean,

    /**
     * Application's viewport height.
     */
    _height: number,

    /**
     * The local participant.
     */
    _localParticipant: Object,

    /**
     * The number of participants in the conference.
     */
    _participantCount: number,

    /**
     * An array with the IDs of the remote participants in the conference.
     */
    _remoteParticipants: Array<string>,

    /**
     * The thumbnail height.
     */
    _thumbnailHeight: number,

    /**
     * Application's viewport height.
     */
    _width: number,

    /**
     * Invoked to update the receiver video quality.
     */
    dispatch: Dispatch<any>,

    /**
     * Object containing the safe area insets.
     */
    insets: Object,

    /**
     * Callback to invoke when tile view is tapped.
     */
    onClick: Function
};

/**
 * An empty array. The purpose of the constant is to use the same reference every time we need an empty array.
 * This will prevent unnecessary re-renders.
 */
const EMPTY_ARRAY = [];

/**
 * Implements a React {@link PureComponent} which displays thumbnails in a two
 * dimensional grid.
 *
 * @augments PureComponent
 */
class TileView extends PureComponent<Props> {

    /**
     * The styles for the content container of the FlatList.
     */
    _contentContainerStyles: Object;

    /**
     * The styles for the FlatList.
     */
    _flatListStyles: Object;

    /**
     * The FlatList's viewabilityConfig.
     */
    _viewabilityConfig: Object;

    /**
     * Creates new TileView component.
     *
     * @param {Props} props - The props of the component.
     */
    constructor(props: Props) {
        super(props);

        this._keyExtractor = this._keyExtractor.bind(this);
        this._onViewableItemsChanged = this._onViewableItemsChanged.bind(this);
        this._renderThumbnail = this._renderThumbnail.bind(this);

        this._viewabilityConfig = {
            itemVisiblePercentThreshold: 30,
            minimumViewTime: 500
        };
        this._flatListStyles = {
            ...styles.flatListTileView
        };
        this._contentContainerStyles = {
            ...styles.contentContainer,
            paddingBottom: this.props.insets?.bottom || 0
        };
    }

    _keyExtractor: string => string;

    /**
     * Returns a key for a passed item of the list.
     *
     * @param {string} item - The user ID.
     * @returns {string} - The user ID.
     */
    _keyExtractor(item) {
        return item;
    }

    _onViewableItemsChanged: Object => void;

    /**
     * A handler for visible items changes.
     *
     * @param {Object} data - The visible items data.
     * @param {Array<Object>} data.viewableItems - The visible items array.
     * @returns {void}
     */
    _onViewableItemsChanged({ viewableItems = [] }: { viewableItems: Array<Object> }) {
        const { _disableSelfView } = this.props;

        if (viewableItems[0]?.index === 0 && !_disableSelfView) {
            // Skip the local thumbnail.
            viewableItems.shift();
        }

        if (viewableItems.length === 0) {
            // User might be fast-scrolling, it will stabilize.
            return;
        }

        // We are off by one in the remote participants array.
        const startIndex = viewableItems[0].index - (_disableSelfView ? 0 : 1);
        const endIndex = viewableItems[viewableItems.length - 1].index - (_disableSelfView ? 0 : 1);

        this.props.dispatch(setVisibleRemoteParticipants(startIndex, endIndex));
    }

    /**
     * Implements React's {@link Component#render()}.
     *
     * @inheritdoc
     * @returns {ReactElement}
     */
    render() {
        const { _columns, _height, _thumbnailHeight, _width, onClick } = this.props;
        const participants = this._getSortedParticipants();
        const initialRowsToRender = Math.ceil(_height / (_thumbnailHeight + (2 * styles.thumbnail.margin)));

        if (this._flatListStyles.minHeight !== _height || this._flatListStyles.minWidth !== _width) {
            this._flatListStyles = {
                ...styles.flatListTileView,
                minHeight: _height,
                minWidth: _width
            };
        }

        if (this._contentContainerStyles.minHeight !== _height || this._contentContainerStyles.minWidth !== _width) {
            this._contentContainerStyles = {
                ...styles.contentContainer,
                minHeight: _height,
                minWidth: _width,
                paddingBottom: this.props.insets?.bottom || 0
            };
        }

        return (
            <TouchableWithoutFeedback onPress = { onClick }>
                <SafeAreaView style = { styles.flatListContainer }>
                    <FlatList
                        bounces = { false }
                        contentContainerStyle = { this._contentContainerStyles }
                        data = { participants }
                        horizontal = { false }
                        initialNumToRender = { initialRowsToRender }
                        key = { _columns }
                        keyExtractor = { this._keyExtractor }
                        numColumns = { _columns }
                        onViewableItemsChanged = { this._onViewableItemsChanged }
                        renderItem = { this._renderThumbnail }
                        showsHorizontalScrollIndicator = { false }
                        showsVerticalScrollIndicator = { false }
                        style = { this._flatListStyles }
                        viewabilityConfig = { this._viewabilityConfig }
                        windowSize = { 2 } />
                </SafeAreaView>
            </TouchableWithoutFeedback>
        );
    }

    /**
     * Returns all participants with the local participant at the end.
     *
     * @private
     * @returns {Participant[]}
     */
    _getSortedParticipants() {
        const { _localParticipant, _remoteParticipants, _disableSelfView } = this.props;

        if (!_localParticipant) {
            return EMPTY_ARRAY;
        }

        if (_disableSelfView) {
            return _remoteParticipants;
        }

        return [ _localParticipant?.id, ..._remoteParticipants ];
    }

    _renderThumbnail: Object => Object;

    /**
     * Creates React Element to display each participant in a thumbnail.
     *
     * @private
     * @returns {ReactElement}
     */
    _renderThumbnail({ item/* , index , separators */ }) {
        const { _thumbnailHeight } = this.props;

        return (
            <Thumbnail
                disableTint = { true }
                height = { _thumbnailHeight }
                key = { item }
                participantID = { item }
                renderDisplayName = { true }
                tileView = { true } />)
        ;
    }
}

/**
 * Maps (parts of) the redux state to the associated {@code TileView}'s props.
 *
 * @param {Object} state - The redux state.
 * @param {Object} ownProps - Component props.
 * @private
 * @returns {Props}
 */
function _mapStateToProps(state, ownProps) {
    const responsiveUi = state['features/base/responsive-ui'];
    const { remoteParticipants, tileViewDimensions } = state['features/filmstrip'];
    const disableSelfView = shouldHideSelfView(state);
    const { height } = tileViewDimensions.thumbnailSize;
    const { columns } = tileViewDimensions;

    return {
        _aspectRatio: responsiveUi.aspectRatio,
        _columns: columns,
        _disableSelfView: disableSelfView,
        _height: responsiveUi.clientHeight - (ownProps.insets?.top || 0),
        _insets: ownProps.insets,
        _localParticipant: getLocalParticipant(state),
        _participantCount: getParticipantCountWithFake(state),
        _remoteParticipants: remoteParticipants,
        _thumbnailHeight: height,
        _width: responsiveUi.clientWidth - (ownProps.insets?.right || 0) - (ownProps.insets?.left || 0)
    };
}

export default withSafeAreaInsets(connect(_mapStateToProps)(TileView));
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Owner: mark@famo.us
 * @license MPL 2.0
 * @copyright Famous Industries, Inc. 2015
 */

define(function(require, exports, module) {
    var Entity = require('./Entity');
    var EventHandler = require('./EventHandler');
    var Transform = require('./Transform');
    var DOMEventHandler = require('./DOMEventHandler');

    var usePrefix = !('transform' in document.documentElement.style);
    var devicePixelRatio = window.devicePixelRatio || 1;

    /**
     * A base class for viewable content and event
     *   targets inside a Famo.us application, containing a renderable document
     *   fragment. Like an HTML div, it can accept internal markup,
     *   properties, classes, and handle events.
     *
     * @class ElementOutput
     * @constructor
     *
     * @param {Node} element document parent of this container
     */
    function ElementOutput(element) {
        this._matrix = null;
        this._opacity = 1;
        this._origin = null;
        this._size = null;

        this._eventOutput = new EventHandler();
        this._eventOutput.bindThis(this);

        /** @ignore */
        this.eventForwarder = function eventForwarder(event) {
            this._eventOutput.emit(event.type, event);
        }.bind(this);

        this.id = Entity.register(this);
        this._element = null;
        this._sizeDirty = false;
        this._originDirty = false;
        this._transformDirty = false;

        this._invisible = false;
        if (element) this.attach(element);
    }

    /**
     * Bind a callback function to an event type handled by this object.
     *
     * @method "on"
     *
     * @param {string} type event type key (for example, 'click')
     * @param {function(string, Object)} fn handler callback
     * @return {EventHandler} this
     */
    ElementOutput.prototype.on = function on(type, fn) {
        if (this._element) DOMEventHandler.addEventListener(this.id, this._element, type, this.eventForwarder);
        this._eventOutput.on(type, fn);
    };

  ElementOutput.prototype.once = function on(type, fn) {
      if (this._element) DOMEventHandler.addEventListener(this.id, this._element, type, this.eventForwarder);
      this._eventOutput.once(type, fn);
    };

    /**
     * Unbind an event by type and handler.
     *   This undoes the work of "on"
     *
     * @method removeListener
     * @param {string} type event type key (for example, 'click')
     * @param {function(string, Object)} fn handler
     */
    ElementOutput.prototype.removeListener = function removeListener(type, fn) {
        this._eventOutput.removeListener(type, fn);
    };

    /**
     * Trigger an event, sending to all downstream handlers
     *   listening for provided 'type' key.
     *
     * @method emit
     *
     * @param {string} type event type key (for example, 'click')
     * @param {Object} [event] event data
     * @return {EventHandler} this
     */
    ElementOutput.prototype.emit = function emit(type, event) {
        if (event && !event.origin) event.origin = this;
        var handled = this._eventOutput.emit(type, event);
        if (handled && event && event.stopPropagation) event.stopPropagation();
        return handled;
    };

    /**
     * Add event handler object to set of downstream handlers.
     *
     * @method pipe
     *
     * @param {EventHandler} target event handler target object
     * @return {EventHandler} passed event handler
     */
    ElementOutput.prototype.pipe = function pipe(target) {
        return this._eventOutput.pipe(target);
    };

    /**
     * Remove handler object from set of downstream handlers.
     *   Undoes work of "pipe"
     *
     * @method unpipe
     *
     * @param {EventHandler} target target handler object
     * @return {EventHandler} provided target
     */
    ElementOutput.prototype.unpipe = function unpipe(target) {
        return this._eventOutput.unpipe(target);
    };

    /**
     * Return spec for this surface. Note that for a base surface, this is
     *    simply an id.
     *
     * @method render
     * @private
     * @return {Object} render spec for this surface (spec id)
     */
    ElementOutput.prototype.render = function render() {
        return this.id;
    };

    //  Attach Famous event handling to document events emanating from target
    //    document element.  This occurs just after attachment to the document.
    //    Calling this enables methods like #on and #pipe.
    function _addEventListeners(target) {
        for (var i in this._eventOutput.listeners) {
            DOMEventHandler.addEventListener(this.id, target, i, this.eventForwarder);
        }
    }

    //  Detach Famous event handling from document events emanating from target
    //  document element.  This occurs just before detach from the document.
    function _removeEventListeners(target) {
        for (var i in this._eventOutput.listeners) {
            DOMEventHandler.removeEventListener(this.id, i, this.eventForwarder)
        }
    }

    /**
     * Return a Matrix's webkit css representation to be used with the
     *    CSS3 -webkit-transform style.
     *    Example: -webkit-transform: matrix3d(1,0,0,0,0,1,0,0,0,0,1,0,716,243,0,1)
     *
     * @method _formatCSSTransform
     * @private
     * @param {FamousMatrix} m matrix
     * @return {string} matrix3d CSS style representation of the transform
     */
    function _formatCSSTransform(m) {
        m[12] = Math.round(m[12] * devicePixelRatio) / devicePixelRatio;
        m[13] = Math.round(m[13] * devicePixelRatio) / devicePixelRatio;

        var result = 'matrix3d(';
        for (var i = 0; i < 15; i++) {
            result += (m[i] < 0.000001 && m[i] > -0.000001) ? '0,' : m[i] + ',';
        }
        result += m[15] + ')';
        return result;
    }
    /**
     * Directly apply given FamousMatrix to the document element as the
     *   appropriate webkit CSS style.
     *
     * @method setMatrix
     *
     * @static
     * @private
     * @param {Element} element document element
     * @param {FamousMatrix} matrix
     */

    var _setMatrix;
    if (usePrefix) {
        _setMatrix = function(element, matrix) {
            element.style.webkitTransform = _formatCSSTransform(matrix);
        };
    }
    else {
        _setMatrix = function(element, matrix) {
            element.style.transform = _formatCSSTransform(matrix);
        };
    }

    // format origin as CSS percentage string
    function _formatCSSOrigin(origin) {
        return (100 * origin[0]) + '% ' + (100 * origin[1]) + '%';
    }

    // Directly apply given origin coordinates to the document element as the
    // appropriate webkit CSS style.
    var _setOrigin = usePrefix ? function(element, origin) {
        element.style.webkitTransformOrigin = _formatCSSOrigin(origin);
    } : function(element, origin) {
        element.style.transformOrigin = _formatCSSOrigin(origin);
    };

    // Shrink given document element until it is effectively invisible.
    var _setInvisible = usePrefix ? function(element) {
        element.style.webkitTransform = 'scale3d(0.0001,0.0001,0.0001)';
        element.style.opacity = 0;
    } : function(element) {
        element.style.transform = 'scale3d(0.0001,0.0001,0.0001)';
        element.style.opacity = 0;
    };

    function _xyNotEquals(a, b) {
        return (a && b) ? (a[0] !== b[0] || a[1] !== b[1]) : a !== b;
    }

    /**
     * Apply changes from this component to the corresponding document element.
     * This includes changes to classes, styles, size, content, opacity, origin,
     * and matrix transforms.
     *
     * @private
     * @method commit
     * @param {Context} context commit context
     */
    ElementOutput.prototype.commit = function commit(context) {
        var target = this._element;
        if (!target) return;

        var matrix = context.transform;
        var opacity = context.opacity;
        var origin = context.origin;
        var size = context.size;

        if (!matrix && this._matrix) {
            this._matrix = null;
            this._opacity = 0;
            _setInvisible(target);
            return;
        }

        if (_xyNotEquals(this._origin, origin)) this._originDirty = true;
        if (Transform.notEquals(this._matrix, matrix)) this._transformDirty = true;

        if (this._invisible) {
            this._invisible = false;
            this._element.style.display = '';
        }

        if (this._opacity !== opacity) {
            this._opacity = opacity;
            target.style.opacity = (opacity >= 1) ? '0.999999' : opacity;
        }

        if (this._transformDirty || this._originDirty || this._sizeDirty) {
            if (this._sizeDirty) this._sizeDirty = false;

            if (this._originDirty) {
                if (origin) {
                    if (!this._origin) this._origin = [0, 0];
                    this._origin[0] = origin[0];
                    this._origin[1] = origin[1];
                }
                else this._origin = null;
                _setOrigin(target, this._origin);
                this._originDirty = false;
            }

            if (!matrix) matrix = Transform.identity;
            this._matrix = matrix;
            var aaMatrix = this._size ? Transform.thenMove(matrix, [-this._size[0]*origin[0], -this._size[1]*origin[1], 0]) : matrix;
            _setMatrix(target, aaMatrix);
           /* Since a lot of browsers are buggy, they need the z-index to be set as well in order to successfully place things
            * on top of each other*/
            target.style.zIndex = aaMatrix[14];
            this._transformDirty = false;
        }
    };

    ElementOutput.prototype.cleanup = function cleanup() {
        if (this._element) {
            this._invisible = true;
            this._element.style.display = 'none';
        }
    };

    /**
     * Place the document element that this component manages into the document.
     *
     * @private
     * @method attach
     * @param {Node} target document parent of this container
     */
    ElementOutput.prototype.attach = function attach(target) {
        this._element = target;
      _addEventListeners.call(this, target);
    };

    /**
     * Remove any contained document content associated with this surface
     *   from the actual document.
     *
     * @private
     * @method detach
     */
    ElementOutput.prototype.detach = function detach() {
        var target = this._element;
        if (target) {
            _removeEventListeners.call(this, target);
            if (this._invisible) {
                this._invisible = false;
                this._element.style.display = '';
            }
        }
        this._element = null;
        return target;
    };

    module.exports = ElementOutput;
});
<commit_msg>Changed the call to removeEventListener to reflect change in DOMEventHandler<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Owner: mark@famo.us
 * @license MPL 2.0
 * @copyright Famous Industries, Inc. 2015
 */

define(function(require, exports, module) {
    var Entity = require('./Entity');
    var EventHandler = require('./EventHandler');
    var Transform = require('./Transform');
    var DOMEventHandler = require('./DOMEventHandler');

    var usePrefix = !('transform' in document.documentElement.style);
    var devicePixelRatio = window.devicePixelRatio || 1;

    /**
     * A base class for viewable content and event
     *   targets inside a Famo.us application, containing a renderable document
     *   fragment. Like an HTML div, it can accept internal markup,
     *   properties, classes, and handle events.
     *
     * @class ElementOutput
     * @constructor
     *
     * @param {Node} element document parent of this container
     */
    function ElementOutput(element) {
        this._matrix = null;
        this._opacity = 1;
        this._origin = null;
        this._size = null;

        this._eventOutput = new EventHandler();
        this._eventOutput.bindThis(this);

        /** @ignore */
        this.eventForwarder = function eventForwarder(event) {
            this._eventOutput.emit(event.type, event);
        }.bind(this);

        this.id = Entity.register(this);
        this._element = null;
        this._sizeDirty = false;
        this._originDirty = false;
        this._transformDirty = false;

        this._invisible = false;
        if (element) this.attach(element);
    }

    /**
     * Bind a callback function to an event type handled by this object.
     *
     * @method "on"
     *
     * @param {string} type event type key (for example, 'click')
     * @param {function(string, Object)} fn handler callback
     * @return {EventHandler} this
     */
    ElementOutput.prototype.on = function on(type, fn) {
        if (this._element) DOMEventHandler.addEventListener(this.id, this._element, type, this.eventForwarder);
        this._eventOutput.on(type, fn);
    };

  ElementOutput.prototype.once = function on(type, fn) {
      if (this._element) DOMEventHandler.addEventListener(this.id, this._element, type, this.eventForwarder);
      this._eventOutput.once(type, fn);
    };

    /**
     * Unbind an event by type and handler.
     *   This undoes the work of "on"
     *
     * @method removeListener
     * @param {string} type event type key (for example, 'click')
     * @param {function(string, Object)} fn handler
     */
    ElementOutput.prototype.removeListener = function removeListener(type, fn) {
        this._eventOutput.removeListener(type, fn);
    };

    /**
     * Trigger an event, sending to all downstream handlers
     *   listening for provided 'type' key.
     *
     * @method emit
     *
     * @param {string} type event type key (for example, 'click')
     * @param {Object} [event] event data
     * @return {EventHandler} this
     */
    ElementOutput.prototype.emit = function emit(type, event) {
        if (event && !event.origin) event.origin = this;
        var handled = this._eventOutput.emit(type, event);
        if (handled && event && event.stopPropagation) event.stopPropagation();
        return handled;
    };

    /**
     * Add event handler object to set of downstream handlers.
     *
     * @method pipe
     *
     * @param {EventHandler} target event handler target object
     * @return {EventHandler} passed event handler
     */
    ElementOutput.prototype.pipe = function pipe(target) {
        return this._eventOutput.pipe(target);
    };

    /**
     * Remove handler object from set of downstream handlers.
     *   Undoes work of "pipe"
     *
     * @method unpipe
     *
     * @param {EventHandler} target target handler object
     * @return {EventHandler} provided target
     */
    ElementOutput.prototype.unpipe = function unpipe(target) {
        return this._eventOutput.unpipe(target);
    };

    /**
     * Return spec for this surface. Note that for a base surface, this is
     *    simply an id.
     *
     * @method render
     * @private
     * @return {Object} render spec for this surface (spec id)
     */
    ElementOutput.prototype.render = function render() {
        return this.id;
    };

    //  Attach Famous event handling to document events emanating from target
    //    document element.  This occurs just after attachment to the document.
    //    Calling this enables methods like #on and #pipe.
    function _addEventListeners(target) {
        for (var i in this._eventOutput.listeners) {
            DOMEventHandler.addEventListener(this.id, target, i, this.eventForwarder);
        }
    }

    //  Detach Famous event handling from document events emanating from target
    //  document element.  This occurs just before detach from the document.
    function _removeEventListeners(target) {
        for (var i in this._eventOutput.listeners) {
            DOMEventHandler.removeEventListener(target, this.id, i, this.eventForwarder)
        }
    }

    /**
     * Return a Matrix's webkit css representation to be used with the
     *    CSS3 -webkit-transform style.
     *    Example: -webkit-transform: matrix3d(1,0,0,0,0,1,0,0,0,0,1,0,716,243,0,1)
     *
     * @method _formatCSSTransform
     * @private
     * @param {FamousMatrix} m matrix
     * @return {string} matrix3d CSS style representation of the transform
     */
    function _formatCSSTransform(m) {
        m[12] = Math.round(m[12] * devicePixelRatio) / devicePixelRatio;
        m[13] = Math.round(m[13] * devicePixelRatio) / devicePixelRatio;

        var result = 'matrix3d(';
        for (var i = 0; i < 15; i++) {
            result += (m[i] < 0.000001 && m[i] > -0.000001) ? '0,' : m[i] + ',';
        }
        result += m[15] + ')';
        return result;
    }
    /**
     * Directly apply given FamousMatrix to the document element as the
     *   appropriate webkit CSS style.
     *
     * @method setMatrix
     *
     * @static
     * @private
     * @param {Element} element document element
     * @param {FamousMatrix} matrix
     */

    var _setMatrix;
    if (usePrefix) {
        _setMatrix = function(element, matrix) {
            element.style.webkitTransform = _formatCSSTransform(matrix);
        };
    }
    else {
        _setMatrix = function(element, matrix) {
            element.style.transform = _formatCSSTransform(matrix);
        };
    }

    // format origin as CSS percentage string
    function _formatCSSOrigin(origin) {
        return (100 * origin[0]) + '% ' + (100 * origin[1]) + '%';
    }

    // Directly apply given origin coordinates to the document element as the
    // appropriate webkit CSS style.
    var _setOrigin = usePrefix ? function(element, origin) {
        element.style.webkitTransformOrigin = _formatCSSOrigin(origin);
    } : function(element, origin) {
        element.style.transformOrigin = _formatCSSOrigin(origin);
    };

    // Shrink given document element until it is effectively invisible.
    var _setInvisible = usePrefix ? function(element) {
        element.style.webkitTransform = 'scale3d(0.0001,0.0001,0.0001)';
        element.style.opacity = 0;
    } : function(element) {
        element.style.transform = 'scale3d(0.0001,0.0001,0.0001)';
        element.style.opacity = 0;
    };

    function _xyNotEquals(a, b) {
        return (a && b) ? (a[0] !== b[0] || a[1] !== b[1]) : a !== b;
    }

    /**
     * Apply changes from this component to the corresponding document element.
     * This includes changes to classes, styles, size, content, opacity, origin,
     * and matrix transforms.
     *
     * @private
     * @method commit
     * @param {Context} context commit context
     */
    ElementOutput.prototype.commit = function commit(context) {
        var target = this._element;
        if (!target) return;

        var matrix = context.transform;
        var opacity = context.opacity;
        var origin = context.origin;
        var size = context.size;

        if (!matrix && this._matrix) {
            this._matrix = null;
            this._opacity = 0;
            _setInvisible(target);
            return;
        }

        if (_xyNotEquals(this._origin, origin)) this._originDirty = true;
        if (Transform.notEquals(this._matrix, matrix)) this._transformDirty = true;

        if (this._invisible) {
            this._invisible = false;
            this._element.style.display = '';
        }

        if (this._opacity !== opacity) {
            this._opacity = opacity;
            target.style.opacity = (opacity >= 1) ? '0.999999' : opacity;
        }

        if (this._transformDirty || this._originDirty || this._sizeDirty) {
            if (this._sizeDirty) this._sizeDirty = false;

            if (this._originDirty) {
                if (origin) {
                    if (!this._origin) this._origin = [0, 0];
                    this._origin[0] = origin[0];
                    this._origin[1] = origin[1];
                }
                else this._origin = null;
                _setOrigin(target, this._origin);
                this._originDirty = false;
            }

            if (!matrix) matrix = Transform.identity;
            this._matrix = matrix;
            var aaMatrix = this._size ? Transform.thenMove(matrix, [-this._size[0]*origin[0], -this._size[1]*origin[1], 0]) : matrix;
            _setMatrix(target, aaMatrix);
           /* Since a lot of browsers are buggy, they need the z-index to be set as well in order to successfully place things
            * on top of each other*/
            target.style.zIndex = aaMatrix[14];
            this._transformDirty = false;
        }
    };

    ElementOutput.prototype.cleanup = function cleanup() {
        if (this._element) {
            this._invisible = true;
            this._element.style.display = 'none';
        }
    };

    /**
     * Place the document element that this component manages into the document.
     *
     * @private
     * @method attach
     * @param {Node} target document parent of this container
     */
    ElementOutput.prototype.attach = function attach(target) {
        this._element = target;
      _addEventListeners.call(this, target);
    };

    /**
     * Remove any contained document content associated with this surface
     *   from the actual document.
     *
     * @private
     * @method detach
     */
    ElementOutput.prototype.detach = function detach() {
        var target = this._element;
        if (target) {
            _removeEventListeners.call(this, target);
            if (this._invisible) {
                this._invisible = false;
                this._element.style.display = '';
            }
        }
        this._element = null;
        return target;
    };

    module.exports = ElementOutput;
});
<|endoftext|>


<commit_before>/**
 * Server Modul
 * @module ActsServer
 * @author Markus Gilg
 * @requires node_http
 * @requires node_https
 * @requires npm_connect
 * @requires npm_socket.io
 * @requires FileSystemHelper
 * @requires RequestHelper
 */
'use strict';

const CONNECT    = require('connect');
const IO         = require('socket.io');
const COMPRESS   = require("compression");
const HELM       = require("helmet");
const WEBSOCKETS = require('./../extensions/websocket.ext');
const CORS       = require("./../extensions/cors.ext");
const BODYPARSER = require("./../extensions/parser.ext");
const REDIRECT   = require("./../extensions/redirect.ext");
const STATICFILE = require("./../extensions/staticfiles.ext");
const DYNAMICAPI = require("./../extensions/dynamicapi.ext");
const ACCESS     = require("./../extensions/accesshandler");
const FILE       = require('./../common/filesystem.helper');
const REQU       = require('./../common/request.helper');
const APP        = CONNECT();

let _cfg        = null;
let _core       = null;
let _logger     = null;
let _socketio   = null;
let _ssloptions = null;
let _plugins    = null;
let _http       = require('http');

/**
 * try to load all Plugins
 * @function loadPlugins
 * @private
 * @param {Array} plugins Array of Plugins to load
 * @param {Object} server Server Instance
 * @param {Object} cfg Server Configuration
 */
const loadPlugins = function (plugins, server, cfg) {
    if (typeof plugins !== typeof [] || plugins.length < 1) {
        _logger.debug("no plugins loaded");
        return;
    }
    for (let i = 0, length = plugins.length; i < length; i++) {
        const plugin = plugins[i];
        try {
            require(plugin)(server, cfg);
            _logger.debug("plugin " + plugin + " successfully loaded");
        } catch (err) {
            _logger.debug("error while load plugin " + plugin);
            _logger.error(err);
        }
    }
};

/**
 * read the certificate Information when using ssl
 * @function handleSsl
 * @private
 */
const handleSsl = function () {
    if (_cfg.server.ssl.usessl) {
        _http = require('https');
        const cas = [];
        for (var i = 0, len = _cfg.server.ssl.certificationauthority.length; i < len; i++) {
            const obj = _cfg.server.ssl.certificationauthority[i];
            cas.push(FILE.getFileContent(obj));
        }
        _ssloptions = {
            ca: cas,
            cert: FILE.getFileContent(_cfg.server.ssl.certificate),
            key: FILE.getFileContent(_cfg.server.ssl.privatekey)
        };
    }
};

/**
 * load the Default Modules
 * @method initStandardModules
 * @private
 */
const initStandardModules = function () {
    // helmet security
    APP.use(HELM());

    // CORS Module first
    let tmpCors = new CORS(_cfg, _logger);
    APP.use(tmpCors.checkRequest);

    // load redirect module
    let tmpRedirect = new REDIRECT(_cfg, _logger);
    APP.use(tmpRedirect.handle);

    // parse request bodys
    let tmpParser = new BODYPARSER(_cfg, _logger);
    APP.use(tmpParser.parse);

    // gzip compression
    if (_cfg.server.compress) {
        APP.use(handleCompress);
    }

    // load plugins
    loadPlugins(_plugins, APP, _cfg);

    // use folder for static files from config file
    let tmpStaticFile = new STATICFILE(_cfg, _logger);
    APP.use(tmpStaticFile.request);

    // use dynamic api
    let tmpDynamicApi = new DYNAMICAPI(_cfg, _logger);
    APP.use(tmpDynamicApi.request);
};

/**
 * log server Infos of current running Server
 * @event serverRunning
 * @private
 */
const serverRunning = function () {
    let folder = 'Server Folder: ' + _cfg.serverdir;
    let server = 'Server running ' + (_cfg.server.ssl.usessl ? 'https' : 'http') + '://' + _cfg.server.address + ':' + _cfg.server.port;
    console.info(folder);
    _logger.info(folder);
    console.info(server);
    _logger.info(server);
    if (typeof this === 'function') {
        this();
    }
};

/**
 * handle Client Connect
 * @event handleClientConnect
 * @private
 * @param {Object} socket Node Socket Object
 */
const handleClientConnect = function (socket) {
    _logger.debug('socket connect ' + socket.remoteAddress);
    ACCESS.onClientConnect(socket);
};

/**
 * handle CLient Errors
 * @event handleClientError
 * @private
 * @param {Object} err Error Object
 * @param {Object} socket Node Socket Object
 */
const handleClientError = function (err, socket) {
    _logger.debug('client create a error');
    _logger.warning(err);
    socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
};

/**
 * handle Server was closed
 * @event handleServerClose
 * @private
 */
const handleServerClose = function () {
  _logger.debug('server is shutting down');
};

/**
 * compress in gzip when activate
 * @event handleCompress
 * @private
 */
const handleCompress = function (req, res, next) {
    if (req.headers['accept-encoding'] === 'compress, gzip') {
        COMPRESS(_cfg)(req, res, next);
    } else {
        next();
    }
};

/**
 * boot the Server
 * @function startServer
 * @param {Function} cb callback when Server is started
 */
const startServer = function (cb) {
    _logger.debug('try to start server');
    try {
        //start Server
        let server;
        if (_cfg.server.ssl.usessl) {
            _logger.debug('ssl was enabled', _ssloptions);
            server = _http.createServer(_ssloptions, APP);
        } else {
            server = _http.createServer(APP);
        }

        // create sockets
        if (_cfg.server.websockets.usewebsockets && _socketio === null) {
            _logger.debug('websockets was enabled');
            _socketio = IO(server);
            let tmpWebsockets = new WEBSOCKETS(_cfg, _logger);
            _socketio.on('connection', tmpWebsockets.setEventsOnSocket);
        }

        // handle Server Events
        server.on('clientError', handleClientError);
        server.on('close', handleServerClose);
        server.on('connection', handleClientConnect);

        APP.use(REQU.notFound);

        server.listen(_cfg.server.port, _cfg.server.address, serverRunning.bind(cb));
    } catch (ex) {
        _logger.error(ex);
    }
};

class ActsServer {
    constructor (cfg, plugins, logger) {
        _logger = logger;
        _cfg = cfg;

        FILE.createDirectoryRecursive(FILE.joinPath(_cfg.serverdir, _cfg.server.webroot));
        FILE.createDirectoryRecursive(FILE.joinPath(_cfg.serverdir, _cfg.server.api.routepath));
        FILE.createDirectoryRecursive(FILE.joinPath(_cfg.serverdir, _cfg.server.websockets.socketpath));
        handleSsl();
        initStandardModules();
    }

    /**
     * Start the Server
     * @function start
     * @param {Function} cb callback when Server is started
     */
    start (cb) {
        return startServer(cb);
    }
}
module.exports = ActsServer;<commit_msg>using acceshandler<commit_after>/**
 * Server Modul
 * @module ActsServer
 * @author Markus Gilg
 * @requires node_http
 * @requires node_https
 * @requires npm_connect
 * @requires npm_socket.io
 * @requires FileSystemHelper
 * @requires RequestHelper
 */
'use strict';

const CONNECT    = require('connect');
const IO         = require('socket.io');
const COMPRESS   = require("compression");
const HELM       = require("helmet");
const WEBSOCKETS = require('./../extensions/websocket.ext');
const CORS       = require("./../extensions/cors.ext");
const BODYPARSER = require("./../extensions/parser.ext");
const REDIRECT   = require("./../extensions/redirect.ext");
const STATICFILE = require("./../extensions/staticfiles.ext");
const DYNAMICAPI = require("./../extensions/dynamicapi.ext");
const ACCESS     = require("./../extensions/accesshandler.ext");
const FILE       = require('./../common/filesystem.helper');
const REQU       = require('./../common/request.helper');
const APP        = CONNECT();

let _cfg        = null;
let _core       = null;
let _logger     = null;
let _socketio   = null;
let _ssloptions = null;
let _plugins    = null;
let _http       = require('http');
let _access     = null;

/**
 * try to load all Plugins
 * @function loadPlugins
 * @private
 * @param {Array} plugins Array of Plugins to load
 * @param {Object} server Server Instance
 * @param {Object} cfg Server Configuration
 */
const loadPlugins = function (plugins, server, cfg) {
    if (typeof plugins !== typeof [] || plugins.length < 1) {
        _logger.debug("no plugins loaded");
        return;
    }
    for (let i = 0, length = plugins.length; i < length; i++) {
        const plugin = plugins[i];
        try {
            require(plugin)(server, cfg);
            _logger.debug("plugin " + plugin + " successfully loaded");
        } catch (err) {
            _logger.debug("error while load plugin " + plugin);
            _logger.error(err);
        }
    }
};

/**
 * read the certificate Information when using ssl
 * @function handleSsl
 * @private
 */
const handleSsl = function () {
    if (_cfg.server.ssl.usessl) {
        _http = require('https');
        const cas = [];
        for (var i = 0, len = _cfg.server.ssl.certificationauthority.length; i < len; i++) {
            const obj = _cfg.server.ssl.certificationauthority[i];
            cas.push(FILE.getFileContent(obj));
        }
        _ssloptions = {
            ca: cas,
            cert: FILE.getFileContent(_cfg.server.ssl.certificate),
            key: FILE.getFileContent(_cfg.server.ssl.privatekey)
        };
    }
};

/**
 * load the Default Modules
 * @method initStandardModules
 * @private
 */
const initStandardModules = function () {
    // helmet security
    APP.use(HELM());

    // CORS Module first
    let tmpCors = new CORS(_cfg, _logger);
    APP.use(tmpCors.checkRequest);

    // load redirect module
    let tmpRedirect = new REDIRECT(_cfg, _logger);
    APP.use(tmpRedirect.handle);

    // parse request bodys
    let tmpParser = new BODYPARSER(_cfg, _logger);
    APP.use(tmpParser.parse);

    // gzip compression
    if (_cfg.server.compress) {
        APP.use(handleCompress);
    }

    // load plugins
    loadPlugins(_plugins, APP, _cfg);

    // use folder for static files from config file
    let tmpStaticFile = new STATICFILE(_cfg, _logger);
    APP.use(tmpStaticFile.request);

    // use dynamic api
    let tmpDynamicApi = new DYNAMICAPI(_cfg, _logger);
    APP.use(tmpDynamicApi.request);
};

/**
 * log server Infos of current running Server
 * @event serverRunning
 * @private
 */
const serverRunning = function () {
    let folder = 'Server Folder: ' + _cfg.serverdir;
    let server = 'Server running ' + (_cfg.server.ssl.usessl ? 'https' : 'http') + '://' + _cfg.server.address + ':' + _cfg.server.port;
    console.info(folder);
    _logger.info(folder);
    console.info(server);
    _logger.info(server);
    if (typeof this === 'function') {
        this();
    }
};

/**
 * handle Client Connect
 * @event handleClientConnect
 * @private
 * @param {Object} socket Node Socket Object
 */
const handleClientConnect = function (socket) {
    _logger.debug('socket connect ' + socket.remoteAddress);
    _access.onClientConnect(socket);
};

/**
 * handle CLient Errors
 * @event handleClientError
 * @private
 * @param {Object} err Error Object
 * @param {Object} socket Node Socket Object
 */
const handleClientError = function (err, socket) {
    _logger.debug('client create a error');
    _logger.warning(err);
    socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
};

/**
 * handle Server was closed
 * @event handleServerClose
 * @private
 */
const handleServerClose = function () {
  _logger.debug('server is shutting down');
};

/**
 * compress in gzip when activate
 * @event handleCompress
 * @private
 */
const handleCompress = function (req, res, next) {
    if (req.headers['accept-encoding'] === 'compress, gzip') {
        COMPRESS(_cfg)(req, res, next);
    } else {
        next();
    }
};

/**
 * boot the Server
 * @function startServer
 * @param {Function} cb callback when Server is started
 */
const startServer = function (cb) {
    _logger.debug('try to start server');
    try {
        //start Server
        let server;
        if (_cfg.server.ssl.usessl) {
            _logger.debug('ssl was enabled', _ssloptions);
            server = _http.createServer(_ssloptions, APP);
        } else {
            server = _http.createServer(APP);
        }

        // create sockets
        if (_cfg.server.websockets.usewebsockets && _socketio === null) {
            _logger.debug('websockets was enabled');
            _socketio = IO(server);
            let tmpWebsockets = new WEBSOCKETS(_cfg, _logger);
            _socketio.on('connection', tmpWebsockets.setEventsOnSocket);
        }

        // handle Server Events
        server.on('clientError', handleClientError);
        server.on('close', handleServerClose);
        server.on('connection', handleClientConnect);

        APP.use(REQU.notFound);

        server.listen(_cfg.server.port, _cfg.server.address, serverRunning.bind(cb));
    } catch (ex) {
        _logger.error(ex);
    }
};

class ActsServer {
    constructor (cfg, plugins, logger) {
        _logger = logger;
        _cfg = cfg;
        _access = new ACCESS(_cfg, _logger);

        FILE.createDirectoryRecursive(FILE.joinPath(_cfg.serverdir, _cfg.server.webroot));
        FILE.createDirectoryRecursive(FILE.joinPath(_cfg.serverdir, _cfg.server.api.routepath));
        FILE.createDirectoryRecursive(FILE.joinPath(_cfg.serverdir, _cfg.server.websockets.socketpath));
        handleSsl();
        initStandardModules();
    }

    /**
     * Start the Server
     * @function start
     * @param {Function} cb callback when Server is started
     */
    start (cb) {
        return startServer(cb);
    }
}
module.exports = ActsServer;<|endoftext|>


<commit_before>'use strict';
var React = require('react');
var PureRenderMixin = require('react/addons').addons.PureRenderMixin;
var Rx = require('rx');
var digestDefinitionFnOutput = require('./util').digestDefinitionFnOutput;

function makeInteractions(rootElem$) {
  return {
    get: function get(selector, eventName, isSingle) {
      if (typeof selector !== 'string') {
        throw new Error('interactions.get() expects first argument to be a ' +
          'string as a CSS selector');
      }
      if (typeof eventName !== 'string') {
        throw new Error('interactions.get() expects second argument to be a ' +
          'string representing the event type to listen for.');
      }

      return rootElem$
        .flatMapLatest(function flatMapDOMUserEventStream(rootElem) {
          if (!rootElem) {
            return Rx.Observable.empty();
          }
          var klass = selector.replace('.', '');
          var klassRegex = new RegExp('\\b' + klass + '\\b');
          if (klassRegex.test(rootElem.className)) {
            return Rx.Observable.fromEvent(rootElem, eventName);
          }
          if (isSingle) {
            var targetElement = rootElem.querySelector(selector);
            if (targetElement) {
              return Rx.Observable.fromEvent(targetElement, eventName);
            }
          } else {
            var targetElements = rootElem.querySelectorAll(selector);
            if (targetElements && targetElements.length > 0) {
              return Rx.Observable.fromEvent(targetElements, eventName);
            }
          }
          return Rx.Observable.empty();
        });
    }
  };
}

function makeDispatchFunction(elementGetter, eventName) {
  return function dispatchCustomEvent(evData) {
    //console.log('%cdispatchCustomEvent ' + eventName,
    //  'background-color: #CCCCFF; color: black');
    var event;
    try {
      event = new Event(eventName);
    } catch (err) {
      event = document.createEvent('Event');
      event.initEvent(eventName, true, true);
    }
    event.data = evData;
    var element = elementGetter();
    if (element) {
      element.dispatchEvent(event);
    } else {
      console.warn(
        eventName +
        ' event dispatched after the element has been destroyed'
      );
    }
  };
}

function composingEventObservables(events, elementGetter) {
  var eventNames = Object.keys(events);
  var eventObservables = [];
  for (var i = 0; i < eventNames.length; i++) {
    var eventName = eventNames[i];
    if (/\$$/.test(eventName) && eventName !== 'vtree$') {
      var eventObs = events[eventName];
      eventObservables.push(
        eventObs.doOnNext(
          makeDispatchFunction(elementGetter, eventName.slice(0, -1))
        )
      );
    }
  }
  return eventObservables;
}

function fixClassName(className) {
  return function innerFixClassName(vtree) {
    if (!className) {
      return vtree;
    }
    var props = vtree.props;
    if (typeof props.className !== 'string' ||
      props.className === '') {
      props.className = className;
    } else {
      props.className = props.className + ' ' + className;
    }
    return vtree;
  };
}

function createGetPropFn(propsSubject$) {
  return function getProp(propName, comparer) {
    var prop$ = propsSubject$.map(function mapProp(p) {
      return p[propName];
    });
    if (comparer) {
      return prop$.distinctUntilChanged(Rx.helpers.identity, comparer);
    }
    return prop$.distinctUntilChanged();
  };
}

function createReactClass(
  displayName,
  definitionFn,
  rootTagName,
  observer,
  eventObserver) {
  if (typeof displayName !== 'string') {
    throw new Error('Invalid displayName');
  }
  if (typeof definitionFn !== 'function') {
    throw new Error('Invalid definitionFn');
  }
  return React.createClass({
    displayName: displayName,
    // https://facebook.github.io/react/docs/pure-render-mixin.html
    // This works perfectly because we're only setting vtree for the state.
    mixins: [PureRenderMixin],
    getInitialState: function getInitialState() {
      return {vtree: null};
    },
    componentWillMount: function componentWillMount() {
      var self = this;
      this.disposable = new Rx.CompositeDisposable();
      var propsSubject$ = new Rx.BehaviorSubject(this.props);
      propsSubject$.get = createGetPropFn(propsSubject$);
      this.propsSubject$ = propsSubject$;
      this.rootElemSubject$ = new Rx.ReplaySubject(1);
      var interactions = makeInteractions(this.rootElemSubject$);
      var cycleComponent = digestDefinitionFnOutput(
        definitionFn(interactions, this.propsSubject$)
      );
      this.cycleComponent = cycleComponent;
      this.onMount = cycleComponent.onMount;
      var vtree$ = cycleComponent.vtree$;
      if (this.props.className) {
        vtree$ = vtree$.doOnNext(fixClassName(this.props.className));
      }
      var vtreeDoSet$ = vtree$.doOnNext(function onNextVTree(vtree) {
        self.setState({vtree: vtree});
      });
      var subscription = observer ?
        vtreeDoSet$.subscribe(observer) : vtreeDoSet$.subscribe();
      this.disposable.add(this.propsSubject$);
      this.disposable.add(this.rootElemSubject$);
      this.disposable.add(subscription);
    },
    componentWillUnmount: function componentWillUnmount() {
      this.propsSubject$.onCompleted();
      this.rootElemSubject$.onCompleted();
      this.disposable.dispose();
    },
    componentDidMount: function componentDidMount() {
      // Subscribe events
      var self = this;
      var eventObservables = composingEventObservables(
        this.cycleComponent.customEvents,
        function getCurrentElement() {
          return React.findDOMNode(self);
        }
      );
      if (eventObservables.length > 0) {
        var eventSubscription;
        if (eventObserver) {
          eventSubscription =
            Rx.Observable.merge(eventObservables).subscribe(eventObserver);
        } else {
          eventSubscription =
            Rx.Observable.merge(eventObservables).subscribe();
        }
        this.disposable.add(eventSubscription);
      }
      // Notify new rootElem
      var node = React.findDOMNode(this);
      this.rootElemSubject$.onNext(node);
      if (this.onMount) {
        this.onMount(node);
      }
    },
    componentDidUpdate: function componentDidUpdate() {
      var node = React.findDOMNode(this);
      this.rootElemSubject$.onNext(node);
    },
    componentWillReceiveProps: function componentWillReceiveProps(nextProps) {
      this.propsSubject$.onNext(nextProps);
    },
    render: function render() {
      if (this.state) {
        return this.state.vtree;
      }
      return React.createElement(rootTagName ? rootTagName : 'div');
    }
  });
}

module.exports = createReactClass;
<commit_msg>Check the availability of state.vtree<commit_after>'use strict';
var React = require('react');
var PureRenderMixin = require('react/addons').addons.PureRenderMixin;
var Rx = require('rx');
var digestDefinitionFnOutput = require('./util').digestDefinitionFnOutput;

function makeInteractions(rootElem$) {
  return {
    get: function get(selector, eventName, isSingle) {
      if (typeof selector !== 'string') {
        throw new Error('interactions.get() expects first argument to be a ' +
          'string as a CSS selector');
      }
      if (typeof eventName !== 'string') {
        throw new Error('interactions.get() expects second argument to be a ' +
          'string representing the event type to listen for.');
      }

      return rootElem$
        .flatMapLatest(function flatMapDOMUserEventStream(rootElem) {
          if (!rootElem) {
            return Rx.Observable.empty();
          }
          var klass = selector.replace('.', '');
          var klassRegex = new RegExp('\\b' + klass + '\\b');
          if (klassRegex.test(rootElem.className)) {
            return Rx.Observable.fromEvent(rootElem, eventName);
          }
          if (isSingle) {
            var targetElement = rootElem.querySelector(selector);
            if (targetElement) {
              return Rx.Observable.fromEvent(targetElement, eventName);
            }
          } else {
            var targetElements = rootElem.querySelectorAll(selector);
            if (targetElements && targetElements.length > 0) {
              return Rx.Observable.fromEvent(targetElements, eventName);
            }
          }
          return Rx.Observable.empty();
        });
    }
  };
}

function makeDispatchFunction(elementGetter, eventName) {
  return function dispatchCustomEvent(evData) {
    //console.log('%cdispatchCustomEvent ' + eventName,
    //  'background-color: #CCCCFF; color: black');
    var event;
    try {
      event = new Event(eventName);
    } catch (err) {
      event = document.createEvent('Event');
      event.initEvent(eventName, true, true);
    }
    event.data = evData;
    var element = elementGetter();
    if (element) {
      element.dispatchEvent(event);
    } else {
      console.warn(
        eventName +
        ' event dispatched after the element has been destroyed'
      );
    }
  };
}

function composingEventObservables(events, elementGetter) {
  var eventNames = Object.keys(events);
  var eventObservables = [];
  for (var i = 0; i < eventNames.length; i++) {
    var eventName = eventNames[i];
    if (/\$$/.test(eventName) && eventName !== 'vtree$') {
      var eventObs = events[eventName];
      eventObservables.push(
        eventObs.doOnNext(
          makeDispatchFunction(elementGetter, eventName.slice(0, -1))
        )
      );
    }
  }
  return eventObservables;
}

function fixClassName(className) {
  return function innerFixClassName(vtree) {
    if (!className) {
      return vtree;
    }
    var props = vtree.props;
    if (typeof props.className !== 'string' ||
      props.className === '') {
      props.className = className;
    } else {
      props.className = props.className + ' ' + className;
    }
    return vtree;
  };
}

function createGetPropFn(propsSubject$) {
  return function getProp(propName, comparer) {
    var prop$ = propsSubject$.map(function mapProp(p) {
      return p[propName];
    });
    if (comparer) {
      return prop$.distinctUntilChanged(Rx.helpers.identity, comparer);
    }
    return prop$.distinctUntilChanged();
  };
}

function createReactClass(
  displayName,
  definitionFn,
  rootTagName,
  observer,
  eventObserver) {
  if (typeof displayName !== 'string') {
    throw new Error('Invalid displayName');
  }
  if (typeof definitionFn !== 'function') {
    throw new Error('Invalid definitionFn');
  }
  return React.createClass({
    displayName: displayName,
    // https://facebook.github.io/react/docs/pure-render-mixin.html
    // This works perfectly because we're only setting vtree for the state.
    mixins: [PureRenderMixin],
    getInitialState: function getInitialState() {
      return {vtree: null};
    },
    componentWillMount: function componentWillMount() {
      var self = this;
      this.disposable = new Rx.CompositeDisposable();
      var propsSubject$ = new Rx.BehaviorSubject(this.props);
      propsSubject$.get = createGetPropFn(propsSubject$);
      this.propsSubject$ = propsSubject$;
      this.rootElemSubject$ = new Rx.ReplaySubject(1);
      var interactions = makeInteractions(this.rootElemSubject$);
      var cycleComponent = digestDefinitionFnOutput(
        definitionFn(interactions, this.propsSubject$)
      );
      this.cycleComponent = cycleComponent;
      this.onMount = cycleComponent.onMount;
      var vtree$ = cycleComponent.vtree$;
      if (this.props.className) {
        vtree$ = vtree$.doOnNext(fixClassName(this.props.className));
      }
      var vtreeDoSet$ = vtree$.doOnNext(function onNextVTree(vtree) {
        self.setState({vtree: vtree});
      });
      var subscription = observer ?
        vtreeDoSet$.subscribe(observer) : vtreeDoSet$.subscribe();
      this.disposable.add(this.propsSubject$);
      this.disposable.add(this.rootElemSubject$);
      this.disposable.add(subscription);
    },
    componentWillUnmount: function componentWillUnmount() {
      this.propsSubject$.onCompleted();
      this.rootElemSubject$.onCompleted();
      this.disposable.dispose();
    },
    componentDidMount: function componentDidMount() {
      // Subscribe events
      var self = this;
      var eventObservables = composingEventObservables(
        this.cycleComponent.customEvents,
        function getCurrentElement() {
          return React.findDOMNode(self);
        }
      );
      if (eventObservables.length > 0) {
        var eventSubscription;
        if (eventObserver) {
          eventSubscription =
            Rx.Observable.merge(eventObservables).subscribe(eventObserver);
        } else {
          eventSubscription =
            Rx.Observable.merge(eventObservables).subscribe();
        }
        this.disposable.add(eventSubscription);
      }
      // Notify new rootElem
      var node = React.findDOMNode(this);
      this.rootElemSubject$.onNext(node);
      if (this.onMount) {
        this.onMount(node);
      }
    },
    componentDidUpdate: function componentDidUpdate() {
      var node = React.findDOMNode(this);
      this.rootElemSubject$.onNext(node);
    },
    componentWillReceiveProps: function componentWillReceiveProps(nextProps) {
      this.propsSubject$.onNext(nextProps);
    },
    render: function render() {
      if (this.state && this.state.vtree) {
        return this.state.vtree;
      }
      return React.createElement(rootTagName ? rootTagName : 'div');
    }
  });
}

module.exports = createReactClass;
<|endoftext|>