<commit_before>/*
 * Copyright (c) 2011-2013 Lp digital system
 *
 * This file is part of BackBee.
 *
 * BackBee is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * BackBee is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with BackBee. If not, see <http://www.gnu.org/licenses/>.
 */

define(['page.abstract.form', 'jquery', 'jsclass'], function (Form, jQuery) {
    'use strict';

    var PageForm = new JS.Class(Form, {

        new: function (moveTo) {

            this.clear();

            var dfd = jQuery.Deferred(),
                config = {
                    elements: {
                        title: this.form.title,
                        alttitle: this.form.alttitle,
                        target: this.form.target,
                        redirect: this.form.redirect,
                        layout_uid: this.form.layout_uid
                    },
                    form_name: 'new-page'
                };

            if (true === moveTo) {
                config.elements.move_to = this.form.move_to;
            }

            this.getLayoutsObject().done(function (layoutObject) {
                config.elements.layout_uid = layoutObject;
                dfd.resolve(config);
            });

            return dfd.promise();
        },

        edit: function (page_uid, moveTo) {
            var dfd = jQuery.Deferred(),
                config = {
                    elements: {
                        title: this.form.title,
                        alttitle: this.form.alttitle,
                        url: this.form.url,
                        target: this.form.target,
                        redirect: this.form.redirect,
                        layout_uid: this.form.layout_uid,
                        state: this.form.state
                    },
                    form_name: 'edit-page',
                    page_uid: page_uid
                },
                self = this;

            if (true === moveTo) {
                config.elements.move_to = this.form.move_to;
            }

            this.getPage(page_uid).done(function (page) {
                self.getLayoutsObject().done(function (layoutObject) {
                    config.elements.layout_uid = layoutObject;
                    self.map(page, config);
                    dfd.resolve(config);
                });
            });

            return dfd.promise();
        },

        clone: function (page_uid) {
            var dfd = jQuery.Deferred(),
                config = {
                    elements: {
                        title: this.form.title
                    }
                },
                self = this;

            this.getPage(page_uid).done(function (page) {
                self.map(page, config);
                dfd.resolve(config);
            });

            return dfd.promise();
        }

    });

    return new JS.Singleton(PageForm);
});
<commit_msg>create page url<commit_after>/*
 * Copyright (c) 2011-2013 Lp digital system
 *
 * This file is part of BackBee.
 *
 * BackBee is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * BackBee is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with BackBee. If not, see <http://www.gnu.org/licenses/>.
 */

define(['page.abstract.form', 'jquery', 'jsclass'], function (Form, jQuery) {
    'use strict';

    var PageForm = new JS.Class(Form, {

        new: function (moveTo) {

            this.clear();

            var dfd = jQuery.Deferred(),
                config = {
                    elements: {
                        title: this.form.title,
                        alttitle: this.form.alttitle,
                        url: this.form.url,
                        target: this.form.target,
                        redirect: this.form.redirect,
                        layout_uid: this.form.layout_uid
                    },
                    form_name: 'new-page'
                };

            if (true === moveTo) {
                config.elements.move_to = this.form.move_to;
            }

            this.getLayoutsObject().done(function (layoutObject) {
                config.elements.layout_uid = layoutObject;
                dfd.resolve(config);
            });

            return dfd.promise();
        },

        edit: function (page_uid, moveTo) {
            var dfd = jQuery.Deferred(),
                config = {
                    elements: {
                        title: this.form.title,
                        alttitle: this.form.alttitle,
                        url: this.form.url,
                        target: this.form.target,
                        redirect: this.form.redirect,
                        layout_uid: this.form.layout_uid,
                        state: this.form.state
                    },
                    form_name: 'edit-page',
                    page_uid: page_uid
                },
                self = this;

            if (true === moveTo) {
                config.elements.move_to = this.form.move_to;
            }

            this.getPage(page_uid).done(function (page) {
                self.getLayoutsObject().done(function (layoutObject) {
                    config.elements.layout_uid = layoutObject;
                    self.map(page, config);
                    dfd.resolve(config);
                });
            });

            return dfd.promise();
        },

        clone: function (page_uid) {
            var dfd = jQuery.Deferred(),
                config = {
                    elements: {
                        title: this.form.title
                    }
                },
                self = this;

            this.getPage(page_uid).done(function (page) {
                self.map(page, config);
                dfd.resolve(config);
            });

            return dfd.promise();
        }

    });

    return new JS.Singleton(PageForm);
});
<|endoftext|>


<commit_before>;(function(){ 'use strict';

  // registers the extension on a cytoscape lib ref
  var register = function( cytoscape, dagre ){
    if( !cytoscape || !dagre ){ return; } // can't register if cytoscape unspecified

    var isFunction = function(o){ return typeof o === 'function'; };

    // default layout options
    var defaults = {
      // dagre algo options, uses default value on undefined
      nodeSep: undefined, // the separation between adjacent nodes in the same rank
      edgeSep: undefined, // the separation between adjacent edges in the same rank
      rankSep: undefined, // the separation between adjacent nodes in the same rank
      rankDir: undefined, // 'TB' for top to bottom flow, 'LR' for left to right
      minLen: function( edge ){ return 1; }, // number of ranks to keep between the source and target of the edge
      edgeWeight: function( edge ){ return 1; }, // higher weight edges are generally made shorter and straighter than lower weight edges

      // general layout options
      fit: true, // whether to fit to viewport
      padding: 30, // fit padding
      animate: false, // whether to transition the node positions
      animationDuration: 500, // duration of animation in ms if enabled
      animationEasing: undefined, // easing of animation if enabled
      boundingBox: undefined, // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h }
      ready: function(){}, // on layoutready
      stop: function(){} // on layoutstop
    };

    // constructor
    // options : object containing layout options
    function DagreLayout( options ){
      var opts = this.options = {};
      for( var i in defaults ){ opts[i] = defaults[i]; }
      for( var i in options ){ opts[i] = options[i]; }
    }

    // runs the layout
    DagreLayout.prototype.run = function(){
      var options = this.options;
      var layout = this;

      var cy = options.cy; // cy is automatically populated for us in the constructor
      var eles = options.eles;

      var getVal = function( ele, val ){
        return isFunction(val) ? val.apply( ele, [ ele ] ) : val;
      };

      var bb = options.boundingBox || { x1: 0, y1: 0, w: cy.width(), h: cy.height() };
      if( bb.x2 === undefined ){ bb.x2 = bb.x1 + bb.w; }
      if( bb.w === undefined ){ bb.w = bb.x2 - bb.x1; }
      if( bb.y2 === undefined ){ bb.y2 = bb.y1 + bb.h; }
      if( bb.h === undefined ){ bb.h = bb.y2 - bb.y1; }

      var g = new dagre.graphlib.Graph({
        multigraph: true,
        compound: true
      });

      var gObj = {};
      var setGObj = function( name, val ){
        if( val != null ){
          gObj[ name ] = val;
        }
      };

      setGObj( 'nodesep', options.nodeSep );
      setGObj( 'edgesep', options.edgeSep );
      setGObj( 'ranksep', options.rankSep );
      setGObj( 'rankdir', options.rankDir );

      g.setGraph( gObj );

      g.setDefaultEdgeLabel(function() { return {}; });
      g.setDefaultNodeLabel(function() { return {}; });

      // add nodes to dagre
      var nodes = eles.nodes();
      for( var i = 0; i < nodes.length; i++ ){
        var node = nodes[i];
        var nbb = node.boundingBox();

        g.setNode( node.id(), {
          width: nbb.w,
          height: nbb.h,
          name: node.id()
        } );

        // console.log( g.node(node.id()) );
      }

      // set compound parents
      for( var i = 0; i < nodes.length; i++ ){
        var node = nodes[i];

        if( node.isChild() ){
          g.setParent( node.id(), node.parent().id() );
        }
      }

      // add edges to dagre
      var edges = eles.edges().stdFilter(function( edge ){
        return !edge.source().isParent() && !edge.target().isParent(); // dagre can't handle edges on compound nodes
      });
      for( var i = 0; i < edges.length; i++ ){
        var edge = edges[i];

        g.setEdge( edge.source().id(), edge.target().id(), {
          minlen: getVal( edge, options.minLen ),
          weight: getVal( edge, options.edgeWeight ),
          name: edge.id()
        }, edge.id() );

        // console.log( g.edge(edge.source().id(), edge.target().id(), edge.id()) );
      }

      dagre.layout( g );

      var gNodeIds = g.nodes();
      for( var i = 0; i < gNodeIds.length; i++ ){
        var id = gNodeIds[i];
        var n = g.node( id );

        cy.getElementById(id).scratch().dagre = n;
      }

      var dagreBB;

      if( options.boundingBox ){
        dagreBB = { x1: Infinity, x2: -Infinity, y1: Infinity, y2: -Infinity };
        nodes.forEach(function( node ){
          var dModel = node.scratch().dagre;

          dagreBB.x1 = Math.min( dagreBB.x1, dModel.x );
          dagreBB.x2 = Math.max( dagreBB.x2, dModel.x );

          dagreBB.y1 = Math.min( dagreBB.y1, dModel.y );
          dagreBB.y2 = Math.max( dagreBB.y2, dModel.y );
        });

        dagreBB.w = dagreBB.x2 - dagreBB.x1;
        dagreBB.h = dagreBB.y2 - dagreBB.y1;
      } else {
        dagreBB = bb;
      }

      var constrainPos = function( p ){
        if( options.boundingBox ){
          var xPct = (p.x - dagreBB.x1) / dagreBB.w;
          var yPct = (p.y - dagreBB.y1) / dagreBB.h;

          return {
            x: bb.x1 + xPct * bb.w,
            y: bb.y1 + yPct * bb.h
          };
        } else {
          return p;
        }
      };

      nodes.layoutPositions(layout, options, function(){
        var dModel = this.scratch().dagre;

        return constrainPos({
          x: dModel.x,
          y: dModel.y
        });
      });

      return this; // chaining
    };

    cytoscape('layout', 'dagre', DagreLayout);

  };

  if( typeof module !== 'undefined' && module.exports ){ // expose as a commonjs module
    module.exports = register;
  }

  if( typeof define !== 'undefined' && define.amd ){ // expose as an amd/requirejs module
    define('cytoscape-dagre', function(){
      return register;
    });
  }

  if( typeof cytoscape !== 'undefined' && typeof dagre !== 'undefined' ){ // expose to global cytoscape (i.e. window.cytoscape)
    register( cytoscape, dagre );
  }

})();
<commit_msg>Avoid division by zero<commit_after>;(function(){ 'use strict';

  // registers the extension on a cytoscape lib ref
  var register = function( cytoscape, dagre ){
    if( !cytoscape || !dagre ){ return; } // can't register if cytoscape unspecified

    var isFunction = function(o){ return typeof o === 'function'; };

    // default layout options
    var defaults = {
      // dagre algo options, uses default value on undefined
      nodeSep: undefined, // the separation between adjacent nodes in the same rank
      edgeSep: undefined, // the separation between adjacent edges in the same rank
      rankSep: undefined, // the separation between adjacent nodes in the same rank
      rankDir: undefined, // 'TB' for top to bottom flow, 'LR' for left to right
      minLen: function( edge ){ return 1; }, // number of ranks to keep between the source and target of the edge
      edgeWeight: function( edge ){ return 1; }, // higher weight edges are generally made shorter and straighter than lower weight edges

      // general layout options
      fit: true, // whether to fit to viewport
      padding: 30, // fit padding
      animate: false, // whether to transition the node positions
      animationDuration: 500, // duration of animation in ms if enabled
      animationEasing: undefined, // easing of animation if enabled
      boundingBox: undefined, // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h }
      ready: function(){}, // on layoutready
      stop: function(){} // on layoutstop
    };

    // constructor
    // options : object containing layout options
    function DagreLayout( options ){
      var opts = this.options = {};
      for( var i in defaults ){ opts[i] = defaults[i]; }
      for( var i in options ){ opts[i] = options[i]; }
    }

    // runs the layout
    DagreLayout.prototype.run = function(){
      var options = this.options;
      var layout = this;

      var cy = options.cy; // cy is automatically populated for us in the constructor
      var eles = options.eles;

      var getVal = function( ele, val ){
        return isFunction(val) ? val.apply( ele, [ ele ] ) : val;
      };

      var bb = options.boundingBox || { x1: 0, y1: 0, w: cy.width(), h: cy.height() };
      if( bb.x2 === undefined ){ bb.x2 = bb.x1 + bb.w; }
      if( bb.w === undefined ){ bb.w = bb.x2 - bb.x1; }
      if( bb.y2 === undefined ){ bb.y2 = bb.y1 + bb.h; }
      if( bb.h === undefined ){ bb.h = bb.y2 - bb.y1; }

      var g = new dagre.graphlib.Graph({
        multigraph: true,
        compound: true
      });

      var gObj = {};
      var setGObj = function( name, val ){
        if( val != null ){
          gObj[ name ] = val;
        }
      };

      setGObj( 'nodesep', options.nodeSep );
      setGObj( 'edgesep', options.edgeSep );
      setGObj( 'ranksep', options.rankSep );
      setGObj( 'rankdir', options.rankDir );

      g.setGraph( gObj );

      g.setDefaultEdgeLabel(function() { return {}; });
      g.setDefaultNodeLabel(function() { return {}; });

      // add nodes to dagre
      var nodes = eles.nodes();
      for( var i = 0; i < nodes.length; i++ ){
        var node = nodes[i];
        var nbb = node.boundingBox();

        g.setNode( node.id(), {
          width: nbb.w,
          height: nbb.h,
          name: node.id()
        } );

        // console.log( g.node(node.id()) );
      }

      // set compound parents
      for( var i = 0; i < nodes.length; i++ ){
        var node = nodes[i];

        if( node.isChild() ){
          g.setParent( node.id(), node.parent().id() );
        }
      }

      // add edges to dagre
      var edges = eles.edges().stdFilter(function( edge ){
        return !edge.source().isParent() && !edge.target().isParent(); // dagre can't handle edges on compound nodes
      });
      for( var i = 0; i < edges.length; i++ ){
        var edge = edges[i];

        g.setEdge( edge.source().id(), edge.target().id(), {
          minlen: getVal( edge, options.minLen ),
          weight: getVal( edge, options.edgeWeight ),
          name: edge.id()
        }, edge.id() );

        // console.log( g.edge(edge.source().id(), edge.target().id(), edge.id()) );
      }

      dagre.layout( g );

      var gNodeIds = g.nodes();
      for( var i = 0; i < gNodeIds.length; i++ ){
        var id = gNodeIds[i];
        var n = g.node( id );

        cy.getElementById(id).scratch().dagre = n;
      }

      var dagreBB;

      if( options.boundingBox ){
        dagreBB = { x1: Infinity, x2: -Infinity, y1: Infinity, y2: -Infinity };
        nodes.forEach(function( node ){
          var dModel = node.scratch().dagre;

          dagreBB.x1 = Math.min( dagreBB.x1, dModel.x );
          dagreBB.x2 = Math.max( dagreBB.x2, dModel.x );

          dagreBB.y1 = Math.min( dagreBB.y1, dModel.y );
          dagreBB.y2 = Math.max( dagreBB.y2, dModel.y );
        });

        dagreBB.w = dagreBB.x2 - dagreBB.x1;
        dagreBB.h = dagreBB.y2 - dagreBB.y1;
      } else {
        dagreBB = bb;
      }

      var constrainPos = function( p ){
        if( options.boundingBox ){
          var xPct = (p.x - dagreBB.x1) / dagreBB.w;
          var yPct = dagreBB.h === 0 ? 0 : (p.y - dagreBB.y1) / dagreBB.h;

          return {
            x: bb.x1 + xPct * bb.w,
            y: bb.y1 + yPct * bb.h
          };
        } else {
          return p;
        }
      };

      nodes.layoutPositions(layout, options, function(){
        var dModel = this.scratch().dagre;

        return constrainPos({
          x: dModel.x,
          y: dModel.y
        });
      });

      return this; // chaining
    };

    cytoscape('layout', 'dagre', DagreLayout);

  };

  if( typeof module !== 'undefined' && module.exports ){ // expose as a commonjs module
    module.exports = register;
  }

  if( typeof define !== 'undefined' && define.amd ){ // expose as an amd/requirejs module
    define('cytoscape-dagre', function(){
      return register;
    });
  }

  if( typeof cytoscape !== 'undefined' && typeof dagre !== 'undefined' ){ // expose to global cytoscape (i.e. window.cytoscape)
    register( cytoscape, dagre );
  }

})();
<|endoftext|>


<commit_before>(function (conversationsRepository) {

    var _database = require("../data/database");

    conversationsRepository.list = function (query, projection, next) {

        _database.getDb(function (err, db) {
            if (err) {
                next(err, null);
            }
            else {
                db.conversations.find(query, projection).toArray(function (err, results) {
                    if (err) {
                        next(err, null);
                    } else {
                        next(null, results);
                    }
                });
            }
        });
    };

})(module.exports)<commit_msg>Added a new method to add or update an element in a collection.<commit_after>(function (conversationsRepository) {

    var _database = require("../data/database");

    conversationsRepository.list = function (query, projection, next) {

        _database.getDb(function (err, db) {
            if (err) {
                next(err, null);
            }
            else {
                db.conversations.find(query, projection).toArray(function (err, results) {
                    if (err) {
                        next(err, null);
                    } else {
                        next(null, results);
                    }
                });
            }
        });
    };

    conversationsRepository.findOneAndUpdate = function (query, projection, options, next) {

        _database.getDb(function (err, db) {
            if (err) {
                next(err, null);
            }
            else {
                db.conversations.findOneAndUpdate(query, projection, options, next);
            }
        });
    };

})(module.exports)<|endoftext|>


<commit_before>import React, { Component } from 'react';
import toBe from 'prop-types';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import ReactTable from 'react-table';
import classNames from 'classnames';
import Modal from 'react-modal';
import matchSorter from 'match-sorter';
import lodashFp from 'lodash/fp';
import ReactTimeAgo from 'react-time-ago';

import 'react-table/react-table.css';
import './rankings.scss';

// import CollapsibleBar from 'components/CollapsibleBar';
// import ParamLink from 'components/ParamLink/ParamLink';

import { fetchRankings } from 'reducers/rankings';

import { modsToString } from 'utils/common';

const _ = lodashFp.convert({ cap: false });

const modalStyles = {
  overlay: {
    backgroundColor: 'rgba(0, 0, 0, 0.4)',
  },
  content: {
    maxHeight: '80%',
    background: '#2d2d2d',
    top: '50%',
    left: '50%',
    right: 'auto',
    bottom: 'auto',
    marginRight: '-50%',
    transform: 'translate(-50%, -50%)',
  },
};

const getScoresColumns = player => [
  {
    minWidth: 500,
    accessor: 'name',
    Header: () => (

        last updated{' '}
        <ReactTimeAgo
          timeStyle={{ units: ['second', 'minute', 'hour'] }}
          date={player.updateDate}
        />

    ),
    Cell: ({ original: item }) => (

        <a href={`http://osu.ppy.sh/b/${item.b}`}>{item.name}
        {item.m !== '0' &&  +{modsToString(item.m)}}

    ),
  },
  {
    Header: 'original pp',
    minWidth: 120,
    accessor: 'pp1',
    Cell: ({ original: item }) => (
      <>
        {item.pp1}pp
        {item.pp1 < item.pp2 &&  +{item.pp2 - item.pp1} =}
        {item.pp1 > item.pp2 &&  -{item.pp1 - item.pp2} =}
        {item.pp1 === item.pp2 &&  +0 =}
      </>
    ),
  },
  {
    Header: 'adjusted pp',
    accessor: 'pp2',
    Cell: ({ original: item }) => (
      <>
        {item.pp2}
        pp
      </>
    ),
  },
  {
    Header: 'weighted',
    accessor: 'ppAdj',
    minWidth: 115,
    Cell: ({ original: item }) => (

        {Math.round(item.ppAdj)}pp ({Math.round((item.ppAdj / item.pp2) * 100)}%)

    ),
  },
];

class ShowScoresCell extends React.Component {
  constructor() {
    super();
    this.state = {
      isModalOpen: false,
    };
  }

  render() {
    const { original } = this.props;
    const { isModalOpen } = this.state;
    return (
      <>
        <button
          type="button"
          className="btn btn-sm btn-primary"
          onClick={() => this.setState({ isModalOpen: true })}
        >
          show scores
        </button>
        <Modal
          isOpen={isModalOpen}
          onRequestClose={() => this.setState({ isModalOpen: false })}
          style={modalStyles}
          contentLabel="top 50 scores"
          ariaHideApp={false}
        >
          <ReactTable
            data={original.scores}
            player={original}
            columns={getScoresColumns(original)}
            showPagination={false}
            showPageSizeOptions={false}
            defaultPageSize={50}
            sortable={false}
            resizable={false}
            minRows={3}
          />
        </Modal>
      </>
    );
  }
}

const columns = [
  {
    minWidth: 35,
    maxWidth: 75,
    Cell: props => `#${props.original.rank2}`,
    accessor: 'rank2',
  },
  {
    minWidth: 35,
    maxWidth: 75,
    Cell: props => {
      const player = props.original;
      if (player.rank2 === player.rank1) {
        return (

            ■0

        );
      }
      if (player.rank1 > player.rank2) {
        return (
          <div
            className={classNames('rank rank-up', {
              'rank-small-change': player.rank1 - player.rank2 < 8,
            })}
          >
            ▲
            {player.rank1 - player.rank2}

        );
      } else if (player.rank2 > player.rank1) {
        return (
          <div
            className={classNames('rank rank-down', {
              'rank-small-change': player.rank2 - player.rank1 < 8,
            })}
          >
            ▼
            {player.rank2 - player.rank1}

        );
      }
    },
    accessor: 'rank2',
  },
  {
    filterable: true,
    filterMethod: (filter, rows) => {
      return matchSorter(rows, filter.value, { keys: ['name'] });
    },
    filterAll: true,
    Cell: props => (
      <a href={`https://osu.ppy.sh/users/${props.original.name}`}>{props.original.name}
    ),
    accessor: 'name',
  },
  {
    maxWidth: 300,
    Cell: ({ original }) => (

        scores updated
        <ReactTimeAgo
          timeStyle={{ units: ['second', 'minute', 'hour'] }}
          date={original.updateDate}
        />

    ),
    accessor: 'pp2',
  },
  {
    maxWidth: 170,
    Cell: ({ original }) => (

        {original.pp2}pp

          {original.ppIncrement !== null ? `+${original.ppIncrement}` : ''}

    ),
    accessor: 'pp2',
  },
  {
    maxWidth: 170,
    Cell: ({ original }) => {
      const totalChange = original.ppDiff;
      return (

           0,
              negative: totalChange < 0,
            })}
          >
            {totalChange > 0 ? '+' : ''}
            {totalChange}pp

      );
    },
    accessor: 'pp2',
  },
  {
    width: 110,
    Cell: ShowScoresCell,
    accessor: 'scores',
  },
];

const dataSelector = createSelector(
  (state, props) => props.match.params.mode,
  state => state.rankings,
  (mode, rankings) => {
    const rawData = rankings[mode].data;
    return _.flow(
      // Get old rank from array ordering / order scores by new pp
      _.map((item, playerIndex) => ({
        name: item.n,
        ppDiff: item.ppDiff,
        updateDate: item.updateDate,
        rank1: playerIndex + 1,
        scores: _.orderBy(['p2'], ['desc'], _.compact(item.s)),
      })),
      // Get total new PP / calculate weighted new pp
      _.map(item => {
        const mappedScores = item.scores.map((score, index) => ({
          name: score.n,
          m: score.m,
          b: score.b,
          pp1: score.p1,
          pp2: score.p2,
          ppAdj: score.p2 * Math.pow(0.95, index),
        }));
        return {
          ...item,
          pp2: Math.round(mappedScores.reduce((sum, score, index) => sum + score.ppAdj, 0)),
          scores: mappedScores,
        };
      }),
      // Order by new pp and get new rank from array order
      _.orderBy(['pp2'], ['desc']),
      items =>
        _.map(
          (item, newIndex) => ({
            ...item,
            rank2: newIndex + 1,
            ppIncrement: items[newIndex + 1] ? item.pp2 - items[newIndex + 1].pp2 : 0,
          }),
          items
        )
    )(rawData);
  }
);

const mapStateToProps = (state, props) => {
  const mode = props.match.params.mode;
  const data = dataSelector(state, props);
  return {
    data: _.isEmpty(data) ? null : data,
    error: state.rankings[mode].error,
    isLoading: state.rankings[mode].isLoading,
  };
};

const mapDispatchToProps = {
  fetchRankings,
};

class TopMapper extends Component {
  static propTypes = {
    match: toBe.object,
    data: toBe.array,
    error: toBe.object,
    isLoading: toBe.bool.isRequired,
  };

  componentDidMount() {
    const { isLoading, data, match } = this.props;
    if (!isLoading && !data) {
      this.props.fetchRankings(match.params.mode);
    }
  }

  componentDidUpdate() {
    const { match, isLoading, data } = this.props;
    if (!data && !isLoading) {
      this.props.fetchRankings(match.params.mode);
    }
  }

  render() {
    const { isLoading, data, error } = this.props;
    return (

            osu! top 10k rankings, but adjusted for farm maps. overweighted maps are nerfed,
            underweighted are slightly buffed. as a result, rare plays give more pp, popular plays
            give less.

 - you won't be in this ranking if your official rank is lower than 11k.

            - total pp for each player is lower than official because i only count top 50 plays (osu
            counts all of them) and i don't add bonus pp.

- this list is updated only once a few days, so it may not include latest
            recent scores

- this list can not replace the official rankings and can't even exist
            without them

          {isLoading &&
loading...
}
          {error && error.message}

            <ReactTable
              data={data || []}
              columns={columns}
              showPaginationTop
              showPageSizeOptions={false}
              defaultPageSize={20}
              sortable={false}
              resizable={false}
              minRows={4}
              noDataText={isLoading ? 'loading...' : 'no data found'}
            />

    );
  }
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(TopMapper);
<commit_msg>Added max-width for scores modal<commit_after>import React, { Component } from 'react';
import toBe from 'prop-types';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import ReactTable from 'react-table';
import classNames from 'classnames';
import Modal from 'react-modal';
import matchSorter from 'match-sorter';
import lodashFp from 'lodash/fp';
import ReactTimeAgo from 'react-time-ago';

import 'react-table/react-table.css';
import './rankings.scss';

// import CollapsibleBar from 'components/CollapsibleBar';
// import ParamLink from 'components/ParamLink/ParamLink';

import { fetchRankings } from 'reducers/rankings';

import { modsToString } from 'utils/common';

const _ = lodashFp.convert({ cap: false });

const modalStyles = {
  overlay: {
    backgroundColor: 'rgba(0, 0, 0, 0.4)',
  },
  content: {
    maxHeight: '80%',
    maxWidth: '100%',
    background: '#2d2d2d',
    top: '50%',
    left: '50%',
    right: 'auto',
    bottom: 'auto',
    marginRight: '-50%',
    transform: 'translate(-50%, -50%)',
  },
};

const getScoresColumns = player => [
  {
    minWidth: 500,
    accessor: 'name',
    Header: () => (

        last updated{' '}
        <ReactTimeAgo
          timeStyle={{ units: ['second', 'minute', 'hour'] }}
          date={player.updateDate}
        />

    ),
    Cell: ({ original: item }) => (

        <a href={`http://osu.ppy.sh/b/${item.b}`}>{item.name}
        {item.m !== '0' &&  +{modsToString(item.m)}}

    ),
  },
  {
    Header: 'original pp',
    minWidth: 120,
    accessor: 'pp1',
    Cell: ({ original: item }) => (
      <>
        {item.pp1}pp
        {item.pp1 < item.pp2 &&  +{item.pp2 - item.pp1} =}
        {item.pp1 > item.pp2 &&  -{item.pp1 - item.pp2} =}
        {item.pp1 === item.pp2 &&  +0 =}
      </>
    ),
  },
  {
    Header: 'adjusted pp',
    accessor: 'pp2',
    Cell: ({ original: item }) => (
      <>
        {item.pp2}
        pp
      </>
    ),
  },
  {
    Header: 'weighted',
    accessor: 'ppAdj',
    minWidth: 115,
    Cell: ({ original: item }) => (

        {Math.round(item.ppAdj)}pp ({Math.round((item.ppAdj / item.pp2) * 100)}%)

    ),
  },
];

class ShowScoresCell extends React.Component {
  constructor() {
    super();
    this.state = {
      isModalOpen: false,
    };
  }

  render() {
    const { original } = this.props;
    const { isModalOpen } = this.state;
    return (
      <>
        <button
          type="button"
          className="btn btn-sm btn-primary"
          onClick={() => this.setState({ isModalOpen: true })}
        >
          show scores
        </button>
        <Modal
          isOpen={isModalOpen}
          onRequestClose={() => this.setState({ isModalOpen: false })}
          style={modalStyles}
          contentLabel="top 50 scores"
          ariaHideApp={false}
        >
          <ReactTable
            data={original.scores}
            player={original}
            columns={getScoresColumns(original)}
            showPagination={false}
            showPageSizeOptions={false}
            defaultPageSize={50}
            sortable={false}
            resizable={false}
            minRows={3}
          />
        </Modal>
      </>
    );
  }
}

const columns = [
  {
    minWidth: 35,
    maxWidth: 75,
    Cell: props => `#${props.original.rank2}`,
    accessor: 'rank2',
  },
  {
    minWidth: 35,
    maxWidth: 75,
    Cell: props => {
      const player = props.original;
      if (player.rank2 === player.rank1) {
        return (

            ■0

        );
      }
      if (player.rank1 > player.rank2) {
        return (
          <div
            className={classNames('rank rank-up', {
              'rank-small-change': player.rank1 - player.rank2 < 8,
            })}
          >
            ▲
            {player.rank1 - player.rank2}

        );
      } else if (player.rank2 > player.rank1) {
        return (
          <div
            className={classNames('rank rank-down', {
              'rank-small-change': player.rank2 - player.rank1 < 8,
            })}
          >
            ▼
            {player.rank2 - player.rank1}

        );
      }
    },
    accessor: 'rank2',
  },
  {
    filterable: true,
    filterMethod: (filter, rows) => {
      return matchSorter(rows, filter.value, { keys: ['name'] });
    },
    filterAll: true,
    Cell: props => (
      <a href={`https://osu.ppy.sh/users/${props.original.name}`}>{props.original.name}
    ),
    accessor: 'name',
  },
  {
    maxWidth: 300,
    Cell: ({ original }) => (

        scores updated
        <ReactTimeAgo
          timeStyle={{ units: ['second', 'minute', 'hour'] }}
          date={original.updateDate}
        />

    ),
    accessor: 'pp2',
  },
  {
    maxWidth: 170,
    Cell: ({ original }) => (

        {original.pp2}pp

          {original.ppIncrement !== null ? `+${original.ppIncrement}` : ''}

    ),
    accessor: 'pp2',
  },
  {
    maxWidth: 170,
    Cell: ({ original }) => {
      const totalChange = original.ppDiff;
      return (

           0,
              negative: totalChange < 0,
            })}
          >
            {totalChange > 0 ? '+' : ''}
            {totalChange}pp

      );
    },
    accessor: 'pp2',
  },
  {
    width: 110,
    Cell: ShowScoresCell,
    accessor: 'scores',
  },
];

const dataSelector = createSelector(
  (state, props) => props.match.params.mode,
  state => state.rankings,
  (mode, rankings) => {
    const rawData = rankings[mode].data;
    return _.flow(
      // Get old rank from array ordering / order scores by new pp
      _.map((item, playerIndex) => ({
        name: item.n,
        ppDiff: item.ppDiff,
        updateDate: item.updateDate,
        rank1: playerIndex + 1,
        scores: _.orderBy(['p2'], ['desc'], _.compact(item.s)),
      })),
      // Get total new PP / calculate weighted new pp
      _.map(item => {
        const mappedScores = item.scores.map((score, index) => ({
          name: score.n,
          m: score.m,
          b: score.b,
          pp1: score.p1,
          pp2: score.p2,
          ppAdj: score.p2 * Math.pow(0.95, index),
        }));
        return {
          ...item,
          pp2: Math.round(mappedScores.reduce((sum, score, index) => sum + score.ppAdj, 0)),
          scores: mappedScores,
        };
      }),
      // Order by new pp and get new rank from array order
      _.orderBy(['pp2'], ['desc']),
      items =>
        _.map(
          (item, newIndex) => ({
            ...item,
            rank2: newIndex + 1,
            ppIncrement: items[newIndex + 1] ? item.pp2 - items[newIndex + 1].pp2 : 0,
          }),
          items
        )
    )(rawData);
  }
);

const mapStateToProps = (state, props) => {
  const mode = props.match.params.mode;
  const data = dataSelector(state, props);
  return {
    data: _.isEmpty(data) ? null : data,
    error: state.rankings[mode].error,
    isLoading: state.rankings[mode].isLoading,
  };
};

const mapDispatchToProps = {
  fetchRankings,
};

class TopMapper extends Component {
  static propTypes = {
    match: toBe.object,
    data: toBe.array,
    error: toBe.object,
    isLoading: toBe.bool.isRequired,
  };

  componentDidMount() {
    const { isLoading, data, match } = this.props;
    if (!isLoading && !data) {
      this.props.fetchRankings(match.params.mode);
    }
  }

  componentDidUpdate() {
    const { match, isLoading, data } = this.props;
    if (!data && !isLoading) {
      this.props.fetchRankings(match.params.mode);
    }
  }

  render() {
    const { isLoading, data, error } = this.props;
    return (

            osu! top 10k rankings, but adjusted for farm maps. overweighted maps are nerfed,
            underweighted are slightly buffed. as a result, rare plays give more pp, popular plays
            give less.

 - you won't be in this ranking if your official rank is lower than 11k.

            - total pp for each player is lower than official because i only count top 50 plays (osu
            counts all of them) and i don't add bonus pp.

- this list is updated only once a few days, so it may not include latest
            recent scores

- this list can not replace the official rankings and can't even exist
            without them

          {isLoading &&
loading...
}
          {error && error.message}

            <ReactTable
              data={data || []}
              columns={columns}
              showPaginationTop
              showPageSizeOptions={false}
              defaultPageSize={20}
              sortable={false}
              resizable={false}
              minRows={4}
              noDataText={isLoading ? 'loading...' : 'no data found'}
            />

    );
  }
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(TopMapper);
<|endoftext|>


<commit_before>/*!
 * Snakeskin
 * https://github.com/SnakeskinTpl/Snakeskin
 *
 * Released under the MIT license
 * https://github.com/SnakeskinTpl/Snakeskin/blob/master/LICENSE
 */

let
	beautify,
	globalBeautify = global.js_beautify;

/* istanbul ignore next */
////#include ../node_modules/js-beautify/js/lib/beautify.js

if (IS_NODE) {
	beautify = exports.js_beautify;
	delete exports.js_beautify;

} else {
	beautify = global.js_beautify;
	global.js_beautify = globalBeautify;
}

let
	esprima,
	globalEsprima = global.esprima;

/* istanbul ignore next */
////#include ../node_modules/esprima/esprima.js

if (IS_NODE) {
	esprima = exports;
	module.exports =
		exports = root;

} else {
	esprima = global.esprima;
	global.esprima = globalEsprima;
}

let
	Escaper,
	globalEscaper = global.Escaper;

/* istanbul ignore next */
////#include ../node_modules/escaper/dist/escaper.js

if (IS_NODE) {
	Escaper = module.exports;
	module.exports =
		exports = root;

} else {
	Escaper = global.Escaper;
	global.Escaper = globalEscaper;
}

<commit_msg>Refactoring<commit_after>/*!
 * Snakeskin
 * https://github.com/SnakeskinTpl/Snakeskin
 *
 * Released under the MIT license
 * https://github.com/SnakeskinTpl/Snakeskin/blob/master/LICENSE
 */

let
	beautify,
	globalBeautify = global.js_beautify;

/* istanbul ignore next */
//#include ../../node_modules/js-beautify/js/lib/beautify.js

if (IS_NODE) {
	beautify = exports.js_beautify;
	delete exports.js_beautify;

} else {
	beautify = global.js_beautify;
	global.js_beautify = globalBeautify;
}

let
	esprima,
	globalEsprima = global.esprima;

/* istanbul ignore next */
//#include ../../node_modules/esprima/esprima.js

if (IS_NODE) {
	esprima = exports;
	module.exports =
		exports = root;

} else {
	esprima = global.esprima;
	global.esprima = globalEsprima;
}

let
	Escaper,
	globalEscaper = global.Escaper;

/* istanbul ignore next */
//#include ../../node_modules/escaper/dist/escaper.js

if (IS_NODE) {
	Escaper = module.exports;
	module.exports =
		exports = root;

} else {
	Escaper = global.Escaper;
	global.Escaper = globalEscaper;
}

<|endoftext|>


<commit_before>var seraph = require('seraph');
var async = require('async');

var fetchdb = [
  'MATCH node',
  'OPTIONAL MATCH node-[rel]-x',
  'RETURN node, labels(node) as labels, collect(rel) as rels'
].join(' ');

function dbToJSON(db, cb) {
  db.query(fetchdb, function(err, data) {
    if (err) return cb(err);
    var normalized = {};
    data.forEach(function(row) {
      normalized['node_'+row.node.id] = {
        type: 'node',
        labels: row.labels,
        data: row.node
      };
      row.rels.forEach(function(rel) {
        normalized['rel_'+rel.id] = {
          type: 'rel',
          data: rel
        };
      });
    });
    cb(null, normalized);
  });
}

function JSONtoStatementList(data) {
  var keys = Object.keys(data);
  var nodes = keys.filter(function(key) {
    return data[key].type == 'node';
  });
  var rels = keys.filter(function(key) {
    return data[key].type == 'rel';
  });
  var keymap = {};
  // can't use query params as we're exporting to a single string
  function createParamString(obj) {
    var kvs = Object.keys(obj).map(function(key) {
      return key + ':' + JSON.stringify(obj[key]);
    }).join(',');
    return '{' + kvs + '}';
  }
  var uniqueNameIndex = 0;
  function createUniqueName(type) {
    return type + (uniqueNameIndex++);
  }
  var creates = [];
  nodes.forEach(function(nodeKey) {
    var node = data[nodeKey];
    delete node.data.id;
    var name = keymap[nodeKey] = createUniqueName('node');
    var params = createParamString(node.data);
    var labels = node.labels.map(function(l) { return ':`' + l + '`' }).join('');
    creates.push({
      statement: '(' + name + labels + ' ' + params + ')',
      refs:[],
      id: name
    });
  });
  rels.forEach(function(relKey) {
    var rel = data[relKey];
    var name = keymap[relKey] = createUniqueName('rel');
    var params = createParamString(rel.data.properties);
    var start = keymap["node_" + rel.data.start];
    var finish = keymap["node_" + rel.data.end];
    var type = '`' + rel.data.type + '`';
    creates.push({
      statement: start + '-[' + name + ':' + type + ' ' + params + ']->' + finish,
      refs:[start,finish],
      id: name
    });
  });
  return creates;
};

function restoreTransactional(db, data) {
  var statements = JSONtoStatementList(data);
  var stepSize = 15;
  var nodeMap = {};
  var groups = [];
  var txn;

  for (var i = 0; i < data.length; i += 15) {
    groups.push(data.slice(i, i + 15));
  }

  async.forEachSeries(groups, function(s,cb) {
    var start = s.reduce(function(start, row) {
      if (row.refs.length == 0) return start;
      else return start.concat(row.refs.map(function(ref) {
        return ref + '=node(' + nodeMap[ref] + ')';
      }));
    }, []).join(',');
    var query = start ? 'START ' + start : '';
    query += 'CREATE ' + s.map(function(row) { return row.statement }).join(',');
    query += 'RETURN ' + s.map(function(row) { return 'id(' + row.id + ') as ' + row.id }).join(',');
    var endpoint = txn || 'transaction';
    var op = db.operation(endpoint, 'POST', {
      statements: [{ statement:query }]
    });
    db.call(op, function(err, result) {
      if (err) return cb(err);
      // see how enterprise.
      var keys = result.result[0].keys;
      var vals = result.result[0].data[0].row;
      keys.forEach(function(key, i) {
        nodeMap[key] = vals[i];
      });

      cb();
    });
  }, function(e) {
    if (e) return cb(e);
    var op = db.operation(txn + '/commit', 'POST', {});
    db.call(op, cb);
  });
}

function JSONtoCypher(data) {
  return 'CREATE ' + JSONtoStatementList(data).map(function(s) { return s.statement }).join(',')
}

module.exports = {
  json: dbToJSON,
  jsonToCypher: JSONtoCypher,
  cypher: function(db, cb) {
    dbToJSON(db, function(err, data) {
      if (err) return cb(err);
      else cb(null, JSONtoCypher(data));
    });
  }
}
<commit_msg>expose restoreTransactional<commit_after>var seraph = require('seraph');
var async = require('async');

var fetchdb = [
  'MATCH node',
  'OPTIONAL MATCH node-[rel]-x',
  'RETURN node, labels(node) as labels, collect(rel) as rels'
].join(' ');

function dbToJSON(db, cb) {
  db.query(fetchdb, function(err, data) {
    if (err) return cb(err);
    var normalized = {};
    data.forEach(function(row) {
      normalized['node_'+row.node.id] = {
        type: 'node',
        labels: row.labels,
        data: row.node
      };
      row.rels.forEach(function(rel) {
        normalized['rel_'+rel.id] = {
          type: 'rel',
          data: rel
        };
      });
    });
    cb(null, normalized);
  });
}

function JSONtoStatementList(data) {
  var keys = Object.keys(data);
  var nodes = keys.filter(function(key) {
    return data[key].type == 'node';
  });
  var rels = keys.filter(function(key) {
    return data[key].type == 'rel';
  });
  var keymap = {};
  // can't use query params as we're exporting to a single string
  function createParamString(obj) {
    var kvs = Object.keys(obj).map(function(key) {
      return key + ':' + JSON.stringify(obj[key]);
    }).join(',');
    return '{' + kvs + '}';
  }
  var uniqueNameIndex = 0;
  function createUniqueName(type) {
    return type + (uniqueNameIndex++);
  }
  var creates = [];
  nodes.forEach(function(nodeKey) {
    var node = data[nodeKey];
    delete node.data.id;
    var name = keymap[nodeKey] = createUniqueName('node');
    var params = createParamString(node.data);
    var labels = node.labels.map(function(l) { return ':`' + l + '`' }).join('');
    creates.push({
      statement: '(' + name + labels + ' ' + params + ')',
      refs:[],
      id: name
    });
  });
  rels.forEach(function(relKey) {
    var rel = data[relKey];
    var name = keymap[relKey] = createUniqueName('rel');
    var params = createParamString(rel.data.properties);
    var start = keymap["node_" + rel.data.start];
    var finish = keymap["node_" + rel.data.end];
    var type = '`' + rel.data.type + '`';
    creates.push({
      statement: start + '-[' + name + ':' + type + ' ' + params + ']->' + finish,
      refs:[start,finish],
      id: name
    });
  });
  return creates;
};

function restoreTransactional(db, data) {
  var statements = JSONtoStatementList(data);
  var stepSize = 15;
  var nodeMap = {};
  var groups = [];
  var txn;

  for (var i = 0; i < data.length; i += 15) {
    groups.push(data.slice(i, i + 15));
  }

  async.forEachSeries(groups, function(s,cb) {
    var start = s.reduce(function(start, row) {
      if (row.refs.length == 0) return start;
      else return start.concat(row.refs.map(function(ref) {
        return ref + '=node(' + nodeMap[ref] + ')';
      }));
    }, []).join(',');
    var query = start ? 'START ' + start : '';
    query += 'CREATE ' + s.map(function(row) { return row.statement }).join(',');
    query += 'RETURN ' + s.map(function(row) { return 'id(' + row.id + ') as ' + row.id }).join(',');
    var endpoint = txn || 'transaction';
    var op = db.operation(endpoint, 'POST', {
      statements: [{ statement:query }]
    });
    db.call(op, function(err, result) {
      if (err) return cb(err);
      // see how enterprise.
      var keys = result.result[0].keys;
      var vals = result.result[0].data[0].row;
      keys.forEach(function(key, i) {
        nodeMap[key] = vals[i];
      });

      cb();
    });
  }, function(e) {
    if (e) return cb(e);
    var op = db.operation(txn + '/commit', 'POST', {});
    db.call(op, cb);
  });
}

function JSONtoCypher(data) {
  return 'CREATE ' + JSONtoStatementList(data).map(function(s) { return s.statement }).join(',')
}

module.exports = {
  json: dbToJSON,
  jsonToCypher: JSONtoCypher,
  restoreTransactional: restoreTransactional,
  cypher: function(db, cb) {
    dbToJSON(db, function(err, data) {
      if (err) return cb(err);
      else cb(null, JSONtoCypher(data));
    });
  }
}
<|endoftext|>


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

/**
 * A Screen is the largest chunk of a simulation. (Java sims used the term Module, but that term
 * is too overloaded to use with JavaScript and Git.)
 *
 * When creating a Sim, Screens are supplied as the arguments. They can be specified as object literals or through
 * instances of this class. This class may centralize default behavior or state for Screens in the future, but right
 * now it only allows you to create Sims without using named parameter object literals.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */

import BooleanProperty from '../../axon/js/BooleanProperty.js';
import DerivedProperty from '../../axon/js/DerivedProperty.js';
import Property from '../../axon/js/Property.js';
import PropertyIO from '../../axon/js/PropertyIO.js';
import Dimension2 from '../../dot/js/Dimension2.js';
import Shape from '../../kite/js/Shape.js';
import inherit from '../../phet-core/js/inherit.js';
import merge from '../../phet-core/js/merge.js';
import StringUtils from '../../phetcommon/js/util/StringUtils.js';
import Path from '../../scenery/js/nodes/Path.js';
import Rectangle from '../../scenery/js/nodes/Rectangle.js';
import PhetioObject from '../../tandem/js/PhetioObject.js';
import Tandem from '../../tandem/js/Tandem.js';
import NullableIO from '../../tandem/js/types/NullableIO.js';
import StringIO from '../../tandem/js/types/StringIO.js';
import joist from './joist.js';
import joistStrings from './joistStrings.js';
import ScreenIcon from './ScreenIcon.js';
import ScreenIO from './ScreenIO.js';

const screenNamePatternString = joistStrings.a11y.screenNamePattern;
const screenSimPatternString = joistStrings.a11y.screenSimPattern;
const simScreenString = joistStrings.a11y.simScreen;

// constants
const MINIMUM_HOME_SCREEN_ICON_SIZE = new Dimension2( 548, 373 );
const MINIMUM_NAVBAR_ICON_SIZE = new Dimension2( 147, 100 );
const NAVBAR_ICON_ASPECT_RATIO = MINIMUM_NAVBAR_ICON_SIZE.width / MINIMUM_NAVBAR_ICON_SIZE.height;
const HOME_SCREEN_ICON_ASPECT_RATIO = MINIMUM_HOME_SCREEN_ICON_SIZE.width / MINIMUM_HOME_SCREEN_ICON_SIZE.height;
const ICON_ASPECT_RATIO_TOLERANCE = 5E-3; // how close to the ideal aspect ratio an icon must be

// Home screen and navigation bar icons must have the same aspect ratio, see https://github.com/phetsims/joist/issues/76
assert && assert( Math.abs( HOME_SCREEN_ICON_ASPECT_RATIO - HOME_SCREEN_ICON_ASPECT_RATIO ) < ICON_ASPECT_RATIO_TOLERANCE,
  'MINIMUM_HOME_SCREEN_ICON_SIZE and MINIMUM_NAVBAR_ICON_SIZE must have the same aspect ratio' );

/**
 * @param {function} createModel
 * @param {function:Object } createView - function( model )
 * @param {Object} [options]
 * @constructor
 */
function Screen( createModel, createView, options ) {

  options = merge( {

    // {string|null} name of the sim, as displayed to the user.
    // For single-screen sims, there is no home screen or navigation bar, and null is OK.
    // For multi-screen sims, this must be provided.
    name: null,

    // {boolean} whether nameProperty should be instrumented. see usage for explanation of its necessity.
    instrumentNameProperty: true,

    // {Property.<Color|string>} background color of the Screen
    backgroundColorProperty: new Property( 'white' ),

    // {Node|null} icon shown on the home screen. If null, then a default is created.
    // For single-screen sims, there is no home screen and the default is OK.
    homeScreenIcon: null,

    // {boolean} whether to draw a frame around the small icons on home screen
    showUnselectedHomeScreenIconFrame: false,

    // {Node|null} icon shown in the navigation bar. If null, then the home screen icon will be used, scaled to fit.
    navigationBarIcon: null,

    // {string|null} show a frame around the screen icon when the navbar's background fill is this color
    // 'black', 'white', or null (no frame)
    showScreenIconFrameForNavigationBarFill: null,

    // dt cap in seconds, see https://github.com/phetsims/joist/issues/130
    maxDT: 0.5,

    tandem: Tandem.REQUIRED,
    phetioType: ScreenIO,
    phetioState: false,
    phetioFeatured: true,

    // pdom - The description that is used when interacting with screen icons/buttons in joist.
    // This is often a full but short sentence with a period at the end of it.
    descriptionContent: null
  }, options );

  // Verify that the home screen and nav bar icons, if provided, are SceenIcons or sub-types thereof.
  assert && assert( !options.homeScreenIcon || options.homeScreenIcon instanceof ScreenIcon, 'invalid homeScreenIcon' );
  assert && assert( !options.navigationBarIcon || options.navigationBarIcon instanceof ScreenIcon, 'invalid navigationBarIcon' );

  assert && assert( _.includes( [ 'black', 'white', null ], options.showScreenIconFrameForNavigationBarFill ),
    'invalid showScreenIconFrameForNavigationBarFill: ' + options.showScreenIconFrameForNavigationBarFill );

  PhetioObject.call( this, options );

  // Create a default homeScreenIcon, using the Screen's background color
  if ( !options.homeScreenIcon ) {
    options.homeScreenIcon = new Rectangle( 0, 0, MINIMUM_HOME_SCREEN_ICON_SIZE.width, MINIMUM_HOME_SCREEN_ICON_SIZE.height, {
      fill: options.backgroundColorProperty.value,
      stroke: 'black'
    } );
  }

  // navigationBarIcon defaults to homeScreenIcon, and will be scaled down
  if ( !options.navigationBarIcon ) {
    options.navigationBarIcon = options.homeScreenIcon;
  }

  // Validate icon sizes
  validateIconSize( options.homeScreenIcon, MINIMUM_HOME_SCREEN_ICON_SIZE, HOME_SCREEN_ICON_ASPECT_RATIO, 'homeScreenIcon' );
  validateIconSize( options.navigationBarIcon, MINIMUM_NAVBAR_ICON_SIZE, NAVBAR_ICON_ASPECT_RATIO, 'navigationBarIcon' );

  if ( assert && options.tandem.supplied ) {
    assert && assert( _.endsWith( options.tandem.phetioID, 'Screen' ), 'Screen tandems should end with Screen suffix' );
  }

  assert && assert( !options.backgroundColor, 'Please provide backgroundColorProperty instead' );

  // @public
  this.backgroundColorProperty = options.backgroundColorProperty;

  // Don't instrument this.nameProperty if options.instrumentNameProperty is false or if options.name is not provided.
  // This additional option is needed because designers requested the ability to not instrument a screen's nameProperty
  // even if it has a name, see https://github.com/phetsims/joist/issues/627 and https://github.com/phetsims/joist/issues/629.
  const instrumentNameProperty = options.instrumentNameProperty && options.name;

  // @public (read-only) {Property<String|null>} - may be null for single-screen simulations
  this.nameProperty = new Property( options.name, {
    phetioType: PropertyIO( NullableIO( StringIO ) ),
    tandem: instrumentNameProperty ? options.tandem.createTandem( 'nameProperty' ) : Tandem.OPT_OUT,
    phetioFeatured: true,
    phetioDocumentation: 'The name of the screen. Changing this value will update the screen name for the screen\'s ' +
                         'corresponding button on the navigation bar and home screen, if they exist.'
  } );

  // @public (read-only)
  this.homeScreenIcon = options.homeScreenIcon;
  this.navigationBarIcon = options.navigationBarIcon;
  this.showUnselectedHomeScreenIconFrame = options.showUnselectedHomeScreenIconFrame;
  this.showScreenIconFrameForNavigationBarFill = options.showScreenIconFrameForNavigationBarFill;

  // @public (read-only) {Property<String|null>} - may be null for single-screen simulations
  this.pdomDisplayNameProperty = new DerivedProperty( [ this.nameProperty ], name => {
    return name === null ? null : StringUtils.fillIn( screenNamePatternString, {
      name: name
    } );
  } );

  // @public (read-only, joist)
  this.maxDT = options.maxDT;

  // @private
  this.createModel = createModel;
  this.createView = createView;

  // Construction of the model and view are delayed and controlled to enable features like
  // a) faster loading when only loading certain screens
  // b) showing a loading progress bar <not implemented>
  this._model = null; // @private
  this._view = null;  // @private

  // @public {Property.<boolean>} indicates whether the Screen is active. Clients can read this, joist sets it.
  // To prevent potential visual glitches, the value should change only while the screen's view is invisible.
  // That is: transitions from false to true before a Screen becomes visible, and from true to false after a Screen becomes invisible.
  this.activeProperty = new BooleanProperty( true, {
    tandem: options.tandem.createTandem( 'activeProperty' ),
    phetioReadOnly: true,
    phetioDocumentation: 'Indicates whether the screen is currently displayed in the simulation.  For single-screen ' +
                         'simulations, there is only one screen and it is always active.'
  } );

  // @public (a11y) - used to set the ScreenView's descriptionContent. This is a bit of a misnomer because Screen is
  // not a Node subtype, so this is a value property rather than a setter.
  this.descriptionContent = '';
  if ( options.descriptionContent ) {
    this.descriptionContent = options.descriptionContent;
  }
  else if ( this.nameProperty.value ) {
    this.descriptionContent = StringUtils.fillIn( screenNamePatternString, {
      name: this.nameProperty.value
    } );
  }
  else {
    this.descriptionContent = simScreenString; // fall back on generic name
  }

  const self = this;
  assert && this.activeProperty.lazyLink( function() {
    assert( self._view, 'isActive should not change before the Screen view has been initialized' );

    // In phet-io mode, the state of a sim can be set without a deterministic order. The activeProperty could be
    // changed before the view's visibility is set.
    if ( !Tandem.PHET_IO_ENABLED ) {
      assert( !self._view.isVisible(), 'isActive should not change while the Screen view is visible' );
    }
  } );
}

joist.register( 'Screen', Screen );

/**
 * Convenience function to validate the sizes for the home screen icon and navigation bar icon.
 * @param {Node} icon - the icon to validate
 * @param {Dimension2} minimumSize - the minimum allowed size for the icon
 * @param {number} aspectRatio - the required aspect ratio
 * @param {string} name - the name of the icon type (for assert messages)
 */
var validateIconSize = function( icon, minimumSize, aspectRatio, name ) {
  assert && assert( icon.width >= minimumSize.width, `${name} width is too small: ${icon.width} < ${minimumSize.width}` );
  assert && assert( icon.height >= minimumSize.height, `${name} height is too small: ${icon.height} < ${minimumSize.height}` );

  // Validate home screen aspect ratio
  const actualAspectRatio = icon.width / icon.height;
  assert && assert(
    Math.abs( aspectRatio - actualAspectRatio ) < ICON_ASPECT_RATIO_TOLERANCE,
    name + ' has invalid aspect ratio: ' + actualAspectRatio
  );
};

/**
 * For showing ScreenView layoutBounds with 'dev' query parameter.
 * @param {Bounds2} layoutBounds
 * @returns {Node}
 */
const devCreateLayoutBoundsNode = function( layoutBounds ) {
  return new Path( Shape.bounds( layoutBounds ), {
    stroke: 'red',
    lineWidth: 3,
    pickable: false
  } );
};

/**
 * For showing ScreenView visible bounds with 'showVisibleBounds' query parameter.
 * @param {ScreenView} screenView
 * @returns {Node}
 */
const devCreateVisibleBoundsNode = function( screenView ) {
  const path = new Path( Shape.bounds( screenView.visibleBoundsProperty.value ), {
    stroke: 'blue',
    lineWidth: 6,
    pickable: false
  } );
  screenView.visibleBoundsProperty.link( function( visibleBounds ) {
    path.shape = Shape.bounds( visibleBounds );
  } );
  return path;
};

inherit( PhetioObject, Screen, {

  // @public
  reset: function() {
    // Background color not reset, as it's a responsibility of the code that changes the property
  },

  // @public - Returns the model (if it has been constructed)
  get model() {
    assert && assert( this._model, 'Model has not yet been constructed' );
    return this._model;
  },

  // @public - Returns the view (if it has been constructed)
  get view() {
    assert && assert( this._view, 'View has not yet been constructed' );
    return this._view;
  },

  /**
   * Initialize the model.
   * @public (joist-internal)
   */
  initializeModel: function() {
    assert && assert( this._model === null, 'there was already a model' );
    this._model = this.createModel();
  },

  /**
   * Initialize the view.
   * @public (joist-internal)
   * @param {string} [simName] - The display name of the sim, used for a11y. Not provided for the home screen.
   * @param {number} [numberOfScreens] - the number of screens in the whole sim.
   */
  initializeView: function( simName, numberOfScreens ) {
    assert && assert( this._view === null, 'there was already a view' );
    this._view = this.createView( this.model );
    this._view.setVisible( false ); // a Screen is invisible until selected

    // Show the home screen's layoutBounds
    if ( phet.chipper.queryParameters.dev ) {
      this._view.addChild( devCreateLayoutBoundsNode( this._view.layoutBounds ) );
    }

    // For debugging, make it possible to see the visibleBounds.  This is not included with ?dev since
    // it should just be equal to what you see.
    if ( phet.chipper.queryParameters.showVisibleBounds ) {
      this._view.addChild( devCreateVisibleBoundsNode( this._view ) );
    }

    // Set the accessible label for the screen.
    simName && this.pdomDisplayNameProperty.link( name => {

      // Single screen sims don't need screen names, instead just show the title of the sim.
      // Using total screens for sim breaks modularity a bit, but it also is needed as that parameter changes the
      // labelling of this screen, see https://github.com/phetsims/joist/issues/496
      if ( numberOfScreens === 1 ) {
        this._view.labelContent = simName;
      }
      else {

        // initialize proper PDOM labelling for ScreenView
        this._view.labelContent = StringUtils.fillIn( screenSimPatternString, {
          screenName: name,
          simName: simName
        } );
      }

      // if there is a screenSummaryNode, then set its intro string now
      this._view.setScreenSummaryIntroString( simName, name, numberOfScreens > 1 );
    } );

    assert && this._view.accessibleAudit();
  }
}, {

  // @public
  MINIMUM_HOME_SCREEN_ICON_SIZE: MINIMUM_HOME_SCREEN_ICON_SIZE,
  HOME_SCREEN_ICON_ASPECT_RATIO: HOME_SCREEN_ICON_ASPECT_RATIO,

  // @public
  MINIMUM_NAVBAR_ICON_SIZE: MINIMUM_NAVBAR_ICON_SIZE
} );

export default Screen;<commit_msg>convert Screen to ES6 class, https://github.com/phetsims/tasks/issues/1044<commit_after>// Copyright 2013-2020, University of Colorado Boulder

/**
 * A Screen is the largest chunk of a simulation. (Java sims used the term Module, but that term
 * is too overloaded to use with JavaScript and Git.)
 *
 * When creating a Sim, Screens are supplied as the arguments. They can be specified as object literals or through
 * instances of this class. This class may centralize default behavior or state for Screens in the future, but right
 * now it only allows you to create Sims without using named parameter object literals.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */

import BooleanProperty from '../../axon/js/BooleanProperty.js';
import DerivedProperty from '../../axon/js/DerivedProperty.js';
import Property from '../../axon/js/Property.js';
import PropertyIO from '../../axon/js/PropertyIO.js';
import Dimension2 from '../../dot/js/Dimension2.js';
import Shape from '../../kite/js/Shape.js';
import merge from '../../phet-core/js/merge.js';
import StringUtils from '../../phetcommon/js/util/StringUtils.js';
import Path from '../../scenery/js/nodes/Path.js';
import Rectangle from '../../scenery/js/nodes/Rectangle.js';
import PhetioObject from '../../tandem/js/PhetioObject.js';
import Tandem from '../../tandem/js/Tandem.js';
import NullableIO from '../../tandem/js/types/NullableIO.js';
import StringIO from '../../tandem/js/types/StringIO.js';
import joist from './joist.js';
import joistStrings from './joistStrings.js';
import ScreenIcon from './ScreenIcon.js';
import ScreenIO from './ScreenIO.js';

const screenNamePatternString = joistStrings.a11y.screenNamePattern;
const screenSimPatternString = joistStrings.a11y.screenSimPattern;
const simScreenString = joistStrings.a11y.simScreen;

// constants
const MINIMUM_HOME_SCREEN_ICON_SIZE = new Dimension2( 548, 373 );
const MINIMUM_NAVBAR_ICON_SIZE = new Dimension2( 147, 100 );
const NAVBAR_ICON_ASPECT_RATIO = MINIMUM_NAVBAR_ICON_SIZE.width / MINIMUM_NAVBAR_ICON_SIZE.height;
const HOME_SCREEN_ICON_ASPECT_RATIO = MINIMUM_HOME_SCREEN_ICON_SIZE.width / MINIMUM_HOME_SCREEN_ICON_SIZE.height;
const ICON_ASPECT_RATIO_TOLERANCE = 5E-3; // how close to the ideal aspect ratio an icon must be

// Home screen and navigation bar icons must have the same aspect ratio, see https://github.com/phetsims/joist/issues/76
assert && assert( Math.abs( HOME_SCREEN_ICON_ASPECT_RATIO - HOME_SCREEN_ICON_ASPECT_RATIO ) < ICON_ASPECT_RATIO_TOLERANCE,
  'MINIMUM_HOME_SCREEN_ICON_SIZE and MINIMUM_NAVBAR_ICON_SIZE must have the same aspect ratio' );

class Screen extends PhetioObject {

  /**
   * @param {function} createModel
   * @param {function:Object } createView - function( model )
   * @param {Object} [options]
   */
  constructor( createModel, createView, options ) {

    options = merge( {

      // {string|null} name of the sim, as displayed to the user.
      // For single-screen sims, there is no home screen or navigation bar, and null is OK.
      // For multi-screen sims, this must be provided.
      name: null,

      // {boolean} whether nameProperty should be instrumented. see usage for explanation of its necessity.
      instrumentNameProperty: true,

      // {Property.<Color|string>} background color of the Screen
      backgroundColorProperty: new Property( 'white' ),

      // {Node|null} icon shown on the home screen. If null, then a default is created.
      // For single-screen sims, there is no home screen and the default is OK.
      homeScreenIcon: null,

      // {boolean} whether to draw a frame around the small icons on home screen
      showUnselectedHomeScreenIconFrame: false,

      // {Node|null} icon shown in the navigation bar. If null, then the home screen icon will be used, scaled to fit.
      navigationBarIcon: null,

      // {string|null} show a frame around the screen icon when the navbar's background fill is this color
      // 'black', 'white', or null (no frame)
      showScreenIconFrameForNavigationBarFill: null,

      // dt cap in seconds, see https://github.com/phetsims/joist/issues/130
      maxDT: 0.5,

      tandem: Tandem.REQUIRED,
      phetioType: ScreenIO,
      phetioState: false,
      phetioFeatured: true,

      // pdom - The description that is used when interacting with screen icons/buttons in joist.
      // This is often a full but short sentence with a period at the end of it.
      descriptionContent: null
    }, options );

    // Verify that the home screen and nav bar icons, if provided, are SceenIcons or sub-types thereof.
    assert && assert( !options.homeScreenIcon || options.homeScreenIcon instanceof ScreenIcon, 'invalid homeScreenIcon' );
    assert && assert( !options.navigationBarIcon || options.navigationBarIcon instanceof ScreenIcon, 'invalid navigationBarIcon' );

    assert && assert( _.includes( [ 'black', 'white', null ], options.showScreenIconFrameForNavigationBarFill ),
      'invalid showScreenIconFrameForNavigationBarFill: ' + options.showScreenIconFrameForNavigationBarFill );

    super( options );

    // Create a default homeScreenIcon, using the Screen's background color
    if ( !options.homeScreenIcon ) {
      options.homeScreenIcon = new Rectangle( 0, 0, MINIMUM_HOME_SCREEN_ICON_SIZE.width, MINIMUM_HOME_SCREEN_ICON_SIZE.height, {
        fill: options.backgroundColorProperty.value,
        stroke: 'black'
      } );
    }

    // navigationBarIcon defaults to homeScreenIcon, and will be scaled down
    if ( !options.navigationBarIcon ) {
      options.navigationBarIcon = options.homeScreenIcon;
    }

    // Validate icon sizes
    validateIconSize( options.homeScreenIcon, MINIMUM_HOME_SCREEN_ICON_SIZE, HOME_SCREEN_ICON_ASPECT_RATIO, 'homeScreenIcon' );
    validateIconSize( options.navigationBarIcon, MINIMUM_NAVBAR_ICON_SIZE, NAVBAR_ICON_ASPECT_RATIO, 'navigationBarIcon' );

    if ( assert && options.tandem.supplied ) {
      assert && assert( _.endsWith( options.tandem.phetioID, 'Screen' ), 'Screen tandems should end with Screen suffix' );
    }

    assert && assert( !options.backgroundColor, 'Please provide backgroundColorProperty instead' );

    // @public
    this.backgroundColorProperty = options.backgroundColorProperty;

    // Don't instrument this.nameProperty if options.instrumentNameProperty is false or if options.name is not provided.
    // This additional option is needed because designers requested the ability to not instrument a screen's nameProperty
    // even if it has a name, see https://github.com/phetsims/joist/issues/627 and https://github.com/phetsims/joist/issues/629.
    const instrumentNameProperty = options.instrumentNameProperty && options.name;

    // @public (read-only) {Property<String|null>} - may be null for single-screen simulations
    this.nameProperty = new Property( options.name, {
      phetioType: PropertyIO( NullableIO( StringIO ) ),
      tandem: instrumentNameProperty ? options.tandem.createTandem( 'nameProperty' ) : Tandem.OPT_OUT,
      phetioFeatured: true,
      phetioDocumentation: 'The name of the screen. Changing this value will update the screen name for the screen\'s ' +
                           'corresponding button on the navigation bar and home screen, if they exist.'
    } );

    // @public (read-only)
    this.homeScreenIcon = options.homeScreenIcon;
    this.navigationBarIcon = options.navigationBarIcon;
    this.showUnselectedHomeScreenIconFrame = options.showUnselectedHomeScreenIconFrame;
    this.showScreenIconFrameForNavigationBarFill = options.showScreenIconFrameForNavigationBarFill;

    // @public (read-only) {Property<String|null>} - may be null for single-screen simulations
    this.pdomDisplayNameProperty = new DerivedProperty( [ this.nameProperty ], name => {
      return name === null ? null : StringUtils.fillIn( screenNamePatternString, {
        name: name
      } );
    } );

    // @public (read-only, joist)
    this.maxDT = options.maxDT;

    // @private
    this.createModel = createModel;
    this.createView = createView;

    // Construction of the model and view are delayed and controlled to enable features like
    // a) faster loading when only loading certain screens
    // b) showing a loading progress bar <not implemented>
    this._model = null; // @private
    this._view = null;  // @private

    // @public {Property.<boolean>} indicates whether the Screen is active. Clients can read this, joist sets it.
    // To prevent potential visual glitches, the value should change only while the screen's view is invisible.
    // That is: transitions from false to true before a Screen becomes visible, and from true to false after a Screen becomes invisible.
    this.activeProperty = new BooleanProperty( true, {
      tandem: options.tandem.createTandem( 'activeProperty' ),
      phetioReadOnly: true,
      phetioDocumentation: 'Indicates whether the screen is currently displayed in the simulation.  For single-screen ' +
                           'simulations, there is only one screen and it is always active.'
    } );

    // @public (a11y) - used to set the ScreenView's descriptionContent. This is a bit of a misnomer because Screen is
    // not a Node subtype, so this is a value property rather than a setter.
    this.descriptionContent = '';
    if ( options.descriptionContent ) {
      this.descriptionContent = options.descriptionContent;
    }
    else if ( this.nameProperty.value ) {
      this.descriptionContent = StringUtils.fillIn( screenNamePatternString, {
        name: this.nameProperty.value
      } );
    }
    else {
      this.descriptionContent = simScreenString; // fall back on generic name
    }

    assert && this.activeProperty.lazyLink( () => {
      assert( this._view, 'isActive should not change before the Screen view has been initialized' );

      // In phet-io mode, the state of a sim can be set without a deterministic order. The activeProperty could be
      // changed before the view's visibility is set.
      if ( !Tandem.PHET_IO_ENABLED ) {
        assert( !this._view.isVisible(), 'isActive should not change while the Screen view is visible' );
      }
    } );
  }

  // @public - Returns the model (if it has been constructed)
  get model() {
    assert && assert( this._model, 'Model has not yet been constructed' );
    return this._model;
  }

  // @public - Returns the view (if it has been constructed)
  get view() {
    assert && assert( this._view, 'View has not yet been constructed' );
    return this._view;
  }

  // @public
  reset() {
    // Background color not reset, as it's a responsibility of the code that changes the property
  }

  /**
   * Initialize the model.
   * @public (joist-internal)
   */
  initializeModel() {
    assert && assert( this._model === null, 'there was already a model' );
    this._model = this.createModel();
  }

  /**
   * Initialize the view.
   * @public (joist-internal)
   * @param {string} [simName] - The display name of the sim, used for a11y. Not provided for the home screen.
   * @param {number} [numberOfScreens] - the number of screens in the whole sim.
   */
  initializeView( simName, numberOfScreens ) {
    assert && assert( this._view === null, 'there was already a view' );
    this._view = this.createView( this.model );
    this._view.setVisible( false ); // a Screen is invisible until selected

    // Show the home screen's layoutBounds
    if ( phet.chipper.queryParameters.dev ) {
      this._view.addChild( devCreateLayoutBoundsNode( this._view.layoutBounds ) );
    }

    // For debugging, make it possible to see the visibleBounds.  This is not included with ?dev since
    // it should just be equal to what you see.
    if ( phet.chipper.queryParameters.showVisibleBounds ) {
      this._view.addChild( devCreateVisibleBoundsNode( this._view ) );
    }

    // Set the accessible label for the screen.
    simName && this.pdomDisplayNameProperty.link( name => {

      // Single screen sims don't need screen names, instead just show the title of the sim.
      // Using total screens for sim breaks modularity a bit, but it also is needed as that parameter changes the
      // labelling of this screen, see https://github.com/phetsims/joist/issues/496
      if ( numberOfScreens === 1 ) {
        this._view.labelContent = simName;
      }
      else {

        // initialize proper PDOM labelling for ScreenView
        this._view.labelContent = StringUtils.fillIn( screenSimPatternString, {
          screenName: name,
          simName: simName
        } );
      }

      // if there is a screenSummaryNode, then set its intro string now
      this._view.setScreenSummaryIntroString( simName, name, numberOfScreens > 1 );
    } );

    assert && this._view.accessibleAudit();
  }
}

/**
 * Validates the sizes for the home screen icon and navigation bar icon.
 * @param {Node} icon - the icon to validate
 * @param {Dimension2} minimumSize - the minimum allowed size for the icon
 * @param {number} aspectRatio - the required aspect ratio
 * @param {string} name - the name of the icon type (for assert messages)
 */
function validateIconSize( icon, minimumSize, aspectRatio, name ) {
  assert && assert( icon.width >= minimumSize.width, `${name} width is too small: ${icon.width} < ${minimumSize.width}` );
  assert && assert( icon.height >= minimumSize.height, `${name} height is too small: ${icon.height} < ${minimumSize.height}` );

  // Validate home screen aspect ratio
  const actualAspectRatio = icon.width / icon.height;
  assert && assert(
    Math.abs( aspectRatio - actualAspectRatio ) < ICON_ASPECT_RATIO_TOLERANCE,
    name + ' has invalid aspect ratio: ' + actualAspectRatio
  );
}

/**
 * Creates a Node for visualizing the ScreenView layoutBounds with 'dev' query parameter.
 * @param {Bounds2} layoutBounds
 * @returns {Node}
 */
function devCreateLayoutBoundsNode( layoutBounds ) {
  return new Path( Shape.bounds( layoutBounds ), {
    stroke: 'red',
    lineWidth: 3,
    pickable: false
  } );
}

/**
 * Creates a Node for visualizing the ScreenView visibleBoundsProperty with 'showVisibleBounds' query parameter.
 * @param {ScreenView} screenView
 * @returns {Node}
 */
function devCreateVisibleBoundsNode( screenView ) {
  const path = new Path( Shape.bounds( screenView.visibleBoundsProperty.value ), {
    stroke: 'blue',
    lineWidth: 6,
    pickable: false
  } );
  screenView.visibleBoundsProperty.link( visibleBounds => {
    path.shape = Shape.bounds( visibleBounds );
  } );
  return path;
}

// @public
Screen.HOME_SCREEN_ICON_ASPECT_RATIO = HOME_SCREEN_ICON_ASPECT_RATIO;
Screen.MINIMUM_HOME_SCREEN_ICON_SIZE = MINIMUM_HOME_SCREEN_ICON_SIZE;
Screen.MINIMUM_NAVBAR_ICON_SIZE = MINIMUM_NAVBAR_ICON_SIZE;

joist.register( 'Screen', Screen );
export default Screen;<|endoftext|>


<commit_before>// Word cloud layout by Jason Davies, http://www.jasondavies.com/word-cloud/
// Algorithm due to Jonathan Feinberg, http://static.mrfeinberg.com/bv_ch03.pdf
(function(exports) {
  function cloud() {
    var size = [256, 256],
        text = cloudText,
        font = cloudFont,
        fontSize = cloudFontSize,
        rotate = cloudRotate,
        padding = cloudPadding,
        spiral = archimedeanSpiral,
        words = [],
        timeInterval = Infinity,
        event = d3.dispatch("word", "end"),
        timer = null,
        cloud = {};

    cloud.start = function() {
      var board = zeroArray((size[0] >> 5) * size[1]),
          bounds = null,
          n = words.length,
          i = -1,
          tags = [],
          data = words.map(function(d, i) {
        return {
          text: text.call(this, d, i),
          font: font.call(this, d, i),
          rotate: rotate.call(this, d, i),
          size: ~~fontSize.call(this, d, i),
          padding: cloudPadding.call(this, d, i)
        };
      }).sort(function(a, b) { return b.size - a.size; });

      if (timer) clearInterval(timer);
      timer = setInterval(step, 0);
      step();

      return cloud;

      function step() {
        var start = +new Date,
            d;
        while (+new Date - start < timeInterval && ++i < n && timer) {
          d = data[i];
          d.x = (size[0] * (Math.random() + .5)) >> 1;
          d.y = (size[1] * (Math.random() + .5)) >> 1;
          cloudSprite(d, data, i);
          if (place(board, d, bounds)) {
            tags.push(d);
            event.word(d);
            if (bounds) cloudBounds(bounds, d);
            else bounds = [{x: d.x + d.x0, y: d.y + d.y0}, {x: d.x + d.x1, y: d.y + d.y1}];
            // Temporary hack
            d.x -= size[0] >> 1;
            d.y -= size[1] >> 1;
          }
        }
        if (i >= n) {
          cloud.stop();
          event.end(tags, bounds);
        }
      }
    }

    cloud.stop = function() {
      if (timer) {
        clearInterval(timer);
        timer = null;
      }
      return cloud;
    };

    cloud.timeInterval = function(x) {
      if (!arguments.length) return timeInterval;
      timeInterval = x == null ? Infinity : x;
      return cloud;
    };

    function place(board, tag, bounds) {
      var perimeter = [{x: 0, y: 0}, {x: size[0], y: size[1]}],
          startX = tag.x,
          startY = tag.y,
          maxDelta = Math.sqrt(size[0] * size[0] + size[1] * size[1]),
          s = spiral(size),
          dt = Math.random() < .5 ? 1 : -1,
          t = -dt,
          dxdy,
          dx,
          dy;

      while (dxdy = s(t += dt)) {
        dx = ~~dxdy[0];
        dy = ~~dxdy[1];

        if (Math.min(dx, dy) > maxDelta) break;

        tag.x = startX + dx;
        tag.y = startY + dy;

        if (tag.x + tag.x0 < 0 || tag.y + tag.y0 < 0 ||
            tag.x + tag.x1 > size[0] || tag.y + tag.y1 > size[1]) continue;
        // TODO only check for collisions within current bounds.
        if (!bounds || !cloudCollide(tag, board, size[0])) {
          if (!bounds || collideRects(tag, bounds)) {
            var sprite = tag.sprite,
                w = tag.width >> 5,
                sw = size[0] >> 5,
                lx = tag.x - (w << 4),
                sx = lx & 0x7f,
                msx = 32 - sx,
                h = tag.y1 - tag.y0,
                x = (tag.y + tag.y0) * sw + (lx >> 5),
                last;
            for (var j = 0; j < h; j++) {
              last = 0;
              for (var i = 0; i <= w; i++) {
                board[x + i] |= (last << msx) | (i < w ? (last = sprite[j * w + i]) >>> sx : 0);
              }
              x += sw;
            }
            delete tag.sprite;
            return true;
          }
        }
      }
      return false;
    }

    cloud.words = function(x) {
      if (!arguments.length) return words;
      words = x;
      return cloud;
    };

    cloud.size = function(x) {
      if (!arguments.length) return size;
      size = [+x[0], +x[1]];
      return cloud;
    };

    cloud.font = function(x) {
      if (!arguments.length) return font;
      font = d3.functor(x);
      return cloud;
    };

    cloud.rotate = function(x) {
      if (!arguments.length) return rotate;
      rotate = d3.functor(x);
      return cloud;
    };

    cloud.text = function(x) {
      if (!arguments.length) return text;
      text = d3.functor(x);
      return cloud;
    };

    cloud.spiral = function(x) {
      if (!arguments.length) return spiral;
      spiral = spirals[x + ""] || x;
      return cloud;
    };

    cloud.fontSize = function(x) {
      if (!arguments.length) return fontSize;
      fontSize = d3.functor(x);
      return cloud;
    };

    cloud.padding = function(x) {
      if (!arguments.length) return padding;
      padding = d3.functor(x);
      return cloud;
    };

    return d3.rebind(cloud, event, "on");
  }

  function cloudText(d) {
    return d.text;
  }

  function cloudFont() {
    return "serif";
  }

  function cloudFontSize(d) {
    return Math.sqrt(d.value);
  }

  function cloudRotate() {
    return (~~(Math.random() * 6) - 3) * 30;
  }

  function cloudPadding() {
    return 1;
  }

  // Fetches a monochrome sprite bitmap for the specified text.
  // Load in batches for speed.
  function cloudSprite(d, data, di) {
    if (d.sprite) return;
    c.clearRect(0, 0, cw << 5, ch);
    var x = 0,
        y = 0,
        maxh = 0,
        n = data.length;
    di--;
    while (++di < n) {
      d = data[di];
      c.save();
      c.font = (d.size + 1) + "px " + d.font;
      var w = c.measureText(d.text + "m").width,
          h = d.size << 1;
      if (d.rotate) {
        var sr = Math.sin(d.rotate * cloudRadians),
            cr = Math.cos(d.rotate * cloudRadians),
            wcr = w * cr,
            wsr = w * sr,
            hcr = h * cr,
            hsr = h * sr;
        w = (Math.max(Math.abs(wcr + hsr), Math.abs(wcr - hsr)) + 0x1f) >> 5 << 5;
        h = ~~Math.max(Math.abs(wsr + hcr), Math.abs(wsr - hcr));
      } else {
        w = (w + 0x1f) >> 5 << 5;
      }
      if (h > maxh) maxh = h;
      if (x + w >= (cw << 5)) {
        x = 0;
        y += maxh;
        maxh = 0;
      }
      if (y + h >= ch) break;
      c.translate(x + (w >> 1), y + (h >> 1));
      if (d.rotate) c.rotate(d.rotate * cloudRadians);
      c.fillText(d.text, 0, 0);
      c.restore();
      d.width = w;
      d.height = h;
      d.xoff = x;
      d.yoff = y;
      d.x1 = w >> 1;
      d.y1 = h >> 1;
      d.x0 = -d.x1;
      d.y0 = -d.y1;
      x += w;
    }
    var pixels = c.getImageData(0, 0, cw << 5, ch).data,
        sprite = [];
    while (--di >= 0) {
      d = data[di];
      var w = d.width,
          w32 = w >> 5,
          h = d.y1 - d.y0,
          p = d.padding;
      // Zero the buffer
      for (var i = 0; i < h * w32; i++) sprite[i] = 0;
      x = d.xoff;
      if (x == null) return;
      y = d.yoff;
      var seen = 0,
          seenRow = -1;
      for (var j = 0; j < h; j++) {
        for (var i = 0; i < w; i++) {
          var k = w32 * j + (i >> 5),
              m = (pixels[((y + j) * (cw << 5) + (x + i)) << 2] ? 1 : 0) << (31 - (i % 32));
          if (p) {
            if (j) sprite[k - w32] |= m;
            if (j < w - 1) sprite[k + w32] |= m;
            m |= (m << 1) | (m >> 1);
          }
          sprite[k] |= m;
          seen |= m;
        }
        if (seen) seenRow = j;
        else {
          d.y0++;
          h--;
          j--;
          y++;
        }
      }
      d.y1 = d.y0 + seenRow;
      d.sprite = sprite.slice(0, (d.y1 - d.y0) * w32);
    }
  }

  // Use mask-based collision detection.
  function cloudCollide(tag, board, sw) {
    sw >>= 5;
    var sprite = tag.sprite,
        w = tag.width >> 5,
        lx = tag.x - (w << 4),
        sx = lx & 0x7f,
        msx = 32 - sx,
        h = tag.y1 - tag.y0,
        x = (tag.y + tag.y0) * sw + (lx >> 5),
        last;
    for (var j = 0; j < h; j++) {
      last = 0;
      for (var i = 0; i <= w; i++) {
        if (((last << msx) | (i < w ? (last = sprite[j * w + i]) >>> sx : 0))
            & board[x + i]) return true;
      }
      x += sw;
    }
    return false;
  }

  function cloudBounds(bounds, d) {
    var b0 = bounds[0],
        b1 = bounds[1];
    if (d.x + d.x0 < b0.x) b0.x = d.x + d.x0;
    if (d.y + d.y0 < b0.y) b0.y = d.y + d.y0;
    if (d.x + d.x1 > b1.x) b1.x = d.x + d.x1;
    if (d.y + d.y1 > b1.y) b1.y = d.y + d.y1;
  }

  function collideRects(a, b) {
    return a.x + a.x1 > b[0].x && a.x + a.x0 < b[1].x && a.y + a.y1 > b[0].y && a.y + a.y0 < b[1].y;
  }

  function archimedeanSpiral(size) {
    var e = size[0] / size[1];
    return function(t) {
      return [e * (t *= .1) * Math.cos(t), t * Math.sin(t)];
    };
  }

  function rectangularSpiral(size) {
    var dy = 4,
        dx = dy * size[0] / size[1],
        x = 0,
        y = 0;
    return function(t) {
      var sign = t < 0 ? -1 : 1;
      // See triangular numbers: T_n = n * (n + 1) / 2.
      switch ((Math.sqrt(1 + 4 * sign * t) - sign) & 3) {
        case 0:  x += dx; break;
        case 1:  y += dy; break;
        case 2:  x -= dx; break;
        default: y -= dy; break;
      }
      return [x, y];
    };
  }

  // TODO reuse arrays?
  function zeroArray(n) {
    var a = [],
        i = -1;
    while (++i < n) a[i] = 0;
    return a;
  }

  var cloudRadians = Math.PI / 180,
      cw = 1 << 12 >> 5,
      ch = 1 << 12,
      canvas;

  if (typeof document !== "undefined") {
    canvas = document.createElement("canvas");
    canvas.width = cw << 5;
    canvas.height = ch;
  } else {
    // node-canvas support
    var Canvas = require("canvas");
    canvas = new Canvas(cw << 5, ch);
  }

  var c = canvas.getContext("2d"),
      spirals = {
        archimedean: archimedeanSpiral,
        rectangular: rectangularSpiral
      };
  c.fillStyle = "red";
  c.textAlign = "center";

  exports.cloud = cloud;
})(typeof exports === "undefined" ? d3.layout || (d3.layout = {}) : exports);
<commit_msg>Tweak default canvas size.<commit_after>// Word cloud layout by Jason Davies, http://www.jasondavies.com/word-cloud/
// Algorithm due to Jonathan Feinberg, http://static.mrfeinberg.com/bv_ch03.pdf
(function(exports) {
  function cloud() {
    var size = [256, 256],
        text = cloudText,
        font = cloudFont,
        fontSize = cloudFontSize,
        rotate = cloudRotate,
        padding = cloudPadding,
        spiral = archimedeanSpiral,
        words = [],
        timeInterval = Infinity,
        event = d3.dispatch("word", "end"),
        timer = null,
        cloud = {};

    cloud.start = function() {
      var board = zeroArray((size[0] >> 5) * size[1]),
          bounds = null,
          n = words.length,
          i = -1,
          tags = [],
          data = words.map(function(d, i) {
        return {
          text: text.call(this, d, i),
          font: font.call(this, d, i),
          rotate: rotate.call(this, d, i),
          size: ~~fontSize.call(this, d, i),
          padding: cloudPadding.call(this, d, i)
        };
      }).sort(function(a, b) { return b.size - a.size; });

      if (timer) clearInterval(timer);
      timer = setInterval(step, 0);
      step();

      return cloud;

      function step() {
        var start = +new Date,
            d;
        while (+new Date - start < timeInterval && ++i < n && timer) {
          d = data[i];
          d.x = (size[0] * (Math.random() + .5)) >> 1;
          d.y = (size[1] * (Math.random() + .5)) >> 1;
          cloudSprite(d, data, i);
          if (place(board, d, bounds)) {
            tags.push(d);
            event.word(d);
            if (bounds) cloudBounds(bounds, d);
            else bounds = [{x: d.x + d.x0, y: d.y + d.y0}, {x: d.x + d.x1, y: d.y + d.y1}];
            // Temporary hack
            d.x -= size[0] >> 1;
            d.y -= size[1] >> 1;
          }
        }
        if (i >= n) {
          cloud.stop();
          event.end(tags, bounds);
        }
      }
    }

    cloud.stop = function() {
      if (timer) {
        clearInterval(timer);
        timer = null;
      }
      return cloud;
    };

    cloud.timeInterval = function(x) {
      if (!arguments.length) return timeInterval;
      timeInterval = x == null ? Infinity : x;
      return cloud;
    };

    function place(board, tag, bounds) {
      var perimeter = [{x: 0, y: 0}, {x: size[0], y: size[1]}],
          startX = tag.x,
          startY = tag.y,
          maxDelta = Math.sqrt(size[0] * size[0] + size[1] * size[1]),
          s = spiral(size),
          dt = Math.random() < .5 ? 1 : -1,
          t = -dt,
          dxdy,
          dx,
          dy;

      while (dxdy = s(t += dt)) {
        dx = ~~dxdy[0];
        dy = ~~dxdy[1];

        if (Math.min(dx, dy) > maxDelta) break;

        tag.x = startX + dx;
        tag.y = startY + dy;

        if (tag.x + tag.x0 < 0 || tag.y + tag.y0 < 0 ||
            tag.x + tag.x1 > size[0] || tag.y + tag.y1 > size[1]) continue;
        // TODO only check for collisions within current bounds.
        if (!bounds || !cloudCollide(tag, board, size[0])) {
          if (!bounds || collideRects(tag, bounds)) {
            var sprite = tag.sprite,
                w = tag.width >> 5,
                sw = size[0] >> 5,
                lx = tag.x - (w << 4),
                sx = lx & 0x7f,
                msx = 32 - sx,
                h = tag.y1 - tag.y0,
                x = (tag.y + tag.y0) * sw + (lx >> 5),
                last;
            for (var j = 0; j < h; j++) {
              last = 0;
              for (var i = 0; i <= w; i++) {
                board[x + i] |= (last << msx) | (i < w ? (last = sprite[j * w + i]) >>> sx : 0);
              }
              x += sw;
            }
            delete tag.sprite;
            return true;
          }
        }
      }
      return false;
    }

    cloud.words = function(x) {
      if (!arguments.length) return words;
      words = x;
      return cloud;
    };

    cloud.size = function(x) {
      if (!arguments.length) return size;
      size = [+x[0], +x[1]];
      return cloud;
    };

    cloud.font = function(x) {
      if (!arguments.length) return font;
      font = d3.functor(x);
      return cloud;
    };

    cloud.rotate = function(x) {
      if (!arguments.length) return rotate;
      rotate = d3.functor(x);
      return cloud;
    };

    cloud.text = function(x) {
      if (!arguments.length) return text;
      text = d3.functor(x);
      return cloud;
    };

    cloud.spiral = function(x) {
      if (!arguments.length) return spiral;
      spiral = spirals[x + ""] || x;
      return cloud;
    };

    cloud.fontSize = function(x) {
      if (!arguments.length) return fontSize;
      fontSize = d3.functor(x);
      return cloud;
    };

    cloud.padding = function(x) {
      if (!arguments.length) return padding;
      padding = d3.functor(x);
      return cloud;
    };

    return d3.rebind(cloud, event, "on");
  }

  function cloudText(d) {
    return d.text;
  }

  function cloudFont() {
    return "serif";
  }

  function cloudFontSize(d) {
    return Math.sqrt(d.value);
  }

  function cloudRotate() {
    return (~~(Math.random() * 6) - 3) * 30;
  }

  function cloudPadding() {
    return 1;
  }

  // Fetches a monochrome sprite bitmap for the specified text.
  // Load in batches for speed.
  function cloudSprite(d, data, di) {
    if (d.sprite) return;
    c.clearRect(0, 0, cw << 5, ch);
    var x = 0,
        y = 0,
        maxh = 0,
        n = data.length;
    di--;
    while (++di < n) {
      d = data[di];
      c.save();
      c.font = (d.size + 1) + "px " + d.font;
      var w = c.measureText(d.text + "m").width,
          h = d.size << 1;
      if (d.rotate) {
        var sr = Math.sin(d.rotate * cloudRadians),
            cr = Math.cos(d.rotate * cloudRadians),
            wcr = w * cr,
            wsr = w * sr,
            hcr = h * cr,
            hsr = h * sr;
        w = (Math.max(Math.abs(wcr + hsr), Math.abs(wcr - hsr)) + 0x1f) >> 5 << 5;
        h = ~~Math.max(Math.abs(wsr + hcr), Math.abs(wsr - hcr));
      } else {
        w = (w + 0x1f) >> 5 << 5;
      }
      if (h > maxh) maxh = h;
      if (x + w >= (cw << 5)) {
        x = 0;
        y += maxh;
        maxh = 0;
      }
      if (y + h >= ch) break;
      c.translate(x + (w >> 1), y + (h >> 1));
      if (d.rotate) c.rotate(d.rotate * cloudRadians);
      c.fillText(d.text, 0, 0);
      c.restore();
      d.width = w;
      d.height = h;
      d.xoff = x;
      d.yoff = y;
      d.x1 = w >> 1;
      d.y1 = h >> 1;
      d.x0 = -d.x1;
      d.y0 = -d.y1;
      x += w;
    }
    var pixels = c.getImageData(0, 0, cw << 5, ch).data,
        sprite = [];
    while (--di >= 0) {
      d = data[di];
      var w = d.width,
          w32 = w >> 5,
          h = d.y1 - d.y0,
          p = d.padding;
      // Zero the buffer
      for (var i = 0; i < h * w32; i++) sprite[i] = 0;
      x = d.xoff;
      if (x == null) return;
      y = d.yoff;
      var seen = 0,
          seenRow = -1;
      for (var j = 0; j < h; j++) {
        for (var i = 0; i < w; i++) {
          var k = w32 * j + (i >> 5),
              m = (pixels[((y + j) * (cw << 5) + (x + i)) << 2] ? 1 : 0) << (31 - (i % 32));
          if (p) {
            if (j) sprite[k - w32] |= m;
            if (j < w - 1) sprite[k + w32] |= m;
            m |= (m << 1) | (m >> 1);
          }
          sprite[k] |= m;
          seen |= m;
        }
        if (seen) seenRow = j;
        else {
          d.y0++;
          h--;
          j--;
          y++;
        }
      }
      d.y1 = d.y0 + seenRow;
      d.sprite = sprite.slice(0, (d.y1 - d.y0) * w32);
    }
  }

  // Use mask-based collision detection.
  function cloudCollide(tag, board, sw) {
    sw >>= 5;
    var sprite = tag.sprite,
        w = tag.width >> 5,
        lx = tag.x - (w << 4),
        sx = lx & 0x7f,
        msx = 32 - sx,
        h = tag.y1 - tag.y0,
        x = (tag.y + tag.y0) * sw + (lx >> 5),
        last;
    for (var j = 0; j < h; j++) {
      last = 0;
      for (var i = 0; i <= w; i++) {
        if (((last << msx) | (i < w ? (last = sprite[j * w + i]) >>> sx : 0))
            & board[x + i]) return true;
      }
      x += sw;
    }
    return false;
  }

  function cloudBounds(bounds, d) {
    var b0 = bounds[0],
        b1 = bounds[1];
    if (d.x + d.x0 < b0.x) b0.x = d.x + d.x0;
    if (d.y + d.y0 < b0.y) b0.y = d.y + d.y0;
    if (d.x + d.x1 > b1.x) b1.x = d.x + d.x1;
    if (d.y + d.y1 > b1.y) b1.y = d.y + d.y1;
  }

  function collideRects(a, b) {
    return a.x + a.x1 > b[0].x && a.x + a.x0 < b[1].x && a.y + a.y1 > b[0].y && a.y + a.y0 < b[1].y;
  }

  function archimedeanSpiral(size) {
    var e = size[0] / size[1];
    return function(t) {
      return [e * (t *= .1) * Math.cos(t), t * Math.sin(t)];
    };
  }

  function rectangularSpiral(size) {
    var dy = 4,
        dx = dy * size[0] / size[1],
        x = 0,
        y = 0;
    return function(t) {
      var sign = t < 0 ? -1 : 1;
      // See triangular numbers: T_n = n * (n + 1) / 2.
      switch ((Math.sqrt(1 + 4 * sign * t) - sign) & 3) {
        case 0:  x += dx; break;
        case 1:  y += dy; break;
        case 2:  x -= dx; break;
        default: y -= dy; break;
      }
      return [x, y];
    };
  }

  // TODO reuse arrays?
  function zeroArray(n) {
    var a = [],
        i = -1;
    while (++i < n) a[i] = 0;
    return a;
  }

  var cloudRadians = Math.PI / 180,
      cw = 1 << 11 >> 5,
      ch = 1 << 11,
      canvas;

  if (typeof document !== "undefined") {
    canvas = document.createElement("canvas");
    canvas.width = cw << 5;
    canvas.height = ch;
  } else {
    // node-canvas support
    var Canvas = require("canvas");
    canvas = new Canvas(cw << 5, ch);
  }

  var c = canvas.getContext("2d"),
      spirals = {
        archimedean: archimedeanSpiral,
        rectangular: rectangularSpiral
      };
  c.fillStyle = "red";
  c.textAlign = "center";

  exports.cloud = cloud;
})(typeof exports === "undefined" ? d3.layout || (d3.layout = {}) : exports);
<|endoftext|>