<commit_before>import enclose from "./enclose";

function place(a, b, c) {
  var ax = a.x,
      ay = a.y,
      da = b.r + c.r,
      db = a.r + c.r,
      dx = b.x - ax,
      dy = b.y - ay,
      dc = dx * dx + dy * dy;
  if (dc) {
    var x = 0.5 + ((db *= db) - (da *= da)) / (2 * dc),
        y = Math.sqrt(Math.max(0, 2 * da * (db + dc) - (db -= dc) * db - da * da)) / (2 * dc);
    c.x = ax + x * dx + y * dy;
    c.y = ay + x * dy - y * dx;
  } else {
    c.x = ax + db;
    c.y = ay;
  }
}

function intersects(a, b) {
  var dx = b.x - a.x,
      dy = b.y - a.y,
      dr = a.r + b.r;
  return dr * dr - 1e-6 > dx * dx + dy * dy;
}

function distance1(a, b) {
  var l = 0, dx, dy, x = a._.x, y = a._.y;
  while (a !== b) {
    a = a.next;
    dx = x - (x = a._.x);
    dy = y - (y = a._.y);
    l += dx * dx + dy * dy;
  }
  return l;
}

function distance2(circle, x, y) {
  var dx = circle.x - x,
      dy = circle.y - y;
  return dx * dx + dy * dy;
}

function Node(circle) {
  this._ = circle;
  this.next = null;
  this.previous = null;
}

export function packEnclose(circles) {
  if (!(n = circles.length)) return 0;

  var a, b, c, n;

  // Place the first circle.
  a = circles[0], a.x = 0, a.y = 0;
  if (!(n > 1)) return a.r;

  // Place the second circle.
  b = circles[1], a.x = -b.r, b.x = a.r, b.y = 0;
  if (!(n > 2)) return a.r + b.r;

  // Place the third circle.
  place(b, a, c = circles[2]);

  // Initialize the weighted centroid.
  var aa = a.r * a.r,
      ba = b.r * b.r,
      ca = c.r * c.r,
      oa = aa + ba + ca,
      ox = aa * a.x + ba * b.x + ca * c.x,
      oy = aa * a.y + ba * b.y + ca * c.y,
      cx, cy, i, j, k, sj, sk;

  // Initialize the front-chain using the first three circles a, b and c.
  a = new Node(a), b = new Node(b), c = new Node(c);
  a.next = c.previous = b;
  b.next = a.previous = c;
  c.next = b.previous = a;

  // Attempt to place each remaining circle…
  pack: for (i = 3; i < n; ++i) {
    place(a._, b._, c = circles[i]), c = new Node(c);

    // Find the closest intersecting circle on the front-chain, if any.
    // “Closeness” is determined by linear distance along the front-chain.
    // “Ahead” or “behind” is likewise determined by linear distance.
    j = b.next, k = a.previous, sj = b._.r + j._.r, sk = a._.r + k._.r;
    do {
      if (sj <= sk) {
        if (intersects(j._, c._)) {
          if (distance1(a, j) > distance1(j, b)) a = j; else b = j;
          a.next = b, b.previous = a, --i;
          continue pack;
        }
        sj += (j = j.next)._.r;
      } else {
        if (intersects(k._, c._)) {
          if (distance1(a, k) > distance1(k, b)) a = k; else b = k;
          a.next = b, b.previous = a, --i;
          continue pack;
        }
        sk += (k = k.previous)._.r;
      }
    } while (j !== k.next);

    // Success! Insert the new circle c between a and b.
    c.previous = a, c.next = b, a.next = b.previous = b = c;

    // Update the weighted centroid.
    oa += ca = c._.r * c._.r;
    ox += ca * c._.x;
    oy += ca * c._.y;

    // Compute the new closest circle a to centroid.
    aa = distance2(a._, cx = ox / oa, cy = oy / oa);
    while ((c = c.next) !== b) {
      if ((ca = distance2(c._, cx, cy)) < aa) {
        a = c, aa = ca;
      }
    }
    b = a.next;
  }

  // Compute the enclosing circle of the front chain.
  a = [b._], c = b; while ((c = c.next) !== b) a.push(c._); c = enclose(a);

  // Translate the circles to put the enclosing circle around the origin.
  for (i = 0; i < n; ++i) a = circles[i], a.x -= c.x, a.y -= c.y;

  return c.r;
}

export default function(circles) {
  packEnclose(circles);
  return circles;
}
<commit_msg>Optimize.<commit_after>import enclose from "./enclose";

function place(a, b, c) {
  var ax = a.x,
      ay = a.y,
      da = b.r + c.r,
      db = a.r + c.r,
      dx = b.x - ax,
      dy = b.y - ay,
      dc = dx * dx + dy * dy;
  if (dc) {
    var x = 0.5 + ((db *= db) - (da *= da)) / (2 * dc),
        y = Math.sqrt(Math.max(0, 2 * da * (db + dc) - (db -= dc) * db - da * da)) / (2 * dc);
    c.x = ax + x * dx + y * dy;
    c.y = ay + x * dy - y * dx;
  } else {
    c.x = ax + db;
    c.y = ay;
  }
}

function intersects(a, b) {
  var dx = b.x - a.x,
      dy = b.y - a.y,
      dr = a.r + b.r;
  return dr * dr - 1e-6 > dx * dx + dy * dy;
}

function distance1(a, b) {
  var l = a._.r;
  while (a !== b) l += 2 * (a = a.next)._.r;
  return l - b._.r;
}

function distance2(circle, x, y) {
  var dx = circle.x - x,
      dy = circle.y - y;
  return dx * dx + dy * dy;
}

function Node(circle) {
  this._ = circle;
  this.next = null;
  this.previous = null;
}

export function packEnclose(circles) {
  if (!(n = circles.length)) return 0;

  var a, b, c, n;

  // Place the first circle.
  a = circles[0], a.x = 0, a.y = 0;
  if (!(n > 1)) return a.r;

  // Place the second circle.
  b = circles[1], a.x = -b.r, b.x = a.r, b.y = 0;
  if (!(n > 2)) return a.r + b.r;

  // Place the third circle.
  place(b, a, c = circles[2]);

  // Initialize the weighted centroid.
  var aa = a.r * a.r,
      ba = b.r * b.r,
      ca = c.r * c.r,
      oa = aa + ba + ca,
      ox = aa * a.x + ba * b.x + ca * c.x,
      oy = aa * a.y + ba * b.y + ca * c.y,
      cx, cy, i, j, k, sj, sk;

  // Initialize the front-chain using the first three circles a, b and c.
  a = new Node(a), b = new Node(b), c = new Node(c);
  a.next = c.previous = b;
  b.next = a.previous = c;
  c.next = b.previous = a;

  // Attempt to place each remaining circle…
  pack: for (i = 3; i < n; ++i) {
    place(a._, b._, c = circles[i]), c = new Node(c);

    // Find the closest intersecting circle on the front-chain, if any.
    // “Closeness” is determined by linear distance along the front-chain.
    // “Ahead” or “behind” is likewise determined by linear distance.
    j = b.next, k = a.previous, sj = b._.r + j._.r, sk = a._.r + k._.r;
    do {
      if (sj <= sk) {
        if (intersects(j._, c._)) {
          if (sj + a._.r + b._.r > distance1(j, b)) a = j; else b = j;
          a.next = b, b.previous = a, --i;
          continue pack;
        }
        sj += j._.r + (j = j.next)._.r;
      } else {
        if (intersects(k._, c._)) {
          if (distance1(a, k) > sk + a._.r + b._.r) a = k; else b = k;
          a.next = b, b.previous = a, --i;
          continue pack;
        }
        sk += k._.r + (k = k.previous)._.r;
      }
    } while (j !== k.next);

    // Success! Insert the new circle c between a and b.
    c.previous = a, c.next = b, a.next = b.previous = b = c;

    // Update the weighted centroid.
    oa += ca = c._.r * c._.r;
    ox += ca * c._.x;
    oy += ca * c._.y;

    // Compute the new closest circle a to centroid.
    aa = distance2(a._, cx = ox / oa, cy = oy / oa);
    while ((c = c.next) !== b) {
      if ((ca = distance2(c._, cx, cy)) < aa) {
        a = c, aa = ca;
      }
    }
    b = a.next;
  }

  // Compute the enclosing circle of the front chain.
  a = [b._], c = b; while ((c = c.next) !== b) a.push(c._); c = enclose(a);

  // Translate the circles to put the enclosing circle around the origin.
  for (i = 0; i < n; ++i) a = circles[i], a.x -= c.x, a.y -= c.y;

  return c.r;
}

export default function(circles) {
  packEnclose(circles);
  return circles;
}
<|endoftext|>


<commit_before><commit_msg>Smallest code for: Find the first character in a string that doesn't repeat.<commit_after><|endoftext|>


<commit_before>import { LineBasicMaterial, Box3Helper, Box3, Group, LineSegments } from 'three';
import { arrayToBox } from './Utils/ArrayBoxUtilities.js';

const boxGeom = new Box3Helper().geometry;
let boundingBox = new Box3();

class MeshBVHRootVisualizer extends Group {

	constructor( mesh, material, depth = 10, group = 0 ) {

		super( 'MeshBVHRootVisualizer' );

		this.depth = depth;
		this.mesh = mesh;
		this._group = group;
		this._material = material;

		this.update();

	}

	update() {

		const boundsTree = this.mesh.geometry.boundsTree;
		let requiredChildren = 0;
		if ( boundsTree ) {

			boundsTree.traverse( ( depth, isLeaf, boundingData, offsetOrSplit, countOrIsUnfinished ) => {

				let isTerminal = isLeaf || countOrIsUnfinished;

				// Stop traversal
				if ( depth >= this.depth ) {

					return true;

				}

				if ( depth === this.depth - 1 || isTerminal ) {

					let m = requiredChildren < this.children.length ? this.children[ requiredChildren ] : null;
					if ( ! m ) {

						m = new LineSegments( boxGeom, this._material );
						m.raycast = () => [];
						this.add( m );

					}

					requiredChildren ++;
					arrayToBox( boundingData, boundingBox );
					boundingBox.getCenter( m.position );
					m.scale.subVectors( boundingBox.max, boundingBox.min ).multiplyScalar( 0.5 );

					if ( m.scale.x === 0 ) m.scale.x = Number.EPSILON;
					if ( m.scale.y === 0 ) m.scale.y = Number.EPSILON;
					if ( m.scale.z === 0 ) m.scale.z = Number.EPSILON;

				}

			} );

		}

		while ( this.children.length > requiredChildren ) this.remove( this.children.pop() );

	}

}

class MeshBVHVisualizer extends Group {

	get color() {

		return this._material.color;

	}

	get opacity() {

		return this._material.opacity;

	}

	set opacity( v ) {

		this._material.opacity = v;

	}

	constructor( mesh, depth = 10 ) {

		super( 'MeshBVHVisualizer' );

		this.depth = depth;
		this.mesh = mesh;
		this._roots = [];
		this._material = new LineBasicMaterial( { color: 0x00FF88, transparent: true, opacity: 0.3 } );

		this.update();

	}

	update() {

		const bvh = this.mesh.geometry.boundsTree;
		const totalRoots = bvh ? bvh._roots.length : 0;
		while ( this._roots.length > totalRoots ) {

			this._roots.pop();

		}

		for ( let i = 0; i < totalRoots; i ++ ) {

			if ( i >= this._roots.length ) {

				const root = new MeshBVHRootVisualizer( this.mesh, this._material, this.depth, i );
				this.add( root );
				this._roots.push( root );

			} else {

				let root = this._roots[ i ];
				root.depth = this.depth;
				root.mesh = this.mesh;
				root.update();

			}

		}

	}

	updateMatrixWorld( ...args ) {

		this.position.copy( this.mesh.position );
		this.rotation.copy( this.mesh.rotation );
		this.scale.copy( this.mesh.scale );

		super.updateMatrixWorld( ...args );

	}

	copy( source ) {

		this.depth = source.depth;
		this.mesh = source.mesh;

	}

	clone() {

		return new MeshBVHVisualizer( this.mesh, this.depth );

	}

	dispose() {

		this._material.dispose();

	}

}

export default MeshBVHVisualizer;
<commit_msg>Improve visualizer performance<commit_after>import { LineBasicMaterial, BufferAttribute, Box3, Group, LineSegments } from 'three';
import { arrayToBox } from './Utils/ArrayBoxUtilities.js';

const boundingBox = new Box3();
const keys = [ 'x', 'y', 'z' ];
class MeshBVHRootVisualizer extends Group {

	constructor( mesh, material, depth = 10, group = 0 ) {

		super( 'MeshBVHRootVisualizer' );

		const lines = new LineSegments( undefined, material );
		this.add( lines );

		this.depth = depth;
		this.mesh = mesh;
		this._lines = lines;
		this._group = group;

		this.update();

	}

	update() {

		const lines = this._lines;
		const linesGeometry = lines.geometry;
		const boundsTree = this.mesh.geometry.boundsTree;
		linesGeometry.dispose();
		lines.visible = false;
		if ( boundsTree ) {

			const targetDepth = this.depth - 1;
			let boundsCount = 0;
			boundsTree.traverse( ( depth, isLeaf ) => {

				if ( depth === targetDepth || isLeaf ) {

					boundsCount ++;
					return true;

				}

			} );

			let index = 0;
			const newPosition = new Float32Array( 12 * 6 * boundsCount );
			boundsTree.traverse( ( depth, isLeaf, boundingData ) => {

				if ( depth === targetDepth || isLeaf ) {

					arrayToBox( boundingData, boundingBox );

					const { min, max } = boundingBox;
					for ( let k = 0; k < 3; k ++ ) {

						const index0 = k;
						const index1 = ( k + 1 ) % 3;
						const index2 = ( k + 2 ) % 3;
						const key0 = keys[ k ];
						const key1 = keys[ index1 ];
						const key2 = keys[ index2 ];

						const v0Pos = max[ key0 ];
						const v0Neg = min[ key0 ];
						for ( let i = - 1; i <= 1; i += 2 ) {

							const v1 = i < 0 ? min[ key1 ] : max[ key1 ];
							for ( let j = - 1; j <= 1; j += 2 ) {

								const v2 = j < 0 ? min[ key2 ] : max[ key2 ];

								newPosition[ index + index0 ] = v0Neg;
								newPosition[ index + index1 ] = v1;
								newPosition[ index + index2 ] = v2;
								index += 3;

								newPosition[ index + index0 ] = v0Pos;
								newPosition[ index + index1 ] = v1;
								newPosition[ index + index2 ] = v2;
								index += 3;

							}

						}

					}

					return true;

				}

			} );

			linesGeometry.setAttribute(
				'position',
				new BufferAttribute( newPosition, 3, false ),
			);
			lines.visible = true;

		}

	}

}

class MeshBVHVisualizer extends Group {

	get color() {

		return this._material.color;

	}

	get opacity() {

		return this._material.opacity;

	}

	set opacity( v ) {

		this._material.opacity = v;

	}

	constructor( mesh, depth = 10 ) {

		super( 'MeshBVHVisualizer' );

		this.depth = depth;
		this.mesh = mesh;
		this._roots = [];
		this._material = new LineBasicMaterial( {
			color: 0x00FF88,
			transparent: true,
			opacity: 0.3,
			depthWrite: false,
		} );

		this.update();

	}

	update() {

		const bvh = this.mesh.geometry.boundsTree;
		const totalRoots = bvh ? bvh._roots.length : 0;
		while ( this._roots.length > totalRoots ) {

			this._roots.pop();

		}

		for ( let i = 0; i < totalRoots; i ++ ) {

			if ( i >= this._roots.length ) {

				const root = new MeshBVHRootVisualizer( this.mesh, this._material, this.depth, i );
				this.add( root );
				this._roots.push( root );

			} else {

				let root = this._roots[ i ];
				root.depth = this.depth;
				root.mesh = this.mesh;
				root.update();

			}

		}

	}

	updateMatrixWorld( ...args ) {

		this.position.copy( this.mesh.position );
		this.rotation.copy( this.mesh.rotation );
		this.scale.copy( this.mesh.scale );

		super.updateMatrixWorld( ...args );

	}

	copy( source ) {

		this.depth = source.depth;
		this.mesh = source.mesh;

	}

	clone() {

		return new MeshBVHVisualizer( this.mesh, this.depth );

	}

	dispose() {

		this._material.dispose();

	}

}

export default MeshBVHVisualizer;
<|endoftext|>


<commit_before>import CheckboxMixin from '../../mixins/checkbox'
import OptionMixin from '../../mixins/option'
import { QIcon } from '../icon'

export default {
  name: 'q-toggle',
  mixins: [CheckboxMixin, OptionMixin],
  props: {
    icon: String
  },
  computed: {
    currentIcon () {
      return (this.isActive ? this.checkedIcon : this.uncheckedIcon) || this.icon
    },
    iconColor () {
      return __THEME__ === 'ios'
        ? 'dark'
        : (this.isTrue ? 'white' : 'dark')
    },
    baseClass () {
      if (__THEME__ === 'ios' && this.dark) {
        return `q-toggle-base-dark`
      }
    }
  },
  methods: {
    __swipe (evt) {
      if (evt.direction === 'left') {
        if (this.isTrue) {
          this.toggle()
        }
      }
      else if (evt.direction === 'right') {
        if (this.isFalse) {
          this.toggle()
        }
      }
    },
    __getContent (h) {
      return [
        h('div', { staticClass: 'q-toggle-base', 'class': this.baseClass }),
        h('div', { staticClass: 'q-toggle-handle row flex-center' }, [
          this.currentIcon
            ? h(QIcon, {
              staticClass: 'q-toggle-icon',
              props: { name: this.currentIcon, color: this.iconColor }
            })
            : null,
          __THEME__ === 'mat'
            ? h('div', { ref: 'ripple', staticClass: 'q-radial-ripple' })
            : null
        ])
      ]
    }
  }
}
<commit_msg>fix(QToggle): icon based on state<commit_after>import CheckboxMixin from '../../mixins/checkbox'
import OptionMixin from '../../mixins/option'
import { QIcon } from '../icon'

export default {
  name: 'q-toggle',
  mixins: [CheckboxMixin, OptionMixin],
  props: {
    icon: String
  },
  computed: {
    currentIcon () {
      return (this.isTrue ? this.checkedIcon : this.uncheckedIcon) || this.icon
    },
    iconColor () {
      return __THEME__ === 'ios'
        ? 'dark'
        : (this.isTrue ? 'white' : 'dark')
    },
    baseClass () {
      if (__THEME__ === 'ios' && this.dark) {
        return `q-toggle-base-dark`
      }
    }
  },
  methods: {
    __swipe (evt) {
      if (evt.direction === 'left') {
        if (this.isTrue) {
          this.toggle()
        }
      }
      else if (evt.direction === 'right') {
        if (this.isFalse) {
          this.toggle()
        }
      }
    },
    __getContent (h) {
      return [
        h('div', { staticClass: 'q-toggle-base', 'class': this.baseClass }),
        h('div', { staticClass: 'q-toggle-handle row flex-center' }, [
          this.currentIcon
            ? h(QIcon, {
              staticClass: 'q-toggle-icon',
              props: { name: this.currentIcon, color: this.iconColor }
            })
            : null,
          __THEME__ === 'mat'
            ? h('div', { ref: 'ripple', staticClass: 'q-radial-ripple' })
            : null
        ])
      ]
    }
  }
}
<|endoftext|>


<commit_before>import React, { PropTypes } from 'react'
import List from 'material-ui/List/List'
import Subheader from 'material-ui/Subheader/Subheader'

import Message from './Message'

function MessagesContainer ({ messages }) {
  return (
    <List style={{ flex: 1 }}>
      <Subheader>Aujourd'hui</Subheader>
      {messages.map(({ from, date, content }, index) =>
        <Message from={from} date={date} key={index} content={content} />)}
    </List>
  )
}

MessagesContainer.propTypes = {
  messages: PropTypes.arrayOf(PropTypes.object)
}

export default MessagesContainer
<commit_msg>Fixed scrolling issue when list items overflowing.<commit_after>import React, { Component, PropTypes } from 'react'
import { findDOMNode } from 'react-dom'
import List from 'material-ui/List/List'
import Subheader from 'material-ui/Subheader/Subheader'

import Message from './Message'

class MessagesContainer extends Component {
  componentDidUpdate () {
    this.scrollToBottom()
  }

  scrollToBottom = () => {
    const list = findDOMNode(this.list)
    list.scrollTop = list.scrollHeight
  }

  render () {
    return (
      <List style={{ flex: 1, overflowY: 'auto' }} ref={ref => (this.list = ref)}>
        <Subheader>Aujourd'hui</Subheader>
        {this.props.messages.map(({ from, date, content }, index) =>
          <Message from={from} date={date} key={index} content={content}/>)}
      </List>
    )
  }
}

MessagesContainer.propTypes = {
  messages: PropTypes.arrayOf(PropTypes.object)
}

export default MessagesContainer
<|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/. */

// @flow

import * as React from 'react';
import classNames from 'classnames';
import {
  formatMilliseconds,
  formatTimestamp,
} from 'firefox-profiler/utils/format-numbers';
import explicitConnect from 'firefox-profiler/utils/connect';
import {
  getCategories,
  getMarkerSchemaByName,
  getImplementationFilter,
  getPageList,
  getZeroAt,
  getThreadIdToNameMap,
  getThreadSelectorsFromThreadsKey,
} from 'firefox-profiler/selectors';

import {
  TooltipNetworkMarkerPhases,
  getNetworkMarkerDetails,
} from './NetworkMarker';
import {
  TooltipDetails,
  TooltipDetail,
  type TooltipDetailComponent,
  TooltipDetailSeparator,
} from './TooltipDetails';
import { Backtrace } from 'firefox-profiler/components/shared/Backtrace';

import {
  formatFromMarkerSchema,
  getSchemaFromMarker,
} from 'firefox-profiler/profile-logic/marker-schema';
import { computeScreenshotSize } from 'firefox-profiler/profile-logic/marker-data';

import type {
  CategoryList,
  Milliseconds,
  Marker,
  ImplementationFilter,
  Thread,
  ThreadsKey,
  PageList,
  MarkerSchemaByName,
  MarkerIndex,
} from 'firefox-profiler/types';

import type { ConnectedProps } from 'firefox-profiler/utils/connect';
import {
  getGCMinorDetails,
  getGCMajorDetails,
  getGCSliceDetails,
} from './GCMarker';

function _maybeFormatDuration(
  start: number | void,
  end: number | void
): string {
  if (start !== undefined && end !== undefined) {
    return formatMilliseconds(end - start);
  }
  return 'unknown';
}

type OwnProps = {|
  +markerIndex: MarkerIndex,
  +marker: Marker,
  +threadsKey: ThreadsKey,
  +className?: string,
  // In tooltips it can be awkward for really long and tall things to force
  // the layout to be huge. This option when set to true will restrict the
  // height of things like stacks, and the width of long things like URLs.
  +restrictHeightWidth: boolean,
|};

type StateProps = {|
  +threadName?: string,
  +thread: Thread,
  +implementationFilter: ImplementationFilter,
  +pages: PageList | null,
  +zeroAt: Milliseconds,
  +threadIdToNameMap: Map<number, string>,
  +markerSchemaByName: MarkerSchemaByName,
  +getMarkerLabel: (MarkerIndex) => string,
  +categories: CategoryList,
|};

type Props = ConnectedProps<OwnProps, StateProps, {||}>;

// Maximum image size of a tooltip field.
const MAXIMUM_IMAGE_SIZE = 350;

/**
 * This component combines Marker Schema, and custom handling to generate tooltips
 * for markers.
 */
class MarkerTooltipContents extends React.PureComponent<Props> {
  _maybeRenderPageUrl = (): TooltipDetailComponent => {
    const { pages, marker } = this.props;

    if (!(pages && marker.data && marker.data.innerWindowID)) {
      return null;
    }

    const innerWindowID = marker.data.innerWindowID;
    const page = pages.find((page) => page.innerWindowID === innerWindowID);

    if (page) {
      // If multiple pages have the same url, show the innerWindowID to disambiguate.
      let innerWindowIDSuffix = null;
      if (pages.filter((p) => p.url === page.url).length > 1) {
        innerWindowIDSuffix = ' (id: ' + innerWindowID + ')';
      }
      try {
        const { host } = new URL(page.url);
        const hostIndex = page.url.indexOf(host);
        if (hostIndex === -1) {
          throw new Error(
            'Unable to find the host in the URL. This is a programming error.'
          );
        }
        const protocol = page.url.slice(0, hostIndex);
        const rest = page.url.slice(hostIndex + host.length);
        return (
          <TooltipDetail label="Page">

              {protocol}
              {host}
              {rest}
              {innerWindowIDSuffix}
              {page.isPrivateBrowsing ? ' (private)' : null}

          </TooltipDetail>
        );
      } catch (error) {
        // Could not parse the URL. Just display the entire thing
        let url = page.url;
        if (innerWindowIDSuffix) {
          url += innerWindowIDSuffix;
        }
        if (page.isPrivateBrowsing) {
          url += ' (private)';
        }
        return <TooltipDetail label="Page">{url}</TooltipDetail>;
      }
    }
    return null;
  };

  /**
   * Either print the thread name that marker is in, or get the thread name from
   * the threadId field if it's given. The second case is useful when a marker
   * belongs to another thread but in this thread for some limitations in the
   * platform side. For example, FileIO marker may belong to another thread if
   * they have threadId field.
   */
  _renderThreadDetails(): TooltipDetailComponent[] {
    const { marker, threadName, threadIdToNameMap } = this.props;
    const data = marker.data;

    if (!data || data.threadId === undefined) {
      return [
        <TooltipDetail label="Thread" key="thread">
          {threadName}
        </TooltipDetail>,
      ];
    }

    const threadId = data.threadId;
    const occurringThreadName = threadIdToNameMap.get(threadId);

    return [
      <TooltipDetail label="Recording Thread" key="recording">
        {threadName}
      </TooltipDetail>,
      // If we have the thread information of the occurring thread, then show.
      // Otherwise, only show the thread ID.
      occurringThreadName ? (
        <TooltipDetail
          label="Occurring Thread"
          key="occurring"
        >{`${occurringThreadName} (TID: ${threadId})`}</TooltipDetail>
      ) : (
        <TooltipDetail label="Occurring Thread ID" key="occurring">
          {threadId}
        </TooltipDetail>
      ),
    ];
  }

  /**
   * This function combines the Marker Schema formatting, and custom handling of
   * properties that are difficult to represent with the Schema.
   */
  _renderMarkerDetails(): TooltipDetailComponent[] {
    const { marker, markerSchemaByName, thread } = this.props;
    const data = marker.data;
    const details: TooltipDetailComponent[] = [];

    if (data) {
      // Add the details for the markers based on their Marker schema.
      const schema = getSchemaFromMarker(markerSchemaByName, marker);
      if (schema) {
        for (const schemaData of schema.data) {
          // Check for a schema that is looking up and formatting a value from
          // the payload.
          if (schemaData.value === undefined) {
            const { key, label, format } = schemaData;
            if (key in data) {
              const value = data[key];

              // Don't add undefined values, as values are optional.
              if (value !== undefined && value !== null) {
                details.push(
                  <TooltipDetail
                    key={schema.name + '-' + key}
                    label={label || key}
                  >
                    {formatFromMarkerSchema(schema.name, format, value)}
                  </TooltipDetail>
                );
              }
            }
          }

          // Do a check to see if there is no key. This means this is a simple
          // label that is applied to every marker of this type, with no data
          // lookup. For some reason Flow as not able to refine this.
          if (schemaData.key === undefined) {
            const { label, value } = schemaData;
            const key = label + '-' + value;
            details.push(
              <TooltipDetail key={key} label={label}>

{value}

              </TooltipDetail>
            );
          }
        }
      }

      switch (data.type) {
        case 'GCMinor': {
          // The GC schema is mostly ignored.
          details.push(...getGCMinorDetails(data));
          break;
        }
        case 'GCMajor': {
          // The GC schema is mostly ignored.
          details.push(...getGCMajorDetails(data));
          break;
        }
        case 'GCSlice': {
          // The GC schema is mostly ignored.
          details.push(...getGCSliceDetails(data));
          break;
        }
        case 'Network': {
          // Network markers embed lots of timing information inside of them, that
          // must be reworked in the tooltip.
          details.push(...getNetworkMarkerDetails(data));
          break;
        }
        case 'IPC': {
          // The logic to subtract times to create durations is not supported by
          // the marker schema. Use custom handling for these in addition to
          // the other properties supported by the Marker Schema.
          details.push(
            <TooltipDetail
              label="Send Thread Latency"
              key="IPC-Send Thread Latency"
            >
              {_maybeFormatDuration(data.startTime, data.sendStartTime)}
            </TooltipDetail>,
            <TooltipDetail
              label="IPC Send Duration"
              key="IPC-IPC Send Duration"
            >
              {_maybeFormatDuration(data.sendStartTime, data.sendEndTime)}
            </TooltipDetail>,
            <TooltipDetail label="IPC Recv Latency" key="IPC-IPC Recv Latency">
              {_maybeFormatDuration(data.sendEndTime, data.recvEndTime)}
            </TooltipDetail>,
            <TooltipDetail
              label="Recv Thread Latency"
              key="IPC-Recv Thread Latency"
            >
              {_maybeFormatDuration(data.recvEndTime, data.endTime)}
            </TooltipDetail>
          );
          break;
        }
        case 'CompositorScreenshot': {
          const { width, height } = computeScreenshotSize(
            data,
            MAXIMUM_IMAGE_SIZE
          );
          details.push(
            <TooltipDetail label="Image" key="CompositorScreenshot-image">

            </TooltipDetail>
          );
          break;
        }
        default:
        // Do nothing
      }
    }

    return details;
  }

  _maybeRenderMarkerDuration() {
    const { marker } = this.props;
    if (marker.end === null) {
      return null;
    }

    // We only know the duration if it's complete.
    const duration = marker.incomplete
      ? 'unknown duration'
      : formatTimestamp(marker.end - marker.start, 3, 1);

    return
{duration}
;
  }

  _maybeRenderBacktrace() {
    const {
      marker,
      thread,
      implementationFilter,
      restrictHeightWidth,
      categories,
    } = this.props;
    const { data, start } = marker;
    if (data && 'cause' in data && data.cause) {
      const { cause } = data;
      const causeAge = start - cause.time;
      return [
        <TooltipDetailSeparator key="backtrace-separator" />,
        <TooltipDetail label="Stack" key="backtrace">

            {
              /* The cause's time might be later than the marker's start. For
                example this happens in some usual cases when the cause is
                captured right when setting the end marker for tracing pairs of
                markers. */
              causeAge > 0 ? (

                  {data.type === 'Styles' || marker.name === 'Reflow'
                    ? `First invalidated ${formatTimestamp(
                        causeAge
                      )} before the flush, at:`
                    : `Triggered ${formatTimestamp(causeAge)} ago, at:`}

              ) : null
            }
            <Backtrace
              maxStacks={restrictHeightWidth ? 20 : Infinity}
              stackIndex={cause.stack}
              thread={thread}
              implementationFilter={implementationFilter}
              categories={categories}
            />

        </TooltipDetail>,
      ];
    }
    return null;
  }

  _maybeRenderNetworkPhases() {
    const {
      marker: { data },
      zeroAt,
    } = this.props;
    if (data && data.type === 'Network') {
      return <TooltipNetworkMarkerPhases payload={data} zeroAt={zeroAt} />;
    }
    return null;
  }

  _renderTitle(): string {
    const { markerIndex, getMarkerLabel } = this.props;
    return getMarkerLabel(markerIndex);
  }

  /**
   * Often-times component logic is split out into several different components. This
   * is really helpful for interactive components, as each list of Props serves as
   * a piece of memoization that can help with not over-rendering.
   *
   * This component is a little bit different, as it will only be rendered once per
   * marker, as it's hovered. In addition, most of the complexity is in generating
   * a list of TooltipDetailComponent, which are label/value pairs. Many parts
   * of the tooltip are controlled by the Marker Schema, however there are some
   * custom implementations for various markers that include some complexity.
   * The general practice here is to provide functions of the signature:
   *
   * Payload => TooltipDetailComponent[]
   *
   * These TooltipDetailComponent are then combined with those generated by the
   * Marker Schema.
   *
   * Finally, many sections are optional based on the data that is present. Note
   * that these render function begin with "maybe", rather than embedded complicated
   * ternaries everywhere. This leads to a style of render function that includes
   * a short list of rendering strategies, in the order they appear.
   */
  render() {
    const { className } = this.props;
    return (

            {this._maybeRenderMarkerDuration()}

{this._renderTitle()}

        <TooltipDetails>
          {this._renderMarkerDetails()}
          {this._renderThreadDetails()}
          {this._maybeRenderPageUrl()}
          {this._maybeRenderBacktrace()}
        </TooltipDetails>
        {this._maybeRenderNetworkPhases()}

    );
  }
}

export const TooltipMarker = explicitConnect<OwnProps, StateProps, {||}>({
  mapStateToProps: (state, props) => {
    const selectors = getThreadSelectorsFromThreadsKey(props.threadsKey);
    return {
      threadName: selectors.getFriendlyThreadName(state),
      thread: selectors.getThread(state),
      implementationFilter: getImplementationFilter(state),
      pages: getPageList(state),
      zeroAt: getZeroAt(state),
      threadIdToNameMap: getThreadIdToNameMap(state),
      markerSchemaByName: getMarkerSchemaByName(state),
      getMarkerLabel: selectors.getMarkerTooltipLabelGetter(state),
      categories: getCategories(state),
    };
  },
  component: MarkerTooltipContents,
});
<commit_msg>Add additional fields for the screenshot marker tooltips<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/. */

// @flow

import * as React from 'react';
import classNames from 'classnames';
import {
  formatMilliseconds,
  formatTimestamp,
} from 'firefox-profiler/utils/format-numbers';
import explicitConnect from 'firefox-profiler/utils/connect';
import {
  getCategories,
  getMarkerSchemaByName,
  getImplementationFilter,
  getPageList,
  getZeroAt,
  getThreadIdToNameMap,
  getThreadSelectorsFromThreadsKey,
} from 'firefox-profiler/selectors';

import {
  TooltipNetworkMarkerPhases,
  getNetworkMarkerDetails,
} from './NetworkMarker';
import {
  TooltipDetails,
  TooltipDetail,
  type TooltipDetailComponent,
  TooltipDetailSeparator,
} from './TooltipDetails';
import { Backtrace } from 'firefox-profiler/components/shared/Backtrace';

import {
  formatFromMarkerSchema,
  getSchemaFromMarker,
} from 'firefox-profiler/profile-logic/marker-schema';
import { computeScreenshotSize } from 'firefox-profiler/profile-logic/marker-data';

import type {
  CategoryList,
  Milliseconds,
  Marker,
  ImplementationFilter,
  Thread,
  ThreadsKey,
  PageList,
  MarkerSchemaByName,
  MarkerIndex,
} from 'firefox-profiler/types';

import type { ConnectedProps } from 'firefox-profiler/utils/connect';
import {
  getGCMinorDetails,
  getGCMajorDetails,
  getGCSliceDetails,
} from './GCMarker';

function _maybeFormatDuration(
  start: number | void,
  end: number | void
): string {
  if (start !== undefined && end !== undefined) {
    return formatMilliseconds(end - start);
  }
  return 'unknown';
}

type OwnProps = {|
  +markerIndex: MarkerIndex,
  +marker: Marker,
  +threadsKey: ThreadsKey,
  +className?: string,
  // In tooltips it can be awkward for really long and tall things to force
  // the layout to be huge. This option when set to true will restrict the
  // height of things like stacks, and the width of long things like URLs.
  +restrictHeightWidth: boolean,
|};

type StateProps = {|
  +threadName?: string,
  +thread: Thread,
  +implementationFilter: ImplementationFilter,
  +pages: PageList | null,
  +zeroAt: Milliseconds,
  +threadIdToNameMap: Map<number, string>,
  +markerSchemaByName: MarkerSchemaByName,
  +getMarkerLabel: (MarkerIndex) => string,
  +categories: CategoryList,
|};

type Props = ConnectedProps<OwnProps, StateProps, {||}>;

// Maximum image size of a tooltip field.
const MAXIMUM_IMAGE_SIZE = 350;

/**
 * This component combines Marker Schema, and custom handling to generate tooltips
 * for markers.
 */
class MarkerTooltipContents extends React.PureComponent<Props> {
  _maybeRenderPageUrl = (): TooltipDetailComponent => {
    const { pages, marker } = this.props;

    if (!(pages && marker.data && marker.data.innerWindowID)) {
      return null;
    }

    const innerWindowID = marker.data.innerWindowID;
    const page = pages.find((page) => page.innerWindowID === innerWindowID);

    if (page) {
      // If multiple pages have the same url, show the innerWindowID to disambiguate.
      let innerWindowIDSuffix = null;
      if (pages.filter((p) => p.url === page.url).length > 1) {
        innerWindowIDSuffix = ' (id: ' + innerWindowID + ')';
      }
      try {
        const { host } = new URL(page.url);
        const hostIndex = page.url.indexOf(host);
        if (hostIndex === -1) {
          throw new Error(
            'Unable to find the host in the URL. This is a programming error.'
          );
        }
        const protocol = page.url.slice(0, hostIndex);
        const rest = page.url.slice(hostIndex + host.length);
        return (
          <TooltipDetail label="Page">

              {protocol}
              {host}
              {rest}
              {innerWindowIDSuffix}
              {page.isPrivateBrowsing ? ' (private)' : null}

          </TooltipDetail>
        );
      } catch (error) {
        // Could not parse the URL. Just display the entire thing
        let url = page.url;
        if (innerWindowIDSuffix) {
          url += innerWindowIDSuffix;
        }
        if (page.isPrivateBrowsing) {
          url += ' (private)';
        }
        return <TooltipDetail label="Page">{url}</TooltipDetail>;
      }
    }
    return null;
  };

  /**
   * Either print the thread name that marker is in, or get the thread name from
   * the threadId field if it's given. The second case is useful when a marker
   * belongs to another thread but in this thread for some limitations in the
   * platform side. For example, FileIO marker may belong to another thread if
   * they have threadId field.
   */
  _renderThreadDetails(): TooltipDetailComponent[] {
    const { marker, threadName, threadIdToNameMap } = this.props;
    const data = marker.data;

    if (!data || data.threadId === undefined) {
      return [
        <TooltipDetail label="Thread" key="thread">
          {threadName}
        </TooltipDetail>,
      ];
    }

    const threadId = data.threadId;
    const occurringThreadName = threadIdToNameMap.get(threadId);

    return [
      <TooltipDetail label="Recording Thread" key="recording">
        {threadName}
      </TooltipDetail>,
      // If we have the thread information of the occurring thread, then show.
      // Otherwise, only show the thread ID.
      occurringThreadName ? (
        <TooltipDetail
          label="Occurring Thread"
          key="occurring"
        >{`${occurringThreadName} (TID: ${threadId})`}</TooltipDetail>
      ) : (
        <TooltipDetail label="Occurring Thread ID" key="occurring">
          {threadId}
        </TooltipDetail>
      ),
    ];
  }

  /**
   * This function combines the Marker Schema formatting, and custom handling of
   * properties that are difficult to represent with the Schema.
   */
  _renderMarkerDetails(): TooltipDetailComponent[] {
    const { marker, markerSchemaByName, thread } = this.props;
    const data = marker.data;
    const details: TooltipDetailComponent[] = [];

    if (data) {
      // Add the details for the markers based on their Marker schema.
      const schema = getSchemaFromMarker(markerSchemaByName, marker);
      if (schema) {
        for (const schemaData of schema.data) {
          // Check for a schema that is looking up and formatting a value from
          // the payload.
          if (schemaData.value === undefined) {
            const { key, label, format } = schemaData;
            if (key in data) {
              const value = data[key];

              // Don't add undefined values, as values are optional.
              if (value !== undefined && value !== null) {
                details.push(
                  <TooltipDetail
                    key={schema.name + '-' + key}
                    label={label || key}
                  >
                    {formatFromMarkerSchema(schema.name, format, value)}
                  </TooltipDetail>
                );
              }
            }
          }

          // Do a check to see if there is no key. This means this is a simple
          // label that is applied to every marker of this type, with no data
          // lookup. For some reason Flow as not able to refine this.
          if (schemaData.key === undefined) {
            const { label, value } = schemaData;
            const key = label + '-' + value;
            details.push(
              <TooltipDetail key={key} label={label}>

{value}

              </TooltipDetail>
            );
          }
        }
      }

      switch (data.type) {
        case 'GCMinor': {
          // The GC schema is mostly ignored.
          details.push(...getGCMinorDetails(data));
          break;
        }
        case 'GCMajor': {
          // The GC schema is mostly ignored.
          details.push(...getGCMajorDetails(data));
          break;
        }
        case 'GCSlice': {
          // The GC schema is mostly ignored.
          details.push(...getGCSliceDetails(data));
          break;
        }
        case 'Network': {
          // Network markers embed lots of timing information inside of them, that
          // must be reworked in the tooltip.
          details.push(...getNetworkMarkerDetails(data));
          break;
        }
        case 'IPC': {
          // The logic to subtract times to create durations is not supported by
          // the marker schema. Use custom handling for these in addition to
          // the other properties supported by the Marker Schema.
          details.push(
            <TooltipDetail
              label="Send Thread Latency"
              key="IPC-Send Thread Latency"
            >
              {_maybeFormatDuration(data.startTime, data.sendStartTime)}
            </TooltipDetail>,
            <TooltipDetail
              label="IPC Send Duration"
              key="IPC-IPC Send Duration"
            >
              {_maybeFormatDuration(data.sendStartTime, data.sendEndTime)}
            </TooltipDetail>,
            <TooltipDetail label="IPC Recv Latency" key="IPC-IPC Recv Latency">
              {_maybeFormatDuration(data.sendEndTime, data.recvEndTime)}
            </TooltipDetail>,
            <TooltipDetail
              label="Recv Thread Latency"
              key="IPC-Recv Thread Latency"
            >
              {_maybeFormatDuration(data.recvEndTime, data.endTime)}
            </TooltipDetail>
          );
          break;
        }
        case 'CompositorScreenshot': {
          const { width, height } = computeScreenshotSize(
            data,
            MAXIMUM_IMAGE_SIZE
          );
          details.push(
            <TooltipDetail label="Image" key="CompositorScreenshot-image">

            </TooltipDetail>,
            <TooltipDetail
              label="Window Size"
              key="CompositorScreenshot-window size"
            >
              <>
                {data.windowWidth}px x {data.windowHeight}px
              </>
            </TooltipDetail>,
            <TooltipDetail
              label="Description"
              key="CompositorScreenshot-description"
            >
              This marker spans the time between each composite of a window and
              shows the window contents during that time.
            </TooltipDetail>
          );
          break;
        }
        default:
        // Do nothing
      }
    }

    return details;
  }

  _maybeRenderMarkerDuration() {
    const { marker } = this.props;
    if (marker.end === null) {
      return null;
    }

    // We only know the duration if it's complete.
    const duration = marker.incomplete
      ? 'unknown duration'
      : formatTimestamp(marker.end - marker.start, 3, 1);

    return
{duration}
;
  }

  _maybeRenderBacktrace() {
    const {
      marker,
      thread,
      implementationFilter,
      restrictHeightWidth,
      categories,
    } = this.props;
    const { data, start } = marker;
    if (data && 'cause' in data && data.cause) {
      const { cause } = data;
      const causeAge = start - cause.time;
      return [
        <TooltipDetailSeparator key="backtrace-separator" />,
        <TooltipDetail label="Stack" key="backtrace">

            {
              /* The cause's time might be later than the marker's start. For
                example this happens in some usual cases when the cause is
                captured right when setting the end marker for tracing pairs of
                markers. */
              causeAge > 0 ? (

                  {data.type === 'Styles' || marker.name === 'Reflow'
                    ? `First invalidated ${formatTimestamp(
                        causeAge
                      )} before the flush, at:`
                    : `Triggered ${formatTimestamp(causeAge)} ago, at:`}

              ) : null
            }
            <Backtrace
              maxStacks={restrictHeightWidth ? 20 : Infinity}
              stackIndex={cause.stack}
              thread={thread}
              implementationFilter={implementationFilter}
              categories={categories}
            />

        </TooltipDetail>,
      ];
    }
    return null;
  }

  _maybeRenderNetworkPhases() {
    const {
      marker: { data },
      zeroAt,
    } = this.props;
    if (data && data.type === 'Network') {
      return <TooltipNetworkMarkerPhases payload={data} zeroAt={zeroAt} />;
    }
    return null;
  }

  _renderTitle(): string {
    const { markerIndex, getMarkerLabel } = this.props;
    return getMarkerLabel(markerIndex);
  }

  /**
   * Often-times component logic is split out into several different components. This
   * is really helpful for interactive components, as each list of Props serves as
   * a piece of memoization that can help with not over-rendering.
   *
   * This component is a little bit different, as it will only be rendered once per
   * marker, as it's hovered. In addition, most of the complexity is in generating
   * a list of TooltipDetailComponent, which are label/value pairs. Many parts
   * of the tooltip are controlled by the Marker Schema, however there are some
   * custom implementations for various markers that include some complexity.
   * The general practice here is to provide functions of the signature:
   *
   * Payload => TooltipDetailComponent[]
   *
   * These TooltipDetailComponent are then combined with those generated by the
   * Marker Schema.
   *
   * Finally, many sections are optional based on the data that is present. Note
   * that these render function begin with "maybe", rather than embedded complicated
   * ternaries everywhere. This leads to a style of render function that includes
   * a short list of rendering strategies, in the order they appear.
   */
  render() {
    const { className } = this.props;
    return (

            {this._maybeRenderMarkerDuration()}

{this._renderTitle()}

        <TooltipDetails>
          {this._renderMarkerDetails()}
          {this._renderThreadDetails()}
          {this._maybeRenderPageUrl()}
          {this._maybeRenderBacktrace()}
        </TooltipDetails>
        {this._maybeRenderNetworkPhases()}

    );
  }
}

export const TooltipMarker = explicitConnect<OwnProps, StateProps, {||}>({
  mapStateToProps: (state, props) => {
    const selectors = getThreadSelectorsFromThreadsKey(props.threadsKey);
    return {
      threadName: selectors.getFriendlyThreadName(state),
      thread: selectors.getThread(state),
      implementationFilter: getImplementationFilter(state),
      pages: getPageList(state),
      zeroAt: getZeroAt(state),
      threadIdToNameMap: getThreadIdToNameMap(state),
      markerSchemaByName: getMarkerSchemaByName(state),
      getMarkerLabel: selectors.getMarkerTooltipLabelGetter(state),
      categories: getCategories(state),
    };
  },
  component: MarkerTooltipContents,
});
<|endoftext|>


<commit_before>// @flow

import { COLOR_ARRAY } from "constants/controlTypes";

import { rgba } from "utils";

import type { ColorRGBA } from "types";

// https://en.wikipedia.org/wiki/List_of_8-bit_computer_hardware_palettes
// https://en.wikipedia.org/wiki/Color_Graphics_Adapter

const cga = {
  BLACK: rgba(0, 0, 0, 255), // 0 black
  BLUE: rgba(0, 0, 170, 255), // 1 blue
  GREEN: rgba(0, 170, 0, 255), // 2 green
  CYAN: rgba(0, 170, 170, 255), // 3 cyan
  RED: rgba(170, 0, 0, 255), // 4 red
  MAGENTA: rgba(170, 0, 170, 255), // 5 magenta
  YELLOW: rgba(170, 170, 0, 255), // 6 yellow
  WHITE: rgba(170, 170, 170, 255), // 7 white
  BBLACK: rgba(85, 85, 85, 255), // 8 bright black
  BBLUE: rgba(85, 85, 255, 255), // 9 bright blue
  BGREEN: rgba(85, 255, 85, 255), // 10 bright green
  BCYAN: rgba(85, 255, 255, 255), // 11 bright cyan
  BRED: rgba(255, 85, 85, 255), // 12 bright red
  BMAGENTA: rgba(255, 85, 255, 255), // 13 bright magenta
  BYELLOW: rgba(255, 255, 85, 255), // 14 bright yellow
  BWHITE: rgba(255, 255, 255, 255) // 15 bright white
};

export const THEMES = {
  CMYK: [
    rgba(0, 255, 255, 255), // cyan
    rgba(255, 0, 255, 255), // magenta
    rgba(255, 255, 0, 255), // yellow
    rgba(0, 0, 0, 255), // black
    rgba(255, 255, 255, 255)
  ],
  EGA_MODE4_PALETTE1_LOW: [cga.BLACK, cga.WHITE, cga.MAGENTA, cga.CYAN],
  EGA_MODE4_PALETTE1_HIGH: [cga.BLACK, cga.WHITE, cga.BMAGENTA, cga.BCYAN],
  EGA_MODE4_PALETTE2_LOW: [cga.BLACK, cga.GREEN, cga.RED, cga.YELLOW],
  EGA_MODE4_PALETTE2_HIGH: [cga.BLACK, cga.BGREEN, cga.BRED, cga.BYELLOW],
  CGA: [
    cga.BLACK,
    cga.BLUE,
    cga.GREEN,
    cga.CYAN,
    cga.RED,
    cga.MAGENTA,
    cga.YELLOW,
    cga.WHITE,
    cga.BBLACK,
    cga.BBLUE,
    cga.BGREEN,
    cga.BCYAN,
    cga.BRED,
    cga.BMAGENTA,
    cga.BYELLOW,
    cga.BWHITE
  ],
  MAC2: [
    rgba(255, 255, 255, 255), // white
    rgba(255, 255, 0, 255), // yellow
    rgba(255, 102, 0, 255), // orange
    rgba(221, 0, 0, 255), // red
    rgba(255, 0, 153, 255), // magenta
    rgba(51, 0, 153, 255), // purple
    rgba(0, 0, 204, 255), // blue
    rgba(0, 153, 255, 255), // cyan
    rgba(0, 170, 0, 255), // green
    rgba(0, 102, 0, 255), // dark green
    rgba(102, 51, 0, 255), // brown
    rgba(153, 102, 51, 255), // tan
    rgba(187, 187, 187, 255), // light grey
    rgba(136, 136, 136, 255), // medium grey
    rgba(68, 68, 68, 255), // dark grey
    rgba(0, 0, 0, 255) // black
  ],
  SEPIA: [
    rgba(8, 8, 0, 255),
    rgba(39, 34, 24, 255),
    rgba(70, 63, 48, 255),
    rgba(101, 93, 72, 255),
    rgba(132, 122, 96, 255),
    rgba(163, 152, 120, 255),
    rgba(194, 182, 145, 255),
    rgba(225, 211, 169, 255)
  ],
  VAPORWAVE: [
    // modified http://www.colourlovers.com/palette/3636765/seapunk_vaporwave
    rgba(255, 106, 213, 255),
    rgba(199, 116, 232, 255),
    rgba(173, 140, 255, 255),
    rgba(135, 149, 232, 255),
    rgba(148, 208, 255, 255),
    rgba(0, 0, 0, 255)
  ],
  WIREDSOUND: [
    // https://fauux.neocities.org
    rgba(210, 115, 138, 255),
    rgba(0, 0, 0, 255),
    rgba(193, 180, 146, 255)
  ],
  EMPTY: []
};

const optionTypes = {
  palette: {
    type: COLOR_ARRAY,
    default: THEMES.CGA
  }
};

const defaults = { colors: optionTypes.palette.default };

// https://en.wikipedia.org/wiki/Color_difference
// Simple Euclidian distance
// const colorDistance = (a: ColorRGBA, b: ColorRGBA): number =>
//   Math.sqrt((a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2 + (a[2] - b[2]) ** 2);

// TODO: Use LAB?
// Approximation given on Wiki page
const colorDistance = (a: ColorRGBA, b: ColorRGBA): number => {
  const r = (a[0] + b[0]) / 2;
  const dR = a[0] - b[0];
  const dG = a[1] - b[1];
  const dB = a[2] - b[2];

  const dRc = (2 + r / 256) * dR ** 2;
  const dGc = 4 * dG ** 2 + (2 + (255 - r) / 256);
  const dBc = dB ** 2;

  return Math.sqrt(dRc + dGc + dBc);
};

// Gets nearest color
const getColor = (
  color: ColorRGBA,
  options: { colors: Array<ColorRGBA> } = defaults
): ColorRGBA => {
  const { colors } = options;

  if (!colors) {
    return color;
  }

  let min = null;
  let minDistance = 0;

  colors.forEach(pc => {
    const distance = Math.abs(colorDistance(pc, color));

    if (min === null) {
      min = pc;
      minDistance = distance;
    } else if (distance < minDistance) {
      min = pc;
      minDistance = distance;
    }
  });

  return !min ? color : min;
};

const user = {
  name: "User",
  getColor,
  options: defaults,
  optionTypes,
  defaults
};

export default user;

export const createPalette = (colors: Array<ColorRGBA>) => ({
  ...user,
  options: { ...user.options, colors, defaults: colors }
});
<commit_msg>Add gameboy palette<commit_after>// @flow

import { COLOR_ARRAY } from "constants/controlTypes";

import { rgba } from "utils";

import type { ColorRGBA } from "types";

// https://en.wikipedia.org/wiki/List_of_8-bit_computer_hardware_palettes
// https://en.wikipedia.org/wiki/Color_Graphics_Adapter

const cga = {
  BLACK: rgba(0, 0, 0, 255), // 0 black
  BLUE: rgba(0, 0, 170, 255), // 1 blue
  GREEN: rgba(0, 170, 0, 255), // 2 green
  CYAN: rgba(0, 170, 170, 255), // 3 cyan
  RED: rgba(170, 0, 0, 255), // 4 red
  MAGENTA: rgba(170, 0, 170, 255), // 5 magenta
  YELLOW: rgba(170, 170, 0, 255), // 6 yellow
  WHITE: rgba(170, 170, 170, 255), // 7 white
  BBLACK: rgba(85, 85, 85, 255), // 8 bright black
  BBLUE: rgba(85, 85, 255, 255), // 9 bright blue
  BGREEN: rgba(85, 255, 85, 255), // 10 bright green
  BCYAN: rgba(85, 255, 255, 255), // 11 bright cyan
  BRED: rgba(255, 85, 85, 255), // 12 bright red
  BMAGENTA: rgba(255, 85, 255, 255), // 13 bright magenta
  BYELLOW: rgba(255, 255, 85, 255), // 14 bright yellow
  BWHITE: rgba(255, 255, 255, 255) // 15 bright white
};

export const THEMES = {
  CMYK: [
    rgba(0, 255, 255, 255), // cyan
    rgba(255, 0, 255, 255), // magenta
    rgba(255, 255, 0, 255), // yellow
    rgba(0, 0, 0, 255), // black
    rgba(255, 255, 255, 255)
  ],
  EGA_MODE4_PALETTE1_LOW: [cga.BLACK, cga.WHITE, cga.MAGENTA, cga.CYAN],
  EGA_MODE4_PALETTE1_HIGH: [cga.BLACK, cga.WHITE, cga.BMAGENTA, cga.BCYAN],
  EGA_MODE4_PALETTE2_LOW: [cga.BLACK, cga.GREEN, cga.RED, cga.YELLOW],
  EGA_MODE4_PALETTE2_HIGH: [cga.BLACK, cga.BGREEN, cga.BRED, cga.BYELLOW],
  CGA: [
    cga.BLACK,
    cga.BLUE,
    cga.GREEN,
    cga.CYAN,
    cga.RED,
    cga.MAGENTA,
    cga.YELLOW,
    cga.WHITE,
    cga.BBLACK,
    cga.BBLUE,
    cga.BGREEN,
    cga.BCYAN,
    cga.BRED,
    cga.BMAGENTA,
    cga.BYELLOW,
    cga.BWHITE
  ],
  MAC2: [
    rgba(255, 255, 255, 255), // white
    rgba(255, 255, 0, 255), // yellow
    rgba(255, 102, 0, 255), // orange
    rgba(221, 0, 0, 255), // red
    rgba(255, 0, 153, 255), // magenta
    rgba(51, 0, 153, 255), // purple
    rgba(0, 0, 204, 255), // blue
    rgba(0, 153, 255, 255), // cyan
    rgba(0, 170, 0, 255), // green
    rgba(0, 102, 0, 255), // dark green
    rgba(102, 51, 0, 255), // brown
    rgba(153, 102, 51, 255), // tan
    rgba(187, 187, 187, 255), // light grey
    rgba(136, 136, 136, 255), // medium grey
    rgba(68, 68, 68, 255), // dark grey
    rgba(0, 0, 0, 255) // black
  ],
  SEPIA: [
    rgba(8, 8, 0, 255),
    rgba(39, 34, 24, 255),
    rgba(70, 63, 48, 255),
    rgba(101, 93, 72, 255),
    rgba(132, 122, 96, 255),
    rgba(163, 152, 120, 255),
    rgba(194, 182, 145, 255),
    rgba(225, 211, 169, 255)
  ],
  GAMEBOY: [
    rgba(155, 188, 15, 255),
    rgba(139, 172, 15, 255),
    rgba(48, 98, 48, 255),
    rgba(15, 56, 15, 255)
  ],
  VAPORWAVE: [
    // modified http://www.colourlovers.com/palette/3636765/seapunk_vaporwave
    rgba(255, 106, 213, 255),
    rgba(199, 116, 232, 255),
    rgba(173, 140, 255, 255),
    rgba(135, 149, 232, 255),
    rgba(148, 208, 255, 255),
    rgba(0, 0, 0, 255)
  ],
  WIREDSOUND: [
    // https://fauux.neocities.org
    rgba(210, 115, 138, 255),
    rgba(0, 0, 0, 255),
    rgba(193, 180, 146, 255)
  ],
  EMPTY: []
};

const optionTypes = {
  palette: {
    type: COLOR_ARRAY,
    default: THEMES.CGA
  }
};

const defaults = { colors: optionTypes.palette.default };

// https://en.wikipedia.org/wiki/Color_difference
// Simple Euclidian distance
// const colorDistance = (a: ColorRGBA, b: ColorRGBA): number =>
//   Math.sqrt((a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2 + (a[2] - b[2]) ** 2);

// TODO: Use LAB?
// Approximation given on Wiki page
const colorDistance = (a: ColorRGBA, b: ColorRGBA): number => {
  const r = (a[0] + b[0]) / 2;
  const dR = a[0] - b[0];
  const dG = a[1] - b[1];
  const dB = a[2] - b[2];

  const dRc = (2 + r / 256) * dR ** 2;
  const dGc = 4 * dG ** 2 + (2 + (255 - r) / 256);
  const dBc = dB ** 2;

  return Math.sqrt(dRc + dGc + dBc);
};

// Gets nearest color
const getColor = (
  color: ColorRGBA,
  options: { colors: Array<ColorRGBA> } = defaults
): ColorRGBA => {
  const { colors } = options;

  if (!colors) {
    return color;
  }

  let min = null;
  let minDistance = 0;

  colors.forEach(pc => {
    const distance = Math.abs(colorDistance(pc, color));

    if (min === null) {
      min = pc;
      minDistance = distance;
    } else if (distance < minDistance) {
      min = pc;
      minDistance = distance;
    }
  });

  return !min ? color : min;
};

const user = {
  name: "User",
  getColor,
  options: defaults,
  optionTypes,
  defaults
};

export default user;

export const createPalette = (colors: Array<ColorRGBA>) => ({
  ...user,
  options: { ...user.options, colors, defaults: colors }
});
<|endoftext|>


<commit_before>import React from 'react/addons';
import GeminiScrollbar from 'react-gemini-scrollbar';

import * as DOMUtil from '../Util/DOMUtil';

const CSSTransitionGroup = React.addons.CSSTransitionGroup;
const METHODS_TO_BIND = ['handleWindowResize', 'handleBackdropClick', 'closeModal'];

export default class ModalPortal extends React.Component {
  constructor() {
    super();

    METHODS_TO_BIND.forEach(function (method) {
      this[method] = this[method].bind(this);
    }, this);
  }

  componentDidUpdate() {
    if (this.props.open) {
      this.checkHeight();
    }
  }

  componentWillMount() {
    window.addEventListener('resize', this.handleWindowResize);
  }

  componentWillUnmount() {
    window.removeEventListener('resize', this.handleWindowResize);
    this.heightInfo = null;
  }

  shouldComponentUpdate(nextProps) {
    return nextProps.open !== this.props.open;
  }

  handleBackdropClick() {
    if (this.props.closeByBackdropClick) {
      this.closeModal();
    }
  }

  handleWindowResize() {
    // Render the modal again if the window resizes because the height
    // of the viewport may change and we need to adapt to that.
    this.forceUpdate();
  }

  checkHeight() {
    this.heightInfo = this.getInnerContainerHeightInfo();
    let heightInfo = this.heightInfo;
    let biggerThanMaxHeight = heightInfo.originalHeight > heightInfo.maxHeight;

    // If content is bigger than the maxHeight, cap it.
    if (biggerThanMaxHeight && this.props.open || !this.rerendered) {
      this.rerendered = true;
      this.forceUpdate();
    }
  }

  closeModal() {
    this.props.onClose();
    this.heightInfo = false;
    this.rerendered = false;
  }

  getInnerContainerHeightInfo() {
    let heightInfo = {
      originalHeight: null,
      innerHeight: null,
      maxHeight: null
    };

    let innerContainer = this.refs['inner-container'];
    if (!innerContainer) {
      return heightInfo;
    }

    let originalHeight = innerContainer.getDOMNode().offsetHeight;

    // Height without padding, margin, border.
    let innerHeight = DOMUtil.getComputedDimensions(innerContainer.getDOMNode()).height;

    // Height of padding, margin, border.
    let outerHeight = originalHeight - innerHeight;

    // Modal cannot be bigger than this height.
    let maxHeight = window.innerHeight * this.props.maxHeightPercentage + 10;

    return {
      // Add 10 for the gemini horizontal scrollbar
      maxHeight: Math.min(originalHeight, maxHeight + 10),

      // We minus the maxHeight with the outerHeight because it will
      // not show the content correctly due to 'box-sizing: border-box'.
      innerHeight: Math.min(innerHeight, maxHeight - outerHeight),

      originalHeight: originalHeight
    };
  }

  getCloseButton() {
    if (!this.props.showCloseButton) {
      return null;
    }

    return (

          Close

        x

    );
  }

  getFooter() {
    if (this.props.showFooter === false) {
      return null;
    }

    return (

          {this.props.footer}

    );
  }

  getModalContent(useScrollbar, innerHeight) {
    if (!useScrollbar) {
      return (

          {this.props.children}

      );
    }

    let geminiContainerStyle = {
      height: innerHeight
    };

    return (
      <GeminiScrollbar autoshow={true} className="container-scrollable" style={geminiContainerStyle}>

          {this.props.children}

      </GeminiScrollbar>
    );
  }

  getModal() {
    if (!this.props.open) {
      return null;
    }

    let heightInfo = this.heightInfo;
    let maxHeight = null;
    let innerHeight = null;
    let useScrollbar = false;
    let originalHeight = null;

    if (heightInfo) {
      useScrollbar = true;
      innerHeight = heightInfo.innerHeight;
      maxHeight = heightInfo.maxHeight;
      originalHeight = heightInfo.originalHeight;
    }

    let modalStyle = {
      height: Math.min(originalHeight, maxHeight)
    };

    let containerStyle = {
      height: window.innerHeight
    };

    return (

          {this.getCloseButton()}

                {this.props.titleText}

              {this.props.subHeader}

              {this.getModalContent(useScrollbar, innerHeight)}

          {this.getFooter()}

    );
  }

  render() {
    return (
      <CSSTransitionGroup transitionAppear={true} transitionName="modal" component="section">
        {this.getModal()}
      </CSSTransitionGroup>
    );
  }
}

ModalPortal.propTypes = {
  closeByBackdropClick: React.PropTypes.bool,
  footer: React.PropTypes.object,
  maxHeightPercentage: React.PropTypes.number,
  onClose: React.PropTypes.func,
  open: React.PropTypes.bool,
  showCloseButton: React.PropTypes.bool,
  showFooter: React.PropTypes.bool,
  subHeader: React.PropTypes.node,
  titleText: React.PropTypes.string,

  // Classes
  backdropClass: React.PropTypes.string,
  bodyClass: React.PropTypes.string,
  closeButtonClass: React.PropTypes.string,
  closeIconClass: React.PropTypes.string,
  closeTitleClass: React.PropTypes.string,
  containerClass: React.PropTypes.string,
  footerClass: React.PropTypes.string,
  footerContainerClass: React.PropTypes.string,
  headerClass: React.PropTypes.string,
  headerContainerClass: React.PropTypes.string,
  innerBodyClass: React.PropTypes.string,
  modalClass: React.PropTypes.string,
  titleClass: React.PropTypes.string
};
<commit_msg>Add margin to modal content<commit_after>import React from 'react/addons';
import GeminiScrollbar from 'react-gemini-scrollbar';

import * as DOMUtil from '../Util/DOMUtil';

const CSSTransitionGroup = React.addons.CSSTransitionGroup;
const METHODS_TO_BIND = ['handleWindowResize', 'handleBackdropClick', 'closeModal'];

export default class ModalPortal extends React.Component {
  constructor() {
    super();

    METHODS_TO_BIND.forEach(function (method) {
      this[method] = this[method].bind(this);
    }, this);
  }

  componentDidUpdate() {
    if (this.props.open) {
      this.checkHeight();
    }
  }

  componentWillMount() {
    window.addEventListener('resize', this.handleWindowResize);
  }

  componentWillUnmount() {
    window.removeEventListener('resize', this.handleWindowResize);
    this.heightInfo = null;
  }

  shouldComponentUpdate(nextProps) {
    return nextProps.open !== this.props.open;
  }

  handleBackdropClick() {
    if (this.props.closeByBackdropClick) {
      this.closeModal();
    }
  }

  handleWindowResize() {
    // Render the modal again if the window resizes because the height
    // of the viewport may change and we need to adapt to that.
    this.forceUpdate();
  }

  checkHeight() {
    this.heightInfo = this.getInnerContainerHeightInfo();
    let heightInfo = this.heightInfo;
    let biggerThanMaxHeight = heightInfo.originalHeight > heightInfo.maxHeight;

    // If content is bigger than the maxHeight, cap it.
    if (biggerThanMaxHeight && this.props.open || !this.rerendered) {
      this.rerendered = true;
      this.forceUpdate();
    }
  }

  closeModal() {
    this.props.onClose();
    this.heightInfo = false;
    this.rerendered = false;
  }

  getInnerContainerHeightInfo() {
    let heightInfo = {
      originalHeight: null,
      innerHeight: null,
      maxHeight: null
    };

    let innerContainer = this.refs['inner-container'];
    if (!innerContainer) {
      return heightInfo;
    }

    let originalHeight = innerContainer.getDOMNode().offsetHeight;

    // Height without padding, margin, border.
    let innerHeight = DOMUtil.getComputedDimensions(innerContainer.getDOMNode()).height;

    // Height of padding, margin, border.
    let outerHeight = originalHeight - innerHeight;

    // Modal cannot be bigger than this height.
    let maxHeight = window.innerHeight * this.props.maxHeightPercentage + 10;

    return {
      // Add 10 for the gemini horizontal scrollbar
      maxHeight: Math.min(originalHeight, maxHeight + 10),

      // We minus the maxHeight with the outerHeight because it will
      // not show the content correctly due to 'box-sizing: border-box'.
      innerHeight: Math.min(innerHeight, maxHeight - outerHeight),

      originalHeight: originalHeight
    };
  }

  getCloseButton() {
    if (!this.props.showCloseButton) {
      return null;
    }

    return (

          Close

        x

    );
  }

  getFooter() {
    if (this.props.showFooter === false) {
      return null;
    }

    return (

          {this.props.footer}

    );
  }

  getModalContent(useScrollbar, innerHeight) {
    if (!useScrollbar) {
      return (

          {this.props.children}

      );
    }

    let geminiContainerStyle = {
      height: innerHeight
    };

    return (
      <GeminiScrollbar autoshow={true} className="container-scrollable" style={geminiContainerStyle}>

          {this.props.children}

      </GeminiScrollbar>
    );
  }

  getModal() {
    if (!this.props.open) {
      return null;
    }

    let heightInfo = this.heightInfo;
    let maxHeight = null;
    let innerHeight = null;
    let useScrollbar = false;
    let originalHeight = null;

    if (heightInfo) {
      useScrollbar = true;
      innerHeight = heightInfo.innerHeight;
      maxHeight = heightInfo.maxHeight;
      originalHeight = heightInfo.originalHeight;
    }

    let modalStyle = {
      height: Math.min(originalHeight, maxHeight)
    };

    let containerStyle = {
      height: window.innerHeight
    };

    return (

          {this.getCloseButton()}

                {this.props.titleText}

              {this.props.subHeader}

              {this.getModalContent(useScrollbar, innerHeight)}

          {this.getFooter()}

    );
  }

  render() {
    return (
      <CSSTransitionGroup transitionAppear={true} transitionName="modal" component="section">
        {this.getModal()}
      </CSSTransitionGroup>
    );
  }
}

ModalPortal.propTypes = {
  closeByBackdropClick: React.PropTypes.bool,
  footer: React.PropTypes.object,
  maxHeightPercentage: React.PropTypes.number,
  onClose: React.PropTypes.func,
  open: React.PropTypes.bool,
  showCloseButton: React.PropTypes.bool,
  showFooter: React.PropTypes.bool,
  subHeader: React.PropTypes.node,
  titleText: React.PropTypes.string,

  // Classes
  backdropClass: React.PropTypes.string,
  bodyClass: React.PropTypes.string,
  closeButtonClass: React.PropTypes.string,
  closeIconClass: React.PropTypes.string,
  closeTitleClass: React.PropTypes.string,
  containerClass: React.PropTypes.string,
  footerClass: React.PropTypes.string,
  footerContainerClass: React.PropTypes.string,
  headerClass: React.PropTypes.string,
  headerContainerClass: React.PropTypes.string,
  innerBodyClass: React.PropTypes.string,
  modalClass: React.PropTypes.string,
  titleClass: React.PropTypes.string
};
<|endoftext|>


<commit_before>var ReservedDataType = { Update: 'u', Remove: 'r' };

class NetworkConnection {

  constructor(networkEntities) {
    this.entities = networkEntities;
    this.setupDefaultDataSubscriptions();

    this.connectedClients = {};
    this.activeDataChannels = {};
  }

  setNetworkAdapter(adapter) {
    this.adapter = adapter;
  }

  setupDefaultDataSubscriptions() {
    this.dataChannelSubs = {};

    this.dataChannelSubs[ReservedDataType.Update]
        = this.entities.updateEntity.bind(this.entities);

    this.dataChannelSubs[ReservedDataType.Remove]
        = this.entities.removeRemoteEntity.bind(this.entities);
  }

  connect(serverUrl, appName, roomName, enableAudio = false) {
    NAF.app = appName;
    NAF.room = roomName;

    this.adapter.setServerUrl(serverUrl);
    this.adapter.setApp(appName);
    this.adapter.setRoom(roomName);

    var webrtcOptions = {
      audio: enableAudio,
      video: false,
      datachannel: true
    };
    this.adapter.setWebRtcOptions(webrtcOptions);

    this.adapter.setServerConnectListeners(
      this.connectSuccess.bind(this),
      this.connectFailure.bind(this)
    );
    this.adapter.setDataChannelListeners(
      this.dataChannelOpen.bind(this),
      this.dataChannelClosed.bind(this),
      this.receivedData.bind(this)
    );
    this.adapter.setRoomOccupantListener(this.occupantsReceived.bind(this));

    this.adapter.connect();
  }

  onConnect(callback) {
    this.onConnectCallback = callback;

    if (this.isConnected()) {
      callback();
    } else {
      document.body.addEventListener('connected', callback, false);
    }
  }

  connectSuccess(clientId) {
    NAF.log.write('Networked-Aframe Client ID:', clientId);
    NAF.clientId = clientId;

    var evt = new CustomEvent('connected', {'detail': { clientId: clientId }});
    document.body.dispatchEvent(evt);
  }

  connectFailure(errorCode, message) {
    NAF.log.error(errorCode, "failure to connect");
  }

  occupantsReceived(occupantList) {
    this.checkForDisconnectingClients(this.connectedClients, occupantList);
    this.connectedClients = occupantList;
    this.checkForConnectingClients(occupantList);
  }

  checkForDisconnectingClients(oldOccupantList, newOccupantList) {
    for (var id in oldOccupantList) {
      var clientFound = newOccupantList.hasOwnProperty(id);
      if (!clientFound) {
        NAF.log.write('Closing stream to ', id);
        this.adapter.closeStreamConnection(id);
      }
    }
  }

  // Some adapters will handle this internally
  checkForConnectingClients(occupantList) {
    for (var id in occupantList) {
      var startConnection = this.isNewClient(id) && this.adapter.shouldStartConnectionTo(occupantList[id]);
      if (startConnection) {
        NAF.log.write('Opening datachannel to ', id);
        this.adapter.startStreamConnection(id);
      }
    }
  }

  getConnectedClients() {
    return this.connectedClients;
  }

  isConnected() {
    return !!NAF.clientId;
  }

  isMineAndConnected(clientId) {
    return this.isConnected() && NAF.clientId === clientId;
  }

  isNewClient(clientId) {
    return !this.isConnectedTo(clientId);
  }

  isConnectedTo(clientId) {
    return this.adapter.getConnectStatus(clientId) === NAF.adapters.IS_CONNECTED;
  }

  dataChannelOpen(clientId) {
    NAF.log.write('Opened data channel from ' + clientId);
    this.activeDataChannels[clientId] = true;
    this.entities.completeSync(clientId);

    var evt = new CustomEvent('clientConnected', {detail: {clientId: clientId}});
    document.body.dispatchEvent(evt);
  }

  dataChannelClosed(clientId) {
    NAF.log.write('Closed data channel from ' + clientId);
    this.activeDataChannels[clientId] = false;
    this.entities.removeEntitiesOfClient(clientId);

    var evt = new CustomEvent('clientDisconnected', {detail: {clientId: clientId}});
    document.body.dispatchEvent(evt);
  }

  hasActiveDataChannel(clientId) {
    return this.activeDataChannels.hasOwnProperty(clientId) && this.activeDataChannels[clientId];
  }

  broadcastData(dataType, data) {
    this.adapter.broadcastData(dataType, data);
  }

  broadcastDataGuaranteed(dataType, data) {
    this.adapter.broadcastDataGuaranteed(dataType, data);
  }

  sendData(toClientId, dataType, data, guaranteed) {
    if (this.hasActiveDataChannel(toClientId)) {
      if (guaranteed) {
        this.adapter.sendDataGuaranteed(toClientId, dataType, data);
      } else {
        this.adapter.sendData(toClientId, dataType, data);
      }
    } else {
      // console.error("NOT-CONNECTED", "not connected to " + toClient);
    }
  }

  sendDataGuaranteed(toClientId, dataType, data) {
    this.sendData(toClientId, dataType, data, true);
  }

  subscribeToDataChannel(dataType, callback) {
    if (this.isReservedDataType(dataType)) {
      NAF.log.error('NetworkConnection@subscribeToDataChannel: ' + dataType + ' is a reserved dataType. Choose another');
      return;
    }
    this.dataChannelSubs[dataType] = callback;
  }

  unsubscribeToDataChannel(dataType) {
    if (this.isReservedDataType(dataType)) {
      NAF.log.error('NetworkConnection@unsubscribeToDataChannel: ' + dataType + ' is a reserved dataType. Choose another');
      return;
    }
    delete this.dataChannelSubs[dataType];
  }

  isReservedDataType(dataType) {
    return dataType == ReservedDataType.Update
        || dataType == ReservedDataType.Remove;
  }

  receivedData(fromClientId, dataType, data) {
    if (this.dataChannelSubs.hasOwnProperty(dataType)) {
      this.dataChannelSubs[dataType](fromClientId, dataType, data);
    } else {
      NAF.log.error('NetworkConnection@receivedData: ' + dataType + ' has not been subscribed to yet. Call subscribeToDataChannel()');
    }
  }

  disconnect() {
    this.entities.removeRemoteEntities();
    this.adapter.disconnect();

    NAF.app = '';
    NAF.room = '';
    NAF.clientId = '';
    this.connectedClients = {};
    this.activeDataChannels = {};
    this.adapter = null;

    this.setupDefaultDataSubscriptions();

    document.body.removeEventListener('connected', this.onConnectCallback);
  }
}

module.exports = NetworkConnection;<commit_msg>Update connectedClients list before sending clientDisconnected event<commit_after>var ReservedDataType = { Update: 'u', Remove: 'r' };

class NetworkConnection {

  constructor(networkEntities) {
    this.entities = networkEntities;
    this.setupDefaultDataSubscriptions();

    this.connectedClients = {};
    this.activeDataChannels = {};
  }

  setNetworkAdapter(adapter) {
    this.adapter = adapter;
  }

  setupDefaultDataSubscriptions() {
    this.dataChannelSubs = {};

    this.dataChannelSubs[ReservedDataType.Update]
        = this.entities.updateEntity.bind(this.entities);

    this.dataChannelSubs[ReservedDataType.Remove]
        = this.entities.removeRemoteEntity.bind(this.entities);
  }

  connect(serverUrl, appName, roomName, enableAudio = false) {
    NAF.app = appName;
    NAF.room = roomName;

    this.adapter.setServerUrl(serverUrl);
    this.adapter.setApp(appName);
    this.adapter.setRoom(roomName);

    var webrtcOptions = {
      audio: enableAudio,
      video: false,
      datachannel: true
    };
    this.adapter.setWebRtcOptions(webrtcOptions);

    this.adapter.setServerConnectListeners(
      this.connectSuccess.bind(this),
      this.connectFailure.bind(this)
    );
    this.adapter.setDataChannelListeners(
      this.dataChannelOpen.bind(this),
      this.dataChannelClosed.bind(this),
      this.receivedData.bind(this)
    );
    this.adapter.setRoomOccupantListener(this.occupantsReceived.bind(this));

    this.adapter.connect();
  }

  onConnect(callback) {
    this.onConnectCallback = callback;

    if (this.isConnected()) {
      callback();
    } else {
      document.body.addEventListener('connected', callback, false);
    }
  }

  connectSuccess(clientId) {
    NAF.log.write('Networked-Aframe Client ID:', clientId);
    NAF.clientId = clientId;

    var evt = new CustomEvent('connected', {'detail': { clientId: clientId }});
    document.body.dispatchEvent(evt);
  }

  connectFailure(errorCode, message) {
    NAF.log.error(errorCode, "failure to connect");
  }

  occupantsReceived(occupantList) {
    var prevConnectedClients = Object.assign({}, this.connectedClients);
    this.connectedClients = occupantList;
    this.checkForDisconnectingClients(prevConnectedClients, occupantList);
    this.checkForConnectingClients(occupantList);
  }

  checkForDisconnectingClients(oldOccupantList, newOccupantList) {
    for (var id in oldOccupantList) {
      var clientFound = newOccupantList.hasOwnProperty(id);
      if (!clientFound) {
        NAF.log.write('Closing stream to ', id);
        this.adapter.closeStreamConnection(id);
      }
    }
  }

  // Some adapters will handle this internally
  checkForConnectingClients(occupantList) {
    for (var id in occupantList) {
      var startConnection = this.isNewClient(id) && this.adapter.shouldStartConnectionTo(occupantList[id]);
      if (startConnection) {
        NAF.log.write('Opening datachannel to ', id);
        this.adapter.startStreamConnection(id);
      }
    }
  }

  getConnectedClients() {
    return this.connectedClients;
  }

  isConnected() {
    return !!NAF.clientId;
  }

  isMineAndConnected(clientId) {
    return this.isConnected() && NAF.clientId === clientId;
  }

  isNewClient(clientId) {
    return !this.isConnectedTo(clientId);
  }

  isConnectedTo(clientId) {
    return this.adapter.getConnectStatus(clientId) === NAF.adapters.IS_CONNECTED;
  }

  dataChannelOpen(clientId) {
    NAF.log.write('Opened data channel from ' + clientId);
    this.activeDataChannels[clientId] = true;
    this.entities.completeSync(clientId);

    var evt = new CustomEvent('clientConnected', {detail: {clientId: clientId}});
    document.body.dispatchEvent(evt);
  }

  dataChannelClosed(clientId) {
    NAF.log.write('Closed data channel from ' + clientId);
    this.activeDataChannels[clientId] = false;
    this.entities.removeEntitiesOfClient(clientId);

    var evt = new CustomEvent('clientDisconnected', {detail: {clientId: clientId}});
    document.body.dispatchEvent(evt);
  }

  hasActiveDataChannel(clientId) {
    return this.activeDataChannels.hasOwnProperty(clientId) && this.activeDataChannels[clientId];
  }

  broadcastData(dataType, data) {
    this.adapter.broadcastData(dataType, data);
  }

  broadcastDataGuaranteed(dataType, data) {
    this.adapter.broadcastDataGuaranteed(dataType, data);
  }

  sendData(toClientId, dataType, data, guaranteed) {
    if (this.hasActiveDataChannel(toClientId)) {
      if (guaranteed) {
        this.adapter.sendDataGuaranteed(toClientId, dataType, data);
      } else {
        this.adapter.sendData(toClientId, dataType, data);
      }
    } else {
      // console.error("NOT-CONNECTED", "not connected to " + toClient);
    }
  }

  sendDataGuaranteed(toClientId, dataType, data) {
    this.sendData(toClientId, dataType, data, true);
  }

  subscribeToDataChannel(dataType, callback) {
    if (this.isReservedDataType(dataType)) {
      NAF.log.error('NetworkConnection@subscribeToDataChannel: ' + dataType + ' is a reserved dataType. Choose another');
      return;
    }
    this.dataChannelSubs[dataType] = callback;
  }

  unsubscribeToDataChannel(dataType) {
    if (this.isReservedDataType(dataType)) {
      NAF.log.error('NetworkConnection@unsubscribeToDataChannel: ' + dataType + ' is a reserved dataType. Choose another');
      return;
    }
    delete this.dataChannelSubs[dataType];
  }

  isReservedDataType(dataType) {
    return dataType == ReservedDataType.Update
        || dataType == ReservedDataType.Remove;
  }

  receivedData(fromClientId, dataType, data) {
    if (this.dataChannelSubs.hasOwnProperty(dataType)) {
      this.dataChannelSubs[dataType](fromClientId, dataType, data);
    } else {
      NAF.log.error('NetworkConnection@receivedData: ' + dataType + ' has not been subscribed to yet. Call subscribeToDataChannel()');
    }
  }

  disconnect() {
    this.entities.removeRemoteEntities();
    this.adapter.disconnect();

    NAF.app = '';
    NAF.room = '';
    NAF.clientId = '';
    this.connectedClients = {};
    this.activeDataChannels = {};
    this.adapter = null;

    this.setupDefaultDataSubscriptions();

    document.body.removeEventListener('connected', this.onConnectCallback);
  }
}

module.exports = NetworkConnection;<|endoftext|>


<commit_before>function unzip(list) {
  let vendors = ['-moz-', '-ms-', '-o-', '-webkit-', ''];
  let unzipped = [];
  let i;

  list.forEach(function (a) {
    if (a.indexOf('-prefix-') !== -1) {
      [].push.apply(unzipped, vendors.map(p => a.replace('-prefix-', p)));
    } else {
      unzipped.push(a);
    }
  });

  return unzipped;
}

let PROPERTIES_LIST = unzip([
  'all',
  'z-index',
  'content',
  'icon',
  'display',
  '*display',
  '-prefix-device-pixel-ratio',
  '-prefix-box-sizing',
  '-prefix-box-align',
  '-prefix-box-direction',
  '-prefix-box-orient',
  '-prefix-box-pack',
  'visibility',
  '-prefix-appearance',
  'zoom',
  'position',
  'top',
  'right',
  'bottom',
  'left',
  'width',
  'min-width',
  'max-width',
  'height',
  'min-height',
  'max-height',
  'float',
  'clear',
  '-prefix-clip',
  '-prefix-clip-path',
  'margin',
  'margin-top',
  'margin-right',
  'margin-bottom',
  'margin-left',
  '-prefix-margin-before',
  '-prefix-margin-after',
  '-prefix-margin-end',
  'padding',
  'padding-top',
  'padding-right',
  'padding-bottom',
  'padding-left',
  'color',
  'opacity',
  'overflow',
  'overflow-x',
  'overflow-y',
  'overflow-wrap',
  'border',
  'border-color',
  'border-image',
  'border-image-source',
  'border-image-repeat',
  'border-image-width',
  'border-image-outset',
  'border-image-slice',
  'border-top',
  'border-right',
  'border-bottom',
  'border-left',
  'border-top-color',
  'border-right-color',
  'border-bottom-color',
  'border-left-color',
  'border-width',
  'border-style',
  '-prefix-border-radius',
  '-prefix-border-top-left-radius',
  '-prefix-border-top-right-radius',
  '-prefix-border-bottom-right-radius',
  '-prefix-border-bottom-left-radius',
  'border-top-width',
  'border-top-style',
  'border-right-width',
  'border-right-style',
  'border-bottom-width',
  'border-bottom-style',
  'border-left-width',
  'border-left-style',
  'border-collapse',
  'border-spacing',
  'background',
  'background-image',
  'background-color',
  '-prefix-background-blend-mode',
  'background-repeat',
  'background-attachment',
  'background-origin',
  'background-position',
  'background-size',
  'background-clip',
  'isolation',
  'fill',
  'stroke',
  'stroke-miterlimit',
  'filter',
  'outline',
  'outline-color',
  'outline-offset',
  'outline-style',
  'outline-width',
  'caption-side',
  'empty-cells',
  'table-layout',
  '-prefix-box-shadow',
  'box-decoration-break',
  'text-align',
  'text-decoration',
  'text-decoration-color',
  'text-decoration-line',
  'text-decoration-style',
  'text-underline-position',
  'text-transform',
  'text-shadow',
  'text-align-last',
  'text-combine-upright',
  'text-indent',
  'text-justify',
  'text-orientation',
  'text-combine-upright',
  'text-overflow',
  '-prefix-text-size-adjust',
  'vertical-align',
  '-prefix-align-content',
  '-prefix-align-items',
  '-prefix-align-self',
  '@font-feature-values',
  'font-feature-settings',
  'font',
  'font-family',
  'font-display',
  'font-size',
  'font-size-adjust',
  'font-style',
  'font-weight',
  'font-kerning',
  'font-stretch',
  '-prefix-font-smoothing',
  '-prefix-osx-font-smoothing',
  'font-synthesis',
  'font-variant',
  'font-variant-alternates',
  'font-variant-caps',
  'font-variant-east-asian',
  'font-variant-ligatures',
  'font-variant-numeric',
  'font-variant-position',
  'font-language-override',
  'src',
  'flex',
  'flex-basis',
  '-prefix-flex-align',
  '-prefix-flex-direction',
  '-prefix-flex-line-pack',
  '-prefix-flex-pack',
  'flex-flow',
  'flex-grow',
  'flex-shrink',
  '-prefix-flex-wrap',
  '-prefix-justify-content',
  'order',
  'hanging-punctuation',
  'line-break',
  'hyphens',
  'line-height',
  'letter-spacing',
  'tab-size',
  'white-space',
  'word-break',
  'word-spacing',
  'word-wrap',
  'direction',
  'unicode-bidi',
  'writing-mode',
  'list-style',
  'list-style-image',
  'list-style-position',
  'list-style-type',
  'counter-increment',
  'counter-reset',
  '@-prefix-keyframes',
  '-prefix-animation',
  '-prefix-animation-delay',
  '-prefix-animation-direction',
  '-prefix-animation-duration',
  '-prefix-animation-fill-mode',
  '-prefix-animation-iteration-count',
  '-prefix-animation-name',
  '-prefix-animation-play-state',
  '-prefix-animation-timing-function',
  'backface-visibility',
  'perspective',
  'perspective-origin',
  '-prefix-transform',
  '-prefix-transform-origin',
  '-prefix-transform-style',
  '-prefix-transition',
  '-prefix-transition-property',
  '-prefix-transition-duration',
  '-prefix-transition-timing-function',
  '-prefix-transition-delay',
  '-prefix-interpolation-mode',
  'ime-mode',
  'Multi-column',
  'break-after',
  'break-before',
  'break-inside',
  '-prefix-column-count',
  '-prefix-column-fill',
  '-prefix-column-gap',
  '-prefix-column-rule',
  '-prefix-column-rule-color',
  '-prefix-column-rule-style',
  '-prefix-column-rule-width',
  '-prefix-column-span',
  '-prefix-column-width',
  '-prefix-columns',
  'widows',
  'orphans',
  'page-break-after',
  'page-break-before',
  'page-break-inside',
  'marks',
  'quotes',
  'image-orientation',
  'image-rendering',
  'image-resolution',
  'object-fit',
  'object-position',
  'mask',
  'mask-type',
  'mark',
  'mark-after',
  'mark-before',
  'phonemes',
  'rest',
  'rest-after',
  'rest-before',
  'voice-balance',
  'voice-duration',
  'voice-pitch',
  'voice-pitch-range',
  'voice-rate',
  'voice-stress',
  'voice-volume',
  'marquee-direction',
  'marquee-play-count',
  'marquee-speed',
  'marquee-style',
  'nav-index',
  'nav-up',
  'nav-right',
  'nav-down',
  'nav-left',
  'resize',
  '-prefix-touch-action',
  '-prefix-tap-highlight-color',
  '-prefix-user-select',
  '-prefix-pointer-events',
  'cursor',
]);

module.exports = PROPERTIES_LIST;
<commit_msg>Added missing svg properties<commit_after>function unzip(list) {
  let vendors = ['-moz-', '-ms-', '-o-', '-webkit-', ''];
  let unzipped = [];
  let i;

  list.forEach(function (a) {
    if (a.indexOf('-prefix-') !== -1) {
      [].push.apply(unzipped, vendors.map(p => a.replace('-prefix-', p)));
    } else {
      unzipped.push(a);
    }
  });

  return unzipped;
}

let PROPERTIES_LIST = unzip([
  'all',
  'z-index',
  'content',
  'icon',
  'display',
  '*display',
  '-prefix-device-pixel-ratio',
  '-prefix-box-sizing',
  '-prefix-box-align',
  '-prefix-box-direction',
  '-prefix-box-orient',
  '-prefix-box-pack',
  'visibility',
  '-prefix-appearance',
  'zoom',
  'position',
  'top',
  'right',
  'bottom',
  'left',
  'width',
  'min-width',
  'max-width',
  'height',
  'min-height',
  'max-height',
  'float',
  'clear',
  '-prefix-clip',
  '-prefix-clip-path',
  'margin',
  'margin-top',
  'margin-right',
  'margin-bottom',
  'margin-left',
  '-prefix-margin-before',
  '-prefix-margin-after',
  '-prefix-margin-end',
  'padding',
  'padding-top',
  'padding-right',
  'padding-bottom',
  'padding-left',
  'color',
  'opacity',
  'overflow',
  'overflow-x',
  'overflow-y',
  'overflow-wrap',
  'border',
  'border-color',
  'border-image',
  'border-image-source',
  'border-image-repeat',
  'border-image-width',
  'border-image-outset',
  'border-image-slice',
  'border-top',
  'border-right',
  'border-bottom',
  'border-left',
  'border-top-color',
  'border-right-color',
  'border-bottom-color',
  'border-left-color',
  'border-width',
  'border-style',
  '-prefix-border-radius',
  '-prefix-border-top-left-radius',
  '-prefix-border-top-right-radius',
  '-prefix-border-bottom-right-radius',
  '-prefix-border-bottom-left-radius',
  'border-top-width',
  'border-top-style',
  'border-right-width',
  'border-right-style',
  'border-bottom-width',
  'border-bottom-style',
  'border-left-width',
  'border-left-style',
  'border-collapse',
  'border-spacing',
  'background',
  'background-image',
  'background-color',
  '-prefix-background-blend-mode',
  'background-repeat',
  'background-attachment',
  'background-origin',
  'background-position',
  'background-size',
  'background-clip',
  'isolation',
  'fill',
  'stroke',
  'stroke-width',
  'stroke-dasharray',
  'stroke-dashoffset',
  'stroke-miterlimit',
  'filter',
  'outline',
  'outline-color',
  'outline-offset',
  'outline-style',
  'outline-width',
  'caption-side',
  'empty-cells',
  'table-layout',
  '-prefix-box-shadow',
  'box-decoration-break',
  'text-align',
  'text-decoration',
  'text-decoration-color',
  'text-decoration-line',
  'text-decoration-style',
  'text-underline-position',
  'text-transform',
  'text-shadow',
  'text-align-last',
  'text-combine-upright',
  'text-indent',
  'text-justify',
  'text-orientation',
  'text-combine-upright',
  'text-overflow',
  '-prefix-text-size-adjust',
  'vertical-align',
  '-prefix-align-content',
  '-prefix-align-items',
  '-prefix-align-self',
  '@font-feature-values',
  'font-feature-settings',
  'font',
  'font-family',
  'font-display',
  'font-size',
  'font-size-adjust',
  'font-style',
  'font-weight',
  'font-kerning',
  'font-stretch',
  '-prefix-font-smoothing',
  '-prefix-osx-font-smoothing',
  'font-synthesis',
  'font-variant',
  'font-variant-alternates',
  'font-variant-caps',
  'font-variant-east-asian',
  'font-variant-ligatures',
  'font-variant-numeric',
  'font-variant-position',
  'font-language-override',
  'src',
  'flex',
  'flex-basis',
  '-prefix-flex-align',
  '-prefix-flex-direction',
  '-prefix-flex-line-pack',
  '-prefix-flex-pack',
  'flex-flow',
  'flex-grow',
  'flex-shrink',
  '-prefix-flex-wrap',
  '-prefix-justify-content',
  'order',
  'hanging-punctuation',
  'line-break',
  'hyphens',
  'line-height',
  'letter-spacing',
  'tab-size',
  'white-space',
  'word-break',
  'word-spacing',
  'word-wrap',
  'direction',
  'unicode-bidi',
  'writing-mode',
  'list-style',
  'list-style-image',
  'list-style-position',
  'list-style-type',
  'counter-increment',
  'counter-reset',
  '@-prefix-keyframes',
  '-prefix-animation',
  '-prefix-animation-delay',
  '-prefix-animation-direction',
  '-prefix-animation-duration',
  '-prefix-animation-fill-mode',
  '-prefix-animation-iteration-count',
  '-prefix-animation-name',
  '-prefix-animation-play-state',
  '-prefix-animation-timing-function',
  'backface-visibility',
  'perspective',
  'perspective-origin',
  '-prefix-transform',
  '-prefix-transform-origin',
  '-prefix-transform-style',
  '-prefix-transition',
  '-prefix-transition-property',
  '-prefix-transition-duration',
  '-prefix-transition-timing-function',
  '-prefix-transition-delay',
  '-prefix-interpolation-mode',
  'ime-mode',
  'Multi-column',
  'break-after',
  'break-before',
  'break-inside',
  '-prefix-column-count',
  '-prefix-column-fill',
  '-prefix-column-gap',
  '-prefix-column-rule',
  '-prefix-column-rule-color',
  '-prefix-column-rule-style',
  '-prefix-column-rule-width',
  '-prefix-column-span',
  '-prefix-column-width',
  '-prefix-columns',
  'widows',
  'orphans',
  'page-break-after',
  'page-break-before',
  'page-break-inside',
  'marks',
  'quotes',
  'image-orientation',
  'image-rendering',
  'image-resolution',
  'object-fit',
  'object-position',
  'mask',
  'mask-type',
  'mark',
  'mark-after',
  'mark-before',
  'phonemes',
  'rest',
  'rest-after',
  'rest-before',
  'voice-balance',
  'voice-duration',
  'voice-pitch',
  'voice-pitch-range',
  'voice-rate',
  'voice-stress',
  'voice-volume',
  'marquee-direction',
  'marquee-play-count',
  'marquee-speed',
  'marquee-style',
  'nav-index',
  'nav-up',
  'nav-right',
  'nav-down',
  'nav-left',
  'resize',
  '-prefix-touch-action',
  '-prefix-tap-highlight-color',
  '-prefix-user-select',
  '-prefix-pointer-events',
  'cursor',
]);

module.exports = PROPERTIES_LIST;
<|endoftext|>