<commit_before>// Copyright 2017, University of Colorado Boulder

/**
 * Generates the lists under perennial/data/, and if there were changes, will commit and push.
 *
 * This grunt task should be run manually by developers when a change has been made that would add or remove
 * an entry from one of the perennial/data/ lists. But it will also be run as part of daily-grunt-work.sh
 * to catch anything that was forgotten.
 *
 * This used to be run automatically by bayes whenever a relevant change was made, see
 * https://github.com/phetsims/perennial/issues/66
 *
 * But we decided to change it to a manual step with a daily fallback, see
 * https://github.com/phetsims/perennial/issues/213
 *
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 */

const getActiveRepos = require( '../common/getActiveRepos' );
const getBranch = require( '../common/getBranch' );
const gitAdd = require( '../common/gitAdd' );
const gitCommit = require( '../common/gitCommit' );
const gitIsClean = require( '../common/gitIsClean' );
const gitPush = require( '../common/gitPush' );
const assert = require( 'assert' );
const fs = require( 'fs' );
const grunt = require( 'grunt' );
const os = require( 'os' );
const winston = require( 'winston' );

/**
 * Generates the lists under perennial/data/, and if there were changes, will commit and push.
 * @public
 */
module.exports = async function() {
  if ( await getBranch( 'perennial' ) !== 'master' || !await gitIsClean( 'perennial' ) ) {
    grunt.fail.fatal( 'Data will only be generated if perennial is on master with no working-copy changes.' );
  }

  const activeRepos = getActiveRepos();

  function writeList( name, packageFilter ) {
    const repos = activeRepos.filter( repo => {
      // Make sure that if someone doesn't have all repositories checked out that this will FAIL. Otherwise bad things.
      assert( grunt.file.exists( `../${repo}` ) );

      let packageObject;
      try {
        packageObject = JSON.parse( fs.readFileSync( `../${repo}/package.json`, 'utf8' ) );
      }
      catch( e ) {
        return false;
      }
      return packageObject.phet && packageFilter( packageObject.phet );
    } );

    grunt.log.writeln( `Writing to data/${name}` );
    fs.writeFileSync( `data/${name}`, repos.join( os.EOL ) + os.EOL );
  }

  writeList( 'interactive-description', phet => phet.features && phet.features.supportsInteractiveDescription );
  writeList( 'active-runnables', phet => phet.runnable );
  writeList( 'active-sims', phet => phet.simulation );
  writeList( 'unit-tests', phet => phet.generatedUnitTests );
  writeList( 'color-profiles', phet => phet.colorProfile );
  writeList( 'phet-io', phet => phet.runnable && phet.supportedBrands && phet.supportedBrands.includes( 'phet-io' ) );
  writeList( 'phet-io-api-stable', phet => {
    return phet.runnable && phet.supportedBrands && phet.supportedBrands.includes( 'phet-io' ) &&
           phet[ 'phet-io' ] && phet[ 'phet-io' ].compareDesignedAPIChanges;
  } );

  await gitAdd( 'perennial', 'data/interactive-description' );
  await gitAdd( 'perennial', 'data/active-runnables' );
  await gitAdd( 'perennial', 'data/active-sims' );
  await gitAdd( 'perennial', 'data/unit-tests' );
  await gitAdd( 'perennial', 'data/color-profiles' );
  await gitAdd( 'perennial', 'data/phet-io' );
  await gitAdd( 'perennial', 'data/phet-io-api-stable' );

  const hasChanges = !await gitIsClean( 'perennial' );
  if ( hasChanges ) {
    winston.info( 'Changes to data files detected, will push' );
    await gitCommit( 'perennial', 'Automated update of perennial data files' );
    await gitPush( 'perennial', 'master' );
  }
  else {
    winston.info( 'No changes detected' );
  }
};
<commit_msg>Add colorProfiles to package.json, see https://github.com/phetsims/scenery-phet/issues/515<commit_after>// Copyright 2017, University of Colorado Boulder

/**
 * Generates the lists under perennial/data/, and if there were changes, will commit and push.
 *
 * This grunt task should be run manually by developers when a change has been made that would add or remove
 * an entry from one of the perennial/data/ lists. But it will also be run as part of daily-grunt-work.sh
 * to catch anything that was forgotten.
 *
 * This used to be run automatically by bayes whenever a relevant change was made, see
 * https://github.com/phetsims/perennial/issues/66
 *
 * But we decided to change it to a manual step with a daily fallback, see
 * https://github.com/phetsims/perennial/issues/213
 *
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 */

const getActiveRepos = require( '../common/getActiveRepos' );
const getBranch = require( '../common/getBranch' );
const gitAdd = require( '../common/gitAdd' );
const gitCommit = require( '../common/gitCommit' );
const gitIsClean = require( '../common/gitIsClean' );
const gitPush = require( '../common/gitPush' );
const assert = require( 'assert' );
const fs = require( 'fs' );
const grunt = require( 'grunt' );
const os = require( 'os' );
const winston = require( 'winston' );

/**
 * Generates the lists under perennial/data/, and if there were changes, will commit and push.
 * @public
 */
module.exports = async function() {
  if ( await getBranch( 'perennial' ) !== 'master' || !await gitIsClean( 'perennial' ) ) {
    grunt.fail.fatal( 'Data will only be generated if perennial is on master with no working-copy changes.' );
  }

  const activeRepos = getActiveRepos();

  function writeList( name, packageFilter ) {
    const repos = activeRepos.filter( repo => {
      // Make sure that if someone doesn't have all repositories checked out that this will FAIL. Otherwise bad things.
      assert( grunt.file.exists( `../${repo}` ) );

      let packageObject;
      try {
        packageObject = JSON.parse( fs.readFileSync( `../${repo}/package.json`, 'utf8' ) );
      }
      catch( e ) {
        return false;
      }
      return packageObject.phet && packageFilter( packageObject.phet );
    } );

    grunt.log.writeln( `Writing to data/${name}` );
    fs.writeFileSync( `data/${name}`, repos.join( os.EOL ) + os.EOL );
  }

  writeList( 'interactive-description', phet => phet.features && phet.features.supportsInteractiveDescription );
  writeList( 'active-runnables', phet => phet.runnable );
  writeList( 'active-sims', phet => phet.simulation );
  writeList( 'unit-tests', phet => phet.generatedUnitTests );

  // TODO: delete colorProfile when all sims are using colorProfiles, see https://github.com/phetsims/scenery-phet/issues/515
  writeList( 'color-profiles', phet => !!( phet.colorProfiles || phet.colorProfile ) );
  writeList( 'phet-io', phet => phet.runnable && phet.supportedBrands && phet.supportedBrands.includes( 'phet-io' ) );
  writeList( 'phet-io-api-stable', phet => {
    return phet.runnable && phet.supportedBrands && phet.supportedBrands.includes( 'phet-io' ) &&
           phet[ 'phet-io' ] && phet[ 'phet-io' ].compareDesignedAPIChanges;
  } );

  await gitAdd( 'perennial', 'data/interactive-description' );
  await gitAdd( 'perennial', 'data/active-runnables' );
  await gitAdd( 'perennial', 'data/active-sims' );
  await gitAdd( 'perennial', 'data/unit-tests' );
  await gitAdd( 'perennial', 'data/color-profiles' );
  await gitAdd( 'perennial', 'data/phet-io' );
  await gitAdd( 'perennial', 'data/phet-io-api-stable' );

  const hasChanges = !await gitIsClean( 'perennial' );
  if ( hasChanges ) {
    winston.info( 'Changes to data files detected, will push' );
    await gitCommit( 'perennial', 'Automated update of perennial data files' );
    await gitPush( 'perennial', 'master' );
  }
  else {
    winston.info( 'No changes detected' );
  }
};
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2017 Roman Lakhtadyr
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

export default ["$rootScope", "TrackService", "$route", ($rootScope, TrackService, $route) => {
    var progress = function (event) {
        if (event.lengthComputable) {
          $rootScope.$applyAsync(upload.data.progress.percent = parseInt(event.loaded / event.total * 100));
        }
      },
      upload = {
        data: {
          queue: [],
          current: null,
          uploading: false,
          promise: null,
          progress: {
            percent: 0
          }
        },
        action: {
          add: function () {
            var selector = $("<input>");
            selector.attr("type", "file");
            selector.attr("accept", "audio/mpeg,audio/aac,audio/flac,audio/ogg");
            selector.attr("multiple", "multiple");
            selector.attr("name", "file");
            selector.on("change", function () {
              if (this.files.length == 0) return;
              var that = this;
              $rootScope.$applyAsync(function () {
                for (var i = 0; i < that.files.length; i++) {
                  if (that.files[i].size > maxFileSize) {
                    // todo: warn if file size is too big
                    continue;
                  }
                  upload.data.queue.push(that.files[i]);
                }
              });
              upload.action.next();
            });
            selector.click();
          },
          addDirectory: function () {
            var selector = $("<input>");
            selector.attr("type", "file");
            selector.attr("accept", "audio/mpeg,audio/aac,audio/flac,audio/ogg");
            selector.attr("multiple", "multiple");
            selector.attr("webkitdirectory", "");
            selector.attr("directory", "");
            selector.attr("name", "file");
            selector.on("change", function () {
              if (this.files.length == 0) return;
              var that = this;
              $rootScope.$applyAsync(function () {
                for (var i = 0; i < that.files.length; i++) {
                  if (that.files[i].size > maxFileSize) {
                    // todo: warn if file size is too big
                    continue;
                  }
                  upload.data.queue.push(that.files[i]);
                }
              });
              upload.action.next();
            });
            selector.click();
          },
          cancel: function () {
            if (confirm("Are you sure want to cancel current uploading?")) {
              if (upload.data.promise !== null) {
                upload.data.promise.abort();
              }
              upload.action.clean();
            }
          },
          clean: function () {
            upload.data.uploading = false;
            upload.data.current = null;
            upload.data.progress.percent = 0;
            upload.data.promise = null;
            upload.data.queue = [];
            $route.reload();
          },
          next: function () {

            $rootScope.$applyAsync(function () {

              if (upload.data.queue.length == 0) {
                upload.action.clean();
                return;
              } else {
                upload.data.uploading = true;
                upload.data.current = upload.data.queue.shift();
              }

              TrackService.create().success(function (id) {
                var form = new FormData();
                form.append("file", upload.data.current);
                form.append("track_id", id);
                upload.data.promise = TrackService.upload(form, progress);
                upload.data.promise.success(function (data) {
                  upload.action.next();
                }).error(function () {
                  TrackService.unlink({ song_id: id }).then(function () {
                    return upload.action.next();
                  });
                });
              });

            });

          }
        }
      };
    $rootScope.upload = upload;
  }
];<commit_msg>Refactoring UploadController<commit_after>/*
 * Copyright (c) 2017 Roman Lakhtadyr
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

export default ["$rootScope", "TrackService", "$route", ($rootScope, TrackService, $route) => {
    const progress = (event) => {
        if (event.lengthComputable) {
          $rootScope.$applyAsync(upload.data.progress.percent = parseInt(event.loaded / event.total * 100, 10));
        }
      },
      upload = {
        data: {
          queue: [],
          current: null,
          uploading: false,
          promise: null,
          progress: {
            percent: 0
          }
        },
        action: {
          add: () => {
            const selector = $("<input>");
            selector.attr("type", "file");
            selector.attr("accept", "audio/mpeg,audio/aac,audio/flac,audio/ogg");
            selector.attr("multiple", "multiple");
            selector.attr("name", "file");
            selector.on("change", () => {
              if (this.files.length === 0) return;
              const that = this;
              $rootScope.$applyAsync(() => {
                for (const file of that.files) {
                  if (file.size > maxFileSize) {
                    // todo: warn if file size is too big
                    continue;
                  }
                  upload.data.queue.push(file);
                }
              });
              upload.action.next();
            });
            selector.click();
          },
          addDirectory: () => {
            const selector = $("<input>");
            selector.attr("type", "file");
            selector.attr("accept", "audio/mpeg,audio/aac,audio/flac,audio/ogg");
            selector.attr("multiple", "multiple");
            selector.attr("webkitdirectory", "");
            selector.attr("directory", "");
            selector.attr("name", "file");
            selector.on("change", () => {
              if (this.files.length === 0) return;
              const that = this;
              $rootScope.$applyAsync(() => {
                for (var i = 0; i < that.files.length; i++) {
                  if (that.files[i].size > maxFileSize) {
                    // todo: warn if file size is too big
                    continue;
                  }
                  upload.data.queue.push(that.files[i]);
                }
              });
              upload.action.next();
            });
            selector.click();
          },
          cancel: () => {
            if (confirm("Are you sure want to cancel current uploading?")) {
              if (upload.data.promise !== null) {
                upload.data.promise.abort();
              }
              upload.action.clean();
            }
          },
          clean: () => {
            upload.data.uploading = false;
            upload.data.current = null;
            upload.data.progress.percent = 0;
            upload.data.promise = null;
            upload.data.queue = [];
            $route.reload();
          },
          next: () => {

            $rootScope.$applyAsync(() => {

              if (upload.data.queue.length === 0) {
                upload.action.clean();
                return;
              } else {
                upload.data.uploading = true;
                upload.data.current = upload.data.queue.shift();
              }

              TrackService.create().success((id) => {
                const form = new FormData();
                form.append("file", upload.data.current);
                form.append("track_id", id);
                upload.data.promise = TrackService.upload(form, progress);
                upload.data.promise.success(() => {
                  upload.action.next();
                }).error(() => {
                  TrackService.unlink({ song_id: id }).then(() => {
                    return upload.action.next();
                  });
                });
              });

            });

          }
        }
      };
    $rootScope.upload = upload;
  }
];<|endoftext|>


<commit_before>import React, { Component } from 'react';
import classnames from 'classnames';
import { deSig, uxToHex } from '/lib/util';
import { Route, Link } from "react-router-dom";

import { LoadingScreen } from './loading';
import { LinksTabBar } from '/components/lib/links-tabbar';
import SidebarSwitcher from './lib/icons/icon-sidebar-switch';
import { makeRoutePath } from '../lib/util';

export class SettingsScreen extends Component {
  constructor(props) {
    super(props);

    this.state = {
      isLoading: false,
      title: "",
      description: "",
      color: ""
    };

    this.changeTitle = this.changeTitle.bind(this);
    this.changeDescription = this.changeDescription.bind(this);
    this.changeColor = this.changeColor.bind(this);
    this.renderDelete = this.renderDelete.bind(this);
    this.renderMetadataSettings = this.renderMetadataSettings.bind(this);
  }

  componentDidMount() {
    if (this.props.resource) {
      this.setState({
        title: this.props.resource.metadata.title,
        description: this.props.resource.metadata.description,
        color: uxToHex(this.props.resource.metadata.color || '0x0')
      });
    }
  }

  componentDidUpdate(prevProps, prevState) {
    const { props, state } = this;
    if (!!state.isLoading && !props.resource) {
      this.setState({
        isLoading: false
      }, () => {
        api.setSpinner(false);
        props.history.push('/~link');
      });
    }

    if (props.resource && (prevProps !== props)) {
      this.setState({
        title: props.resource.metadata.title,
        description: props.resource.metadata.description,
        color: uxToHex(props.resource.metadata.color || '0x0')
      });
    }
  }

  changeTitle() {
    this.setState({title: event.target.value})
  }

  changeDescription() {
    this.setState({description: event.target.value});
  }

  changeColor() {
    this.setState({color: event.target.value});
  }

  deleteCollection() {
    const { props, state } = this;

    api.deleteCollection(props.resourcePath);
    api.setSpinner(true);

    this.setState({
      isLoading: true
    });
  }

  renderDelete() {
    const { props, state } = this;

    const isManaged = ('/~/' !== props.groupPath.slice(0,3));

    let deleteClasses = 'dib f9 black gray4-d bg-gray0-d ba pa2 b--black b--gray1-d pointer';
    let deleteText = 'Remove this collection from your collection list.';
    let deleteAction = 'Remove';
    if (props.amOwner && isManaged) {
      deleteText = 'Delete this collection. (All group members will no longer see this chat.)';
      deleteAction = 'Delete';
      deleteClasses = 'dib f9 ba pa2 b--red2 red2 pointer bg-gray0-d';
    }

    return (

Delete Collection

{deleteText}

        {deleteAction + ' collection'}

    );
  }

  renderMetadataSettings() {
    const { props, state } = this;
    const { resource } = props;

    return(

Rename

Change the name of this collection

          <input
            className={"f8 ba b--gray3 b--gray2-d bg-gray0-d white-d " +
            "focus-b--black focus-b--white-d pa3 db w-100 flex-auto mr3"}
            value={this.state.title}
            disabled={!props.amOwner}
            onChange={this.changeTitle}
          />
           {
              if (props.amOwner) {
                api.setSpinner(true);
                api.metadataAdd(
                  props.resourcePath,
                  props.groupPath,
                  state.title,
                  resource.metadata.description,
                  resource.metadata['date-created'],
                  uxToHex(resource.metadata.color)
                ).then(() => {
                  api.setSpinner(false);
                  this.refs.rename.innerText = "Saved";
                });
              }
            }}>
            Save

Change description

            Change the description of this collection

            <input
              className={"f8 ba b--gray3 b--gray2-d bg-gray0-d white-d " +
                "focus-b--black focus-b--white-d pa3 db w-100 flex-auto mr3"}
              value={this.state.description}
              disabled={!props.amOwner}
              onChange={this.changeDescription}
            />
             {
                if (props.amOwner) {
                  api.setSpinner(true);
                  api.metadataAdd(
                    props.resourcePath,
                    props.groupPath,
                    resource.metadata.title,
                    state.description,
                    resource['date-created'],
                    uxToHex(resource.color)
                  ).then(() => {
                    api.setSpinner(false);
                    this.refs.description.innerText = "Saved";
                  });
                }
              }}>
              Save

Change color

Give this collection a color when viewing group channels

            <input
              className={"f8 ba b--gray3 b--gray2-d bg-gray0-d white-d " +
                "focus-b--black focus-b--white-d pa3 db w-100 flex-auto mr3"}
              value={this.state.color}
              disabled={!props.amOwner}
              onChange={this.changeColor}
            />
             {
                if (props.amOwner && state.color.match(/[0-9A-F]{6}/i)) {
                  api.setSpinner(true);
                  api.metadataAdd(
                    props.resourcePath,
                    props.groupPath,
                    resource.metadata.title,
                    resource.metadata.description,
                    resource.metadata['date-created'],
                    state.color
                  ).then(() => {
                    api.setSpinner(false);
                    this.refs.color.innerText = "Saved";
                  });
                }
              }}>
              Save

    )
  }

  render() {
    const { props, state } = this;
    const isinPopout = this.props.popout ? "popout/" : "";

    let writeGroup = Array.from(props.group.values());

    if (props.groupPath === undefined) {
      return <LoadingScreen/>;
    }

    if (!!state.isLoading) {
      return (

            <Link to="/~link">{"⟵ All Collections"}</Link>

            <SidebarSwitcher
              sidebarShown={this.props.sidebarShown}
              popout={this.props.popout}
            />
            <Link to={makeRoutePath(props.resourcePath, props.popout)}
            className="pt2 white-d">

                {props.resource.metadata.title}

            </Link>
            <LinksTabBar {...props}/>

Removing...

      );
    }

    return (

          <Link to="/~link">{"⟵ All Collections"}</Link>

          <SidebarSwitcher
            sidebarShown={this.props.sidebarShown}
            popout={this.props.popout}
          />
          <Link to={makeRoutePath(props.resourcePath, props.popout)}
          className="pt2">

              {props.resource.metadata.title}

          </Link>
          <LinksTabBar {...props}/>

Collection Settings

          {this.renderDelete()}
          {this.renderMetadataSettings()}

    );
  }
}<commit_msg>link: additional safety checks in settings view<commit_after>import React, { Component } from 'react';
import classnames from 'classnames';
import { deSig, uxToHex } from '/lib/util';
import { Route, Link } from "react-router-dom";

import { LoadingScreen } from './loading';
import { LinksTabBar } from '/components/lib/links-tabbar';
import SidebarSwitcher from './lib/icons/icon-sidebar-switch';
import { makeRoutePath } from '../lib/util';

export class SettingsScreen extends Component {
  constructor(props) {
    super(props);

    this.state = {
      isLoading: false,
      title: "",
      description: "",
      color: ""
    };

    this.changeTitle = this.changeTitle.bind(this);
    this.changeDescription = this.changeDescription.bind(this);
    this.changeColor = this.changeColor.bind(this);
    this.renderDelete = this.renderDelete.bind(this);
    this.renderMetadataSettings = this.renderMetadataSettings.bind(this);
  }

  componentDidMount() {
    if ((this.props.resource) && ("metadata" in this.props.resource)) {
      this.setState({
        title: this.props.resource.metadata.title,
        description: this.props.resource.metadata.description,
        color: uxToHex(this.props.resource.metadata.color || '0x0')
      });
    }
  }

  componentDidUpdate(prevProps, prevState) {
    const { props, state } = this;
    if (!!state.isLoading && !props.resource) {
      this.setState({
        isLoading: false
      }, () => {
        api.setSpinner(false);
        props.history.push('/~link');
      });
    }

    if (((this.props.resource) && ("metadata" in this.props.resource))
      && (prevProps !== props)) {
      this.setState({
        title: props.resource.metadata.title,
        description: props.resource.metadata.description,
        color: uxToHex(props.resource.metadata.color || '0x0')
      });
    }
  }

  changeTitle() {
    this.setState({title: event.target.value})
  }

  changeDescription() {
    this.setState({description: event.target.value});
  }

  changeColor() {
    this.setState({color: event.target.value});
  }

  deleteCollection() {
    const { props, state } = this;

    api.deleteCollection(props.resourcePath);
    api.setSpinner(true);

    this.setState({
      isLoading: true
    });
  }

  renderDelete() {
    const { props, state } = this;

    const isManaged = ('/~/' !== props.groupPath.slice(0,3));

    let deleteClasses = 'dib f9 black gray4-d bg-gray0-d ba pa2 b--black b--gray1-d pointer';
    let deleteText = 'Remove this collection from your collection list.';
    let deleteAction = 'Remove';
    if (props.amOwner && isManaged) {
      deleteText = 'Delete this collection. (All group members will no longer see this chat.)';
      deleteAction = 'Delete';
      deleteClasses = 'dib f9 ba pa2 b--red2 red2 pointer bg-gray0-d';
    }

    return (

Delete Collection

{deleteText}

        {deleteAction + ' collection'}

    );
  }

  renderMetadataSettings() {
    const { props, state } = this;
    const { resource } = props;

    if (!("metadata" in resource)) {
      resource.metadata = {};
    }

    return(

Rename

Change the name of this collection

          <input
            className={"f8 ba b--gray3 b--gray2-d bg-gray0-d white-d " +
            "focus-b--black focus-b--white-d pa3 db w-100 flex-auto mr3"}
            value={this.state.title}
            disabled={!props.amOwner}
            onChange={this.changeTitle}
          />
           {
              if (props.amOwner) {
                api.setSpinner(true);
                api.metadataAdd(
                  props.resourcePath,
                  props.groupPath,
                  state.title,
                  resource.metadata.description,
                  resource.metadata['date-created'],
                  uxToHex(resource.metadata.color)
                ).then(() => {
                  api.setSpinner(false);
                  this.refs.rename.innerText = "Saved";
                });
              }
            }}>
            Save

Change description

            Change the description of this collection

            <input
              className={"f8 ba b--gray3 b--gray2-d bg-gray0-d white-d " +
                "focus-b--black focus-b--white-d pa3 db w-100 flex-auto mr3"}
              value={this.state.description}
              disabled={!props.amOwner}
              onChange={this.changeDescription}
            />
             {
                if (props.amOwner) {
                  api.setSpinner(true);
                  api.metadataAdd(
                    props.resourcePath,
                    props.groupPath,
                    resource.metadata.title,
                    state.description,
                    resource['date-created'],
                    uxToHex(resource.color)
                  ).then(() => {
                    api.setSpinner(false);
                    this.refs.description.innerText = "Saved";
                  });
                }
              }}>
              Save

Change color

Give this collection a color when viewing group channels

            <input
              className={"f8 ba b--gray3 b--gray2-d bg-gray0-d white-d " +
                "focus-b--black focus-b--white-d pa3 db w-100 flex-auto mr3"}
              value={this.state.color}
              disabled={!props.amOwner}
              onChange={this.changeColor}
            />
             {
                if (props.amOwner && state.color.match(/[0-9A-F]{6}/i)) {
                  api.setSpinner(true);
                  api.metadataAdd(
                    props.resourcePath,
                    props.groupPath,
                    resource.metadata.title,
                    resource.metadata.description,
                    resource.metadata['date-created'],
                    state.color
                  ).then(() => {
                    api.setSpinner(false);
                    this.refs.color.innerText = "Saved";
                  });
                }
              }}>
              Save

    )
  }

  render() {
    const { props, state } = this;
    const isinPopout = this.props.popout ? "popout/" : "";

    let writeGroup = Array.from(props.group.values());

    if (props.groupPath === undefined) {
      return <LoadingScreen/>;
    }

    if (!!state.isLoading) {
      return (

            <Link to="/~link">{"⟵ All Collections"}</Link>

            <SidebarSwitcher
              sidebarShown={this.props.sidebarShown}
              popout={this.props.popout}
            />
            <Link to={makeRoutePath(props.resourcePath, props.popout)}
            className="pt2 white-d">

                {props.resource.metadata.title}

            </Link>
            <LinksTabBar {...props}/>

Removing...

      );
    }

    return (

          <Link to="/~link">{"⟵ All Collections"}</Link>

          <SidebarSwitcher
            sidebarShown={this.props.sidebarShown}
            popout={this.props.popout}
          />
          <Link to={makeRoutePath(props.resourcePath, props.popout)}
          className="pt2">

              {props.resource.metadata.title}

          </Link>
          <LinksTabBar {...props}/>

Collection Settings

          {this.renderDelete()}
          {this.renderMetadataSettings()}

    );
  }
}<|endoftext|>


<commit_before>var NavigationView = React.createClass({
  render: function() {
    if(this.props.contractApp.getView() === "pdf") {
      pdfClass = "active";
      textClass = "";
    } else {
      textClass = "active";
      pdfClass = "";
    }
    return (

        Text
        Pdf

    );
  }
});
//Both
var TextPaginationView = React.createClass({
  getInitialState: function() {
    return {
      visiblePage: 1,
      totalPages: 0
    }
  },
  changePage: function(page_no) {
    this.refs.userInputText.getDOMNode().value = page_no;
    this.props.contractApp.setCurrentPage(page_no);
    this.setState({visiblePage: page_no});
    this.props.contractApp.triggerScrollToTextPage();
    // this.props.contractApp.trigger("scroll-to-page");
    // this.props.currentPage.set({"page_no": page_no});
    // this.props.currentPage.trigger("scroll-to-page");
  },
  clickPrevious: function(e) {
    e.preventDefault();
    if(this.state.visiblePage > 1) {
      this.changePage(this.state.visiblePage-1);
    }
  },
  clickNext: function(e) {
    e.preventDefault();
    if(this.state.visiblePage < this.state.totalPages) {
      this.changePage(this.state.visiblePage+1);
    }
  },
  handleKeyDown: function(e) {
    if(e.keyCode == 13) {
      var inputPage = parseInt(this.refs.userInputText.getDOMNode().value);
      if(inputPage > 0 && inputPage <= this.state.totalPages) {
        this.changePage(inputPage);
      } else {
        this.changePage(this.state.visiblePage);
      }
    }
  },
  componentDidMount: function() {
    var self = this;
    self.setState({totalPages: self.props.contractApp.getTotalPages()});
    this.props.contractApp.on("update-text-pagination-page", function(page_no) {
      self.refs.userInputText.getDOMNode().value = page_no;
      self.setState({visiblePage: page_no});
    });
    // this.props.currentPage.on("update-pagination-page", function(page_no) {
    //   self.refs.userInputText.getDOMNode().value = page_no;
    //   self.setState({visiblePage: page_no});
    // });
    // this.props.pagesCollection.on("reset", function() {
    //   self.setState({totalPages: self.props.pagesCollection.length});
    // });
    this.refs.userInputText.getDOMNode().value = this.state.visiblePage;
  },
  render: function() {
    return (

        Previous
        <input type="text" className="goto" ref="userInputText" onKeyDown={this.handleKeyDown} />
        Next of {this.state.totalPages}

    );
  }
});

var TextPageView = React.createClass({
  _onEnter: function(msg, e) {
    this.props.contractApp.triggerUpdateTextPaginationPage(this.props.page.get("page_no"));
  },
  _onLeave: function(e) {
  },
  sanitizeTxt: function(text) {
    //replace the <  and > with <%gt if they are not one of the tags below
    text = text.replace(/(<)(\/?)([span|div|p|br])([^>]*)(>)/g,"----lt----$2$3$4----gt----");
    text = text.replace(/</g,"<");
    text = text.replace(/>/g,">");
    text = text.replace(/----lt----/g,"<");
    text = text.replace(/----gt----/g,">");
    return text;
  },
  highlightSearchQuery: function(text, highlightword) {
    var re = new RegExp(highlightword, "gi");
    return text.replace(re,"" + highlightword + "");
  },
  render: function() {
    var text = this.sanitizeTxt(this.props.page.get('text'));
    var page_no = this.props.page.get('page_no');
    if(this.props.page.get('page_no') == 30) {
      var t = this.props.page.get('text');
      t = t.replace(/(<)(\/?)([span|div|p|br])([^>]*)(>)/g,"----lt----$2$3$4----gt----")
      t = t.replace(/</g,"<");
      t = t.replace(/>/g,">");
      t = t.replace(/----lt----/g,"<");
      t = t.replace(/----gt----/g,">");
    }
    if(this.props.contractApp.getSearchQuery()) {
      text = this.highlightSearchQuery(text, this.props.contractApp.getSearchQuery());
    }
    return (

        {page_no}

        <Waypoint
            onEnter={this._onEnter.bind(this, "enter" + page_no)}
            onLeave={this._onLeave}
            threshold={-0.4}/>

    );
  }
});
var TextViewer = React.createClass({
  loadAnnotations: function() {
    if(!this.annotator) {
      this.annotator = new AnnotatorjsView({
          el: ".text-annotator",
          api: this.props.contractApp.getLoadAnnotationsUrl(),
          availableTags: ["Country","Local-Company-Name"],
          // collection: annotationCollection,
          annotationCategories: ["General information","Country","Local company name"],
          enablePdfAnnotation: false,
          contractApp: this.props.contractApp
      });
    }
  },
  scrollToPage: function(page) {
    if($('.'+page).offset()) {
      var pageOffsetTop = $('.'+page).offset().top;
      var parentTop = $('.text-annotator ').scrollTop();
      var parentOffsetTop = $('.text-annotator').offset().top
      $('.text-annotator').animate({scrollTop: parentTop - parentOffsetTop + pageOffsetTop},100);
    }
  },
  componentDidMount: function() {
    var self = this;
    this.props.pagesCollection.on("reset", function() {
      self.message = "";
      if(self.props.pagesCollection.models.length === 0) {
        self.message = "There seems to be problem with this contract text. Please contact administrator with the url.";
      }
      self.forceUpdate();
      self.loadAnnotations();
      self.props.contractApp.triggerScrollToTextPage();
    });
    this.props.contractApp.on("scroll-to-text-page", function() {
      self.scrollToPage(self.props.contractApp.getCurrentPage());
    });
  },
  render: function() {
    var self = this;
    var pagesView = (this.message)?this.message:"Please wait while loading ...";
    if(this.props.pagesCollection.models.length > 0) {
      pagesView = this.props.pagesCollection.models.map(function(model, i) {
        return (
          <TextPageView
            key={i}
            contractApp={self.props.contractApp}
            page={model} />
        );
      });
    }
    return (

        {pagesView}

    );
  }
});<commit_msg>bug: Minor fix in regex, some pdf-text not rendering properly<commit_after>var NavigationView = React.createClass({
  render: function() {
    if(this.props.contractApp.getView() === "pdf") {
      pdfClass = "active";
      textClass = "";
    } else {
      textClass = "active";
      pdfClass = "";
    }
    return (

        Text
        Pdf

    );
  }
});
//Both
var TextPaginationView = React.createClass({
  getInitialState: function() {
    return {
      visiblePage: 1,
      totalPages: 0
    }
  },
  changePage: function(page_no) {
    this.refs.userInputText.getDOMNode().value = page_no;
    this.props.contractApp.setCurrentPage(page_no);
    this.setState({visiblePage: page_no});
    this.props.contractApp.triggerScrollToTextPage();
    // this.props.contractApp.trigger("scroll-to-page");
    // this.props.currentPage.set({"page_no": page_no});
    // this.props.currentPage.trigger("scroll-to-page");
  },
  clickPrevious: function(e) {
    e.preventDefault();
    if(this.state.visiblePage > 1) {
      this.changePage(this.state.visiblePage-1);
    }
  },
  clickNext: function(e) {
    e.preventDefault();
    if(this.state.visiblePage < this.state.totalPages) {
      this.changePage(this.state.visiblePage+1);
    }
  },
  handleKeyDown: function(e) {
    if(e.keyCode == 13) {
      var inputPage = parseInt(this.refs.userInputText.getDOMNode().value);
      if(inputPage > 0 && inputPage <= this.state.totalPages) {
        this.changePage(inputPage);
      } else {
        this.changePage(this.state.visiblePage);
      }
    }
  },
  componentDidMount: function() {
    var self = this;
    self.setState({totalPages: self.props.contractApp.getTotalPages()});
    this.props.contractApp.on("update-text-pagination-page", function(page_no) {
      self.refs.userInputText.getDOMNode().value = page_no;
      self.setState({visiblePage: page_no});
    });
    // this.props.currentPage.on("update-pagination-page", function(page_no) {
    //   self.refs.userInputText.getDOMNode().value = page_no;
    //   self.setState({visiblePage: page_no});
    // });
    // this.props.pagesCollection.on("reset", function() {
    //   self.setState({totalPages: self.props.pagesCollection.length});
    // });
    this.refs.userInputText.getDOMNode().value = this.state.visiblePage;
  },
  render: function() {
    return (

        Previous
        <input type="text" className="goto" ref="userInputText" onKeyDown={this.handleKeyDown} />
        Next of {this.state.totalPages}

    );
  }
});

var TextPageView = React.createClass({
  _onEnter: function(msg, e) {
    this.props.contractApp.triggerUpdateTextPaginationPage(this.props.page.get("page_no"));
  },
  _onLeave: function(e) {
  },
  sanitizeTxt: function(text) {
    //replace the <  and > with <%gt if they are not one of the tags below
    text = text.replace(/(<)(\/?)(?=span|div|p|br)([^>]*)(>)/g,"----lt----$2$3----gt----");
    text = text.replace(/</g,"<");
    text = text.replace(/>/g,">");
    text = text.replace(/----lt----/g,"<");
    text = text.replace(/----gt----/g,">");
    return text;
  },
  highlightSearchQuery: function(text, highlightword) {
    var re = new RegExp(highlightword, "gi");
    return text.replace(re,"" + highlightword + "");
  },
  render: function() {
    var text = this.sanitizeTxt(this.props.page.get('text'));
    var page_no = this.props.page.get('page_no');
    if(this.props.contractApp.getSearchQuery()) {
      text = this.highlightSearchQuery(text, this.props.contractApp.getSearchQuery());
    }
    return (

        {page_no}

        <Waypoint
            onEnter={this._onEnter.bind(this, "enter" + page_no)}
            onLeave={this._onLeave}
            threshold={-0.4}/>

    );
  }
});
var TextViewer = React.createClass({
  loadAnnotations: function() {
    if(!this.annotator) {
      this.annotator = new AnnotatorjsView({
          el: ".text-annotator",
          api: this.props.contractApp.getLoadAnnotationsUrl(),
          availableTags: ["Country","Local-Company-Name"],
          // collection: annotationCollection,
          annotationCategories: ["General information","Country","Local company name"],
          enablePdfAnnotation: false,
          contractApp: this.props.contractApp
      });
    }
  },
  scrollToPage: function(page) {
    if($('.'+page).offset()) {
      var pageOffsetTop = $('.'+page).offset().top;
      var parentTop = $('.text-annotator ').scrollTop();
      var parentOffsetTop = $('.text-annotator').offset().top
      $('.text-annotator').animate({scrollTop: parentTop - parentOffsetTop + pageOffsetTop},100);
    }
  },
  componentDidMount: function() {
    var self = this;
    this.props.pagesCollection.on("reset", function() {
      self.message = "";
      if(self.props.pagesCollection.models.length === 0) {
        self.message = "There seems to be problem with this contract text. Please contact administrator with the url.";
      }
      self.forceUpdate();
      self.loadAnnotations();
      self.props.contractApp.triggerScrollToTextPage();
    });
    this.props.contractApp.on("scroll-to-text-page", function() {
      self.scrollToPage(self.props.contractApp.getCurrentPage());
    });
  },
  render: function() {
    var self = this;
    var pagesView = (this.message)?this.message:"Please wait while loading ...";
    if(this.props.pagesCollection.models.length > 0) {
      pagesView = this.props.pagesCollection.models.map(function(model, i) {
        return (
          <TextPageView
            key={i}
            contractApp={self.props.contractApp}
            page={model} />
        );
      });
    }
    return (

        {pagesView}

    );
  }
});<|endoftext|>


<commit_before>/*jslint unparam: true, browser: true, indent: 2 */
;(function ($, window, document, undefined) {
  'use strict';

  Foundation.libs.tab = {
    name : 'tab',

    version : '5.0.3',

    settings : {
      active_class: 'active',
      callback : function () {}
    },

    init : function (scope, method, options) {
      this.bindings(method, options);
    },

    events : function () {
      $(this.scope).off('.tab').on('click.fndtn.tab', '[data-tab] > dd > a', function (e) {
        e.preventDefault();

        var tab = $(this).parent(),
            tabs = tab.closest('[data-tab]'),
            target = $('#' + this.href.split('#')[1]),
            siblings = tab.siblings(),
            settings = tabs.data('tab-init');

        tab.addClass(settings.active_class).trigger('opened');
        siblings.removeClass(settings.active_class);
        target.siblings().removeClass(settings.active_class).end().addClass(settings.active_class);
        settings.callback(tab);
        tabs.trigger('toggled', [tab]);
      });
    },

    off : function () {},

    reflow : function () {}
  };
}(jQuery, this, this.document));
<commit_msg>Allow usage of data-tab-content attibute instead of href in tabs<commit_after>/*jslint unparam: true, browser: true, indent: 2 */
;(function ($, window, document, undefined) {
  'use strict';

  Foundation.libs.tab = {
    name : 'tab',

    version : '5.0.3',

    settings : {
      active_class: 'active',
      callback : function () {}
    },

    init : function (scope, method, options) {
      this.bindings(method, options);
    },

    events : function () {
      $(this.scope).off('.tab').on('click.fndtn.tab', '[data-tab] > dd > a', function (e) {
        e.preventDefault();

        var tab = $(this).parent(),
            tabs = tab.closest('[data-tab]'),
            target = $('#' + this.href.split('#')[1]),
            siblings = tab.siblings(),
            settings = tabs.data('tab-init');

        // allow usage of data-tab-content attribute instead of href
        if ($(this).data('tab-content')) {
          target = $('#' + $(this).data('tab-content').split('#')[1]);
        }

        tab.addClass(settings.active_class).trigger('opened');
        siblings.removeClass(settings.active_class);
        target.siblings().removeClass(settings.active_class).end().addClass(settings.active_class);
        settings.callback(tab);
        tabs.trigger('toggled', [tab]);
      });
    },

    off : function () {},

    reflow : function () {}
  };
}(jQuery, this, this.document));
<|endoftext|>


<commit_before>module.exports = {
  microbreakDuration: 20000,
  microbreakInterval: 600000,
  breakDuration: 300000,
  breakInterval: 2,
  breakNotification: true,
  microbreakNotification: true,
  breakNotificationInterval: 30000,
  microbreakNotificationInterval: 10000,
  microbreak: true,
  break: true,
  microbreakStrictMode: false,
  breakStrictMode: false,
  morningHour: 6,
  microbreakPostpone: true,
  breakPostpone: true,
  microbreakPostponeTime: 120000,
  breakPostponeTime: 300000,
  microbreakPostponesLimit: 1,
  microbreakPostponableDurationPercent: 30,
  breakPostponesLimit: 1,
  breakPostponableDurationPercent: 30,
  mainColor: '#478484',
  transparentMode: false,
  opacity: 0.9,
  audio: 'crystal-glass',
  volume: 1,
  fullscreen: false,
  ideas: true,
  naturalBreaks: true,
  naturalBreaksInactivityResetTime: 300000,
  allScreens: true,
  useIdeasFromSettings: false,
  language: 'en',
  notifyNewVersion: true,
  isFirstRun: true,
  posLatitude: 0.0,
  posLongitude: 0.0,
  useMonochromeTrayIcon: false,
  useMonochromeInvertedTrayIcon: false,
  silentNotifications: false,
  monitorDnd: true,
  microbreakStartSoundPlaying: false,
  breakStartSoundPlaying: false,
  themeSource: 'system',
  endBreakShortcut: 'CmdOrCtrl+X',
  breakWindowWidth: 0.85,
  breakWindowHeight: 0.85,
  checkNewVersion: true,
  breakIdeas: [
    {
      data: ['Not alone', 'Do you find it hard to take a break alone? Try to do it with a co-worker. Aside from making it easier to stick to breaks, you will have a chance to get to know them better. Taking breaks together increases productivity.'],
      enabled: true
    },
    {
      data: ['Step away', 'Do you ever notice how your brain can figure things out by itself? All it takes is to step away from the computer and take a break to think about something totally unrelated.'],
      enabled: true
    },
    {
      data: ['Microbreaks', 'Rest is a key component in ensuring the performance of the musculoskeletal system. Frequent breaks can decrease the duration of a task and help lower the exposure to ergonomic injury risk.'],
      enabled: true
    },
    {
      data: ['Meditation', 'Research studies suggest that mindfulness-based exercises help decrease anxiety, depression, stress, and pain, and help improve general health, mental health, and quality of life. Not sure how to start? There are numerous apps to help you out.'],
      enabled: true
    },
    {
      data: ['Blink', 'Looking at screens for a long time causes you to blink less, thus exposing your eyes to the air. Blink rapidly for a few seconds to refresh the tear film and clear dust from the eye surface.'],
      enabled: true
    },
    {
      data: ['Ergonomics', 'Improper height and angle of the keyboard, mouse, monitor or working surface can cause health problems. Take some time to read about desk ergonomics.'],
      enabled: true
    },
    {
      data: ['Move', 'There are a lot of ways you can exercise within your office. Try marching in place or doing desk push-ups.'],
      enabled: true
    },
    {
      data: ['Change', 'Do you have a stability ball or standing work desk? Consider replacing your desk chair with them for a while.'],
      enabled: true
    },
    {
      data: ['Notice', 'Are you daydreaming or having trouble focusing? It is a sign that you need to take a break.'],
      enabled: true
    },
    {
      data: ['Tech', 'How about taking a no-tech walk?'],
      enabled: true
    },
    {
      data: ['Metabolism', 'Emerging research shows that sitting for long periods of time contributes to risk of metabolic syndrome, heart attack and stroke risk and overall death risk, among others. Taking regular walking breaks can help your circulation, working to counteract some of those problems.'],
      enabled: true
    },
    {
      data: ['Active Meetings', 'How about moving meetings from the conference room to the concourse? Walking not only burns calories but it may even foster a sense of collaboration.'],
      enabled: true
    },
    {
      data: ['Fruit', 'Take your time and eat some fruit. Slowly. Notice the flavor, the texture, the freshness.'],
      enabled: true
    },
    {
      data: ['Bathrooms', 'Walk to the farthest bathroom in the worksite facility when going to the restroom.'],
      enabled: true
    },
    {
      data: ['Coffee break', 'Going on coffee break? Consider doing a 5-minute walk every time you go for one.'],
      enabled: true
    },
    {
      data: ['Colleagues', 'Do not email or message office colleagues, walk to their desks to communicate with them.'],
      enabled: true
    },
    {
      data: ['Learning', 'In a study of healthy volunteers, NIH researchers found that taking short breaks, early and often, may help our brains learn new skills.'],
      enabled: true
    },
    {
      data: ['Exercise', 'Evidence suggests small amounts of regular exercise can bring dramatic health benefits, including measurably reducing stress.'],
      enabled: true
    },
    {
      data: ['Repeat', 'Have you found your stretch-ly-routine? Do not forget to repeat it for more than once to better fight effects of prolonged sitting.'],
      enabled: true
    },
    {
      data: ['Wrist and forearm', 'Extend your arms with the palms facing towards you, then slowly rotate the hands four times clockwise, then four times counter-clockwise.'],
      enabled: true
    },
    {
      data: ['Back stretching', 'Join your hands behind your head, then lift them together up above your head ending with your palms facing upward.'],
      enabled: true
    },
    {
      data: ['Mobilize', 'For every thirty minutes of stagnation, you should have at least one minute of stimulation.'],
      enabled: true
    },
    {
      data: ['7 Minute Workout', 'This workout packs in a full-body exercise routine in a fraction of the time. But as with any exercise, be careful. There are numerous apps to get you started.'],
      enabled: true
    },
    {
      data: ['Pulse', ' Raise your pulse rate to 120 beats per minute for 20 straight minutes four or five times a week doing anything you enjoy. Regularly raising your heart rate results in improved cardiovascular health.'],
      enabled: true
    },
    {
      data: ['Take the stairs ', 'Studies have shown that stair climbing, which is considered vigorous-intensity physical activity, burns more calories per minute than jogging.'],
      enabled: true
    },
    {
      data: ['Make art', 'Art therapy is known to have great mental health benefits, especially when it comes to stress management. How about writing a quick poem, taking a picture or painting something small?'],
      enabled: true
    },
    {
      data: ['Declutter', 'A clean space helps your focus at work and is often linked to positive emotions like happiness.'],
      enabled: true
    },
    {
      data: ['Lunch outside', 'Nature is linked to positive emotions and decreased stress and anxiety. Whenever possible, try to take your daily lunch break outside, surrounded by some greenery.'],
      enabled: true
    },
    {
      data: ['Public transport', 'If you use public transport regularly, you can stand instead of sitting. If it is possible, try to replace as many of your daily trips as possible with walking or cycling.'],
      enabled: true
    },
    {
      data: ['Yawning', 'Yawning can be really helpful, as it produces tears to help moisten and lubricate the eyes.'],
      enabled: true
    },
    {
      data: ['Focus change', 'Hold one finger close to the eye an focus on it. Slowly move the finger away, focus far into the distance and then back to the finger. Bring the finger back and focus on something far away.'],
      enabled: true
    },
    {
      data: ['Palming', 'While seated, brace elbows on the desk and close to the desk edge. Let your weight fall forward and cup hands over eyes. Close your eyes and inhale slowly through nose and hold for few seconds. Continue deep breathing.'],
      enabled: true
    },
    {
      data: ['Hand squeezes', 'Squeeze a pair of balled-up socks or a soft rubber ball, hold for 5 seconds. Repeat whole process few times.'],
      enabled: true
    }
  ],
  microbreakIdeas: [
    {
      data: 'Go grab a glass of water.',
      enabled: true
    },
    {
      data: 'Slowly look all the way left, then right.',
      enabled: true
    },
    {
      data: 'Slowly look all the way up, then down.',
      enabled: true
    },
    {
      data: 'Close your eyes and take few deep breaths.',
      enabled: true
    },
    {
      data: 'Close your eyes and relax.',
      enabled: true
    },
    {
      data: 'Stretch your legs.',
      enabled: true
    },
    {
      data: 'Stretch your arms.',
      enabled: true
    },
    {
      data: 'Is your sitting posture correct?',
      enabled: true
    },
    {
      data: 'Slowly turn head to side and hold for 10 seconds.',
      enabled: true
    },
    {
      data: 'Slowly tilt head to side and hold for 5-10 seconds.',
      enabled: true
    },
    {
      data: 'Stand from your chair and stretch.',
      enabled: true
    },
    {
      data: 'Refocus your eyes on an object at least 20 meters away.',
      enabled: true
    },
    {
      data: 'Take a moment to think about something you appreciate.',
      enabled: true
    },
    {
      data: 'Take a moment to smile at being alive.',
      enabled: true
    },
    {
      data: 'A truly ergonomic workstation is one that you regularly push away from.',
      enabled: true
    },
    {
      data: 'Close your eyes and count your breaths.',
      enabled: true
    },
    {
      data: 'Close your eyes and name the things you hear.',
      enabled: true
    },
    {
      data: 'Place your fingertips on your shoulders. Roll your shoulders forward for 10 seconds, then backward.',
      enabled: true
    },
    {
      data: 'Raise your right arm, stretch it over your head to the left, and hold for 10 seconds. Repeat on the other side.',
      enabled: true
    },
    {
      data: 'With your right hand, grab each finger of your left hand in turn and squeeze. Repeat on the other side.',
      enabled: true
    },
    {
      data: 'Stand up and do a lunge. Hold for 10 seconds, then do the other leg.',
      enabled: true
    },
    {
      data: 'Close your eyes and simply notice whatever arises in current moment, without judgement.',
      enabled: true
    },
    {
      data: 'One should focus every 20 minutes for 20 seconds on an object that is 20 feet distance.',
      enabled: true
    },
    {
      data: 'If you need help, ask for it.',
      enabled: true
    },
    {
      data: 'Do one thing at a time.',
      enabled: true
    },
    {
      data: 'Is your attention spent wisely?',
      enabled: true
    },
    {
      data: 'Change your sitting posture.',
      enabled: true
    },
    {
      data: 'Expose your eyes to natural light.',
      enabled: true
    },
    {
      data: 'With your eyes closed, slowly and gently move eyes up to the ceiling, then slowly down to the floor.',
      enabled: true
    },
    {
      data: 'With your eyes closed, slowly and gently move eyes to the left, then slowly to the right.',
      enabled: true
    },
    {
      data: 'Shake your hands out to get some relief.',
      enabled: true
    },
    {
      data: 'One at a time, touch the tip of each finger to the tip of your thumb so they make an O-shape.',
      enabled: true
    },
    {
      data: 'Make a fist and then slide your fingers up until they point toward the ceiling, like you\'re telling someone to stop.',
      enabled: true
    },
    {
      data: 'Make a fist and then fan your fingers out and stretch them as far as you can.',
      enabled: true
    }
  ]
}
<commit_msg>Remove duplicates<commit_after>const defaultBreakIdeas = require('./defaultBreakIdeas')
const defaultMicrobreakIdeas = require('./defaultMicrobreakIdeas')

module.exports = {
  microbreakDuration: 20000,
  microbreakInterval: 600000,
  breakDuration: 300000,
  breakInterval: 2,
  breakNotification: true,
  microbreakNotification: true,
  breakNotificationInterval: 30000,
  microbreakNotificationInterval: 10000,
  microbreak: true,
  break: true,
  microbreakStrictMode: false,
  breakStrictMode: false,
  morningHour: 6,
  microbreakPostpone: true,
  breakPostpone: true,
  microbreakPostponeTime: 120000,
  breakPostponeTime: 300000,
  microbreakPostponesLimit: 1,
  microbreakPostponableDurationPercent: 30,
  breakPostponesLimit: 1,
  breakPostponableDurationPercent: 30,
  mainColor: '#478484',
  transparentMode: false,
  opacity: 0.9,
  audio: 'crystal-glass',
  volume: 1,
  fullscreen: false,
  ideas: true,
  naturalBreaks: true,
  naturalBreaksInactivityResetTime: 300000,
  allScreens: true,
  useIdeasFromSettings: false,
  language: 'en',
  notifyNewVersion: true,
  isFirstRun: true,
  posLatitude: 0.0,
  posLongitude: 0.0,
  useMonochromeTrayIcon: false,
  useMonochromeInvertedTrayIcon: false,
  silentNotifications: false,
  monitorDnd: true,
  microbreakStartSoundPlaying: false,
  breakStartSoundPlaying: false,
  themeSource: 'system',
  endBreakShortcut: 'CmdOrCtrl+X',
  breakWindowWidth: 0.85,
  breakWindowHeight: 0.85,
  checkNewVersion: true,
  breakIdeas: defaultBreakIdeas,
  microbreakIdeas: defaultMicrobreakIdeas
}
<|endoftext|>


<commit_before>// Copyright 2015, University of Colorado Boulder

/**
 * Creates files required for the phet-io branded simulation.
 */

// modules
var ChipperStringUtils = require( '../../../chipper/js/common/ChipperStringUtils' );

/**
 * @param grunt - the grunt instance
 * @param {Object} buildConfig - see getBuildConfig.js
 */
module.exports = function( grunt, buildConfig ) {
  'use strict';

// TODO: chipper#101 eek, this is scary! we are importing from the repository dir. ideally we should just have uglify-js installed once in chipper?
  var uglify = require( '../../../' + buildConfig.name + '/node_modules/uglify-js' );

  var minified = uglify.minify( [ '../together/js/SimIFrameClient.js' ], {
    mangle: true,
    output: {
      inline_script: true, // escape </script
      beautify: false
    },
    compress: {
      global_defs: {}
    }
  } ).code;

  var copyrightHeader = '// Copyright 2002-2015, University of Colorado Boulder\n' +
                        '// For licensing, please contact phethelp@colorado.edu';
  grunt.file.write( 'build/js/SimIFrameClient.js', copyrightHeader + '\n' + minified );

  // Create a mirror-input-events.html file for testing.

  var filter = function( text ) {

    text = ChipperStringUtils.replaceAll(
      text,
      '../../js/SimIFrameClient.js',
      '../js/SimIFrameClient.js'
    );
    var camelCase = ChipperStringUtils.toCamelCase( buildConfig.name );

    // TODO: Regex or more matches
    text = ChipperStringUtils.replaceAll(
      text,
      'togetherID: \'concentration.sim\'',
      'togetherID: \'' + camelCase + '.sim\''
    );
    text = ChipperStringUtils.replaceAll(
      text,
      '\'concentration.sim.active\'',
      '\'' + camelCase + '.sim.active\''
    );
    text = ChipperStringUtils.replaceAll(
      text,
      '\'faradaysLaw.sim',
      '\'' + camelCase + '.sim'
    );
    text = ChipperStringUtils.replaceAll(
      text,
      'src="../html/color-vision-together.html?brand=phet-io&togetherEvents.storeInitialMessages"',
      'src="' + buildConfig.name + '_en.html?togetherEvents.storeInitialMessages"'
    );

    // Use a ?build so that use cases that require further query parameters don't have to distinguish
    // between ? and &
    text = ChipperStringUtils.replaceAll(
      text,
      '\'../../html/color-vision-together.html?brand=phet-io\'',
      '\'../' + buildConfig.name + '_en.html?build\''
    );
    text = ChipperStringUtils.replaceAll(
      text,
      '\'../../html/faradays-law-together.html?brand=phet-io\'',
      '\'../' + buildConfig.name + '_en.html?build\''
    );
    text = ChipperStringUtils.replaceAll(
      text,
      '\'../../html/concentration-together.html?brand=phet-io\'',
      '\'../' + buildConfig.name + '_en.html?build\''
    );
    //
    if ( camelCase === 'beaker' ) {
      text = ChipperStringUtils.replaceAll(
        text,
        '\'../../html/beaker-together.html?brand=phet-io&together.values=',
        '\'../' + buildConfig.name + '_en.html?build\''
      );
    }
    else {
      text = ChipperStringUtils.replaceAll(
        text,
        '<!--if camelcase===beaker-->',
        '<!--if camelcase===beaker' // don't end this comment, so the next comment will end
      );
    }
    return text;
  };

  var destinationPath = 'build/';

  // Iterate over the file system and copy files, changing filenames and contents as we go.
  grunt.file.recurse( '../together/doc/', function( abspath, rootdir, subdir, filename ) {
      // skip these files
      if ( abspath.indexOf( '../simula-rasa/README.md' ) === 0 ||
           abspath.indexOf( '../simula-rasa/node_modules/' ) === 0 ||
           abspath.indexOf( '../simula-rasa/.git/' ) === 0 ||
           abspath.indexOf( '../simula-rasa/build/' ) === 0 ) {

        // do nothing
      }
      else {
        var contentsPath = subdir ? ( destinationPath + '/' + subdir + '/' + filename ) : ( destinationPath + '/' + filename );
        if ( abspath.indexOf( '.png' ) >= 0 ) {
          grunt.file.copy( abspath, contentsPath );
        }
        else {
          var contents = grunt.file.read( abspath );
          contents = filter( contents );

          // Replace variations of the repository name
          //contents = ChipperStringUtils.replaceAll( contents, 'simula-rasa', repositoryName );
          //contents = ChipperStringUtils.replaceAll( contents, 'SIMULA_RASA', configPath );
          //contents = ChipperStringUtils.replaceAll( contents, 'simulaRasa', lowerCamelCase );
          //contents = ChipperStringUtils.replaceAll( contents, 'SimulaRasa', upperCamelCase );
          //contents = ChipperStringUtils.replaceAll( contents, 'Simula Rasa', title );
          //
          //// Replace author
          //contents = ChipperStringUtils.replaceAll( contents, 'Your Name (Your Affiliation)', author );
          //
          //// Replace names in the path where the contents will be written

          //contentsPath = ChipperStringUtils.replaceFirst( contentsPath, 'simula-rasa', repositoryName );
          //contentsPath = ChipperStringUtils.replaceFirst( contentsPath, 'simulaRasa', lowerCamelCase );
          //contentsPath = ChipperStringUtils.replaceFirst( contentsPath, 'SimulaRasa', upperCamelCase );

          // Write the file
          grunt.file.write( contentsPath, contents );
          grunt.log.writeln( 'wrote', contentsPath );
        }

      }
    }
  );
};
<commit_msg>Removed together/html files and use the newly generated ones in the sim directories, see https://github.com/phetsims/together/issues/201<commit_after>// Copyright 2015, University of Colorado Boulder

/**
 * Creates files required for the phet-io branded simulation.
 */

// modules
var ChipperStringUtils = require( '../../../chipper/js/common/ChipperStringUtils' );

/**
 * @param grunt - the grunt instance
 * @param {Object} buildConfig - see getBuildConfig.js
 */
module.exports = function( grunt, buildConfig ) {
  'use strict';

// TODO: chipper#101 eek, this is scary! we are importing from the repository dir. ideally we should just have uglify-js installed once in chipper?
  var uglify = require( '../../../' + buildConfig.name + '/node_modules/uglify-js' );

  var minified = uglify.minify( [ '../together/js/SimIFrameClient.js' ], {
    mangle: true,
    output: {
      inline_script: true, // escape </script
      beautify: false
    },
    compress: {
      global_defs: {}
    }
  } ).code;

  var copyrightHeader = '// Copyright 2002-2015, University of Colorado Boulder\n' +
                        '// For licensing, please contact phethelp@colorado.edu';
  grunt.file.write( 'build/js/SimIFrameClient.js', copyrightHeader + '\n' + minified );

  // Create a mirror-input-events.html file for testing.

  var filter = function( text ) {

    text = ChipperStringUtils.replaceAll(
      text,
      '../../js/SimIFrameClient.js',
      '../js/SimIFrameClient.js'
    );
    var camelCase = ChipperStringUtils.toCamelCase( buildConfig.name );

    // TODO: Regex or more matches
    text = ChipperStringUtils.replaceAll(
      text,
      'togetherID: \'concentration.sim\'',
      'togetherID: \'' + camelCase + '.sim\''
    );
    text = ChipperStringUtils.replaceAll(
      text,
      '\'concentration.sim.active\'',
      '\'' + camelCase + '.sim.active\''
    );
    text = ChipperStringUtils.replaceAll(
      text,
      '\'faradaysLaw.sim',
      '\'' + camelCase + '.sim'
    );
    text = ChipperStringUtils.replaceAll(
      text,
      'src="../html/color-vision-together.html?brand=phet-io&togetherEvents.storeInitialMessages"',
      'src="' + buildConfig.name + '_en.html?togetherEvents.storeInitialMessages"'
    );

    // Use a ?build so that use cases that require further query parameters don't have to distinguish
    // between ? and &
    text = ChipperStringUtils.replaceAll(
      text,
      '\'../../../color-vision/color-vision_en.html?brand=phet-io\'',
      '\'../' + buildConfig.name + '_en.html?build\''
    );
    text = ChipperStringUtils.replaceAll(
      text,
      '\'../../../faradays-law/faradays-law_en.html?brand=phet-io\'',
      '\'../' + buildConfig.name + '_en.html?build\''
    );
    text = ChipperStringUtils.replaceAll(
      text,
      '\'../../../concentration/concentration_en.html?brand=phet-io\'',
      '\'../' + buildConfig.name + '_en.html?build\''
    );
    //
    if ( camelCase === 'beaker' ) {
      text = ChipperStringUtils.replaceAll(
        text,
        '\'../../html/beaker-together.html?brand=phet-io&together.values=',
        '\'../' + buildConfig.name + '_en.html?build\''
      );
    }
    else {
      text = ChipperStringUtils.replaceAll(
        text,
        '<!--if camelcase===beaker-->',
        '<!--if camelcase===beaker' // don't end this comment, so the next comment will end
      );
    }
    return text;
  };

  var destinationPath = 'build/';

  // Iterate over the file system and copy files, changing filenames and contents as we go.
  grunt.file.recurse( '../together/doc/', function( abspath, rootdir, subdir, filename ) {
      // skip these files
      if ( abspath.indexOf( '../simula-rasa/README.md' ) === 0 ||
           abspath.indexOf( '../simula-rasa/node_modules/' ) === 0 ||
           abspath.indexOf( '../simula-rasa/.git/' ) === 0 ||
           abspath.indexOf( '../simula-rasa/build/' ) === 0 ) {

        // do nothing
      }
      else {
        var contentsPath = subdir ? ( destinationPath + '/' + subdir + '/' + filename ) : ( destinationPath + '/' + filename );
        if ( abspath.indexOf( '.png' ) >= 0 ) {
          grunt.file.copy( abspath, contentsPath );
        }
        else {
          var contents = grunt.file.read( abspath );
          contents = filter( contents );

          // Replace variations of the repository name
          //contents = ChipperStringUtils.replaceAll( contents, 'simula-rasa', repositoryName );
          //contents = ChipperStringUtils.replaceAll( contents, 'SIMULA_RASA', configPath );
          //contents = ChipperStringUtils.replaceAll( contents, 'simulaRasa', lowerCamelCase );
          //contents = ChipperStringUtils.replaceAll( contents, 'SimulaRasa', upperCamelCase );
          //contents = ChipperStringUtils.replaceAll( contents, 'Simula Rasa', title );
          //
          //// Replace author
          //contents = ChipperStringUtils.replaceAll( contents, 'Your Name (Your Affiliation)', author );
          //
          //// Replace names in the path where the contents will be written

          //contentsPath = ChipperStringUtils.replaceFirst( contentsPath, 'simula-rasa', repositoryName );
          //contentsPath = ChipperStringUtils.replaceFirst( contentsPath, 'simulaRasa', lowerCamelCase );
          //contentsPath = ChipperStringUtils.replaceFirst( contentsPath, 'SimulaRasa', upperCamelCase );

          // Write the file
          grunt.file.write( contentsPath, contents );
          grunt.log.writeln( 'wrote', contentsPath );
        }

      }
    }
  );
};
<|endoftext|>


<commit_before>/* Commands */

/*--
Interface Command {
  public void   constructor(JSXGraph board, object Arguments)
  public void   remove()
  public object execute()
}
--*/

var delete_ = function(board, args) {
  var args = args || {
    figure: $('input[name="figure"]:last').val()
  };
  this.remove = function() {
    this.figure.visible(true);
    this.figure.isVisible = true;
    for (i in this.figure.ancestors) {
      if (this.figure.ancestors.hasOwnProperty(i)) {
        this.figure.ancestors[i].visible(true);
        this.figure.ancestors[i].isVisible = true;
      }
    }
  };
  this.execute = function() {
    for(var i = 0; i <  board.shapes.length; i++) {
      if (board.shapes[i].name == args.figure) {
        board.shapes[i].visible(false);
        this.figure           = board.shapes[i];
        this.figure.isVisible = false;
        for (i in this.figure.ancestors) {
          this.figure.ancestors[i].visible(false);
          this.figure.ancestors[i].isVisible = false;
        }
      }
    }
    if (typeof this.figure === 'undefined') {
      throw ReferenceError("Could not find figure '" + args.figure + "'");
    }
    return args;
  };
};

module.exports = {
  delete_: delete_
};<commit_msg>can delete a single point<commit_after>/* Commands */

/*--
Interface Command {
  public void   constructor(JSXGraph board, object Arguments)
  public void   remove()
  public object execute()
}
--*/

var delete_ = function(board, args) {
  var args = args || {
    figure: $('input[name="figure"]:last').val()
  };
  this.remove = function() {
    this.figure.visible(true);
    this.figure.isVisible = true;
    for (i in this.figure.ancestors) {
      if (this.figure.ancestors.hasOwnProperty(i)) {
        this.figure.ancestors[i].visible(true);
        this.figure.ancestors[i].isVisible = true;
      }
    }
  };
  this.execute = function() {
    for(var i = 0; i <  board.shapes.length; i++) {
      if (board.shapes[i].name == args.figure) {
        board.shapes[i].visible(false);
        this.figure           = board.shapes[i];
        this.figure.isVisible = false;
        for (i in this.figure.ancestors) {
          this.figure.ancestors[i].visible(false);
          this.figure.ancestors[i].isVisible = false;
        }
      }
    }
    // try with a single point
    if (typeof this.figure === 'undefined') {
      for(p in board.points) {
        if (board.points.hasOwnProperty(p)) {
          if (board.points[p].name + '0' == args.figure) {
            this.figure           = board.points[p];
            this.figure.isVisible = false;
            this.figure.visible(false);
          }
        }
      }
    }
    if (typeof this.figure === 'undefined') {
      throw ReferenceError("Could not find figure '" + args.figure + "'");
    }
    return args;
  };
};

module.exports = {
  delete_: delete_
};<|endoftext|>


<commit_before>const got = require('got');
const time = require('../utils/time.js');

/* Stops */

// Get trips for stop.
async function getStopDepartures(id) {
	const now = time.getSeconds();
	const departures = JSON.parse((await got(`http://elron.ee/api/v1/stop?stop=${encodeURIComponent(id)}`, { timeout: 1000, retry: 1 })).body).data;
	if (!departures) throw new Error('Invalid response');

	return departures.filter((departure) => !departure.tegelik_aeg).map((departure) => ({
		tripId: departure.reis,
		routeId: departure.reis,
		name: departure.reis,
		description: departure.liin,
		type: 'train',
		time: time.toSeconds(departure.plaaniline_aeg),
		countdown: time.toSeconds(departure.plaaniline_aeg) - now,
		live: false,
		provider: 'elron'
	})).filter((departure) => now - departure.time < 600).slice(0, 15);
}

/* Routes */

// Get route by id.
async function getRoute(id) {
	const stops = JSON.parse((await got(`http://elron.ee/api/v1/trip?id=${id}`, { timeout: 600, retry: 1 })).body).data;
	if (!stops) throw new Error('Invalid response');
	if (!stops.length) return null;

	return {
		id,
		name: stops[0].peatus,
		description: '',
		type: 'train'
	};
}

// Get directions for route.
async function getRouteDirections(id) {
	const stops = JSON.parse((await got(`http://elron.ee/api/v1/trip?id=${id}`, { timeout: 600, retry: 1 })).body).data;
	if (!stops) throw new Error('Invalid response');

	return [
		{
			name: stops[0].peatus + ' - ' + stops[stops.length - 1].peatus,
			stops: stops.map((stop) => ({
				id: stop.peatus,
				name: stop.peatus,
				description: '',
				type: 'train'
			}))
		}
	];
}

module.exports = {
	getStopDepartures,
	getRoute,
	getRouteDirections
};
<commit_msg>Fix error when stop opened from train route view (api)<commit_after>const got = require('got');
const db = require('../db.js');
const cache = require('../utils/cache.js');
const time = require('../utils/time.js');

/* Stops */

// Get trips for stop.
async function getStopDepartures(id) {
	const now = time.getSeconds();
	const departures = JSON.parse((await got(`http://elron.ee/api/v1/stop?stop=${encodeURIComponent(id)}`, { timeout: 1000, retry: 1 })).body).data;
	if (!departures) throw new Error('Invalid response');

	return departures.filter((departure) => !departure.tegelik_aeg).map((departure) => ({
		tripId: departure.reis,
		routeId: departure.reis,
		name: departure.reis,
		description: departure.liin,
		type: 'train',
		time: time.toSeconds(departure.plaaniline_aeg),
		countdown: time.toSeconds(departure.plaaniline_aeg) - now,
		live: false,
		provider: 'elron'
	})).filter((departure) => now - departure.time < 600).slice(0, 15);
}

/* Routes */

// Get route by id.
async function getRoute(id) {
	const stops = JSON.parse((await got(`http://elron.ee/api/v1/trip?id=${id}`, { timeout: 600, retry: 1 })).body).data;
	if (!stops) throw new Error('Invalid response');
	if (!stops.length) return null;

	return {
		id,
		name: stops[0].peatus,
		description: '',
		type: 'train'
	};
}

// Get directions for route.
async function getRouteDirections(id) {
	const stops = JSON.parse((await got(`http://elron.ee/api/v1/trip?id=${id}`, { timeout: 600, retry: 1 })).body).data;
	if (!stops) throw new Error('Invalid response');
	const coords = await cache.use('elron', 'coords', () => db.query("SELECT id, lat, lng FROM stops WHERE type = 'train'"));

	return [
		{
			name: stops[0].peatus + ' - ' + stops[stops.length - 1].peatus,
			stops: stops.map((stop) => {
				const { lat, lng } = coords.find((coord) => coord.id === stop.peatus);

				return {
					id: stop.peatus,
					name: stop.peatus,
					description: '',
					type: 'train',
					lat,
					lng
				};
			})
		}
	];
}

module.exports = {
	getStopDepartures,
	getRoute,
	getRouteDirections
};
<|endoftext|>


<commit_before>import Promise from 'bluebird'
import Immutable from 'immutable'
import {flatten, forEach, omit} from 'lodash'
import {v4 as uuid} from 'node-uuid'

import {version as currentVersionString} from '../../package.json'

import {randomChar, countCredits} from 'sto-helpers'

import Schedule from './schedule'
import Study from './study'

let StudentRecord = Immutable.Record({
	id: null,
	name: null,
	version: currentVersionString,

	creditsNeeded: 35,

	matriculation: 1894,
	graduation: 1898,

	dateLastModified: null,
	dateCreated: null,

	studies: Immutable.OrderedMap(),
	schedules: Immutable.OrderedMap(),
	overrides: Immutable.OrderedMap(),
	fabrications: Immutable.OrderedMap(),

	settings: Immutable.Map(),
})

class Student extends StudentRecord {
	constructor(encodedStudent={}) {
		// let startTime = present()
		// Don't pass the list params into the StudentRecord constructor; it creates them as JS objects,
		// instead of our custom Studies, Schedules, and such.
		let toRemove = ['studies', 'schedules', 'overrides', 'fabrications', 'settings']
		let filtered = omit(encodedStudent, toRemove)
		let immutableStudent = Immutable.fromJS(filtered)

		super(immutableStudent)

		// Then add them manually.
		if (encodedStudent) {
			return this.withMutations((student) => {
				student = student.set('id', encodedStudent.id || uuid())
				student = student.set('name', encodedStudent.name || 'Student ' + randomChar())
				student = student.set('dateCreated', encodedStudent.dateCreated || new Date())
				student = student.set('dateLastModified', encodedStudent.dateLastModified || new Date())

				forEach((encodedStudent.studies || []), study => {
					student = student.addArea(study)
				})

				forEach((encodedStudent.schedules || []), schedule => {
					student = student.addSchedule(schedule)
				})

				forEach((encodedStudent.overrides || []), override => {
					student = student.addOverride(override)
				})

				forEach((encodedStudent.fabrications || []), fabrication => {
					student = student.addFabrication(fabrication)
				})

				forEach((encodedStudent.settings || {}), (value, key) => {
					student = student.changeSetting(key, value)
				})

				student = student.set('version', currentVersionString)

				// console.log('it took', present() - startTime, 'ms to make a student')

				return student
			})
		}
	}

	changeName(newName) {
		return this.set('name', newName)
	}

	changeCreditsNeeded(newCreditsNeeded) {
		return this.set('creditsNeeded', newCreditsNeeded)
	}

	changeMatriculation(newMatriculation) {
		return this.set('matriculation', newMatriculation)
	}

	changeGraduation(newGraduation) {
		return this.set('graduation', newGraduation)
	}

	changeSetting(key, value) {
		return this.setIn(['settings', key], value)
	}

	// schedule methods

	get schedulesByYear() {
		return this.schedules.groupBy(sched => sched.year)
	}

	get activeSchedules() {
		return this.schedules.filter(sched => sched.active).sortBy(sched => sched.semester)
	}

	addSchedule(newSchedule) {
		let sched = new Schedule(newSchedule)
		return this.setIn(['schedules', sched.id], sched)
	}

	destroySchedule(scheduleId) {
		// console.log(`removing schedule ${scheduleId}`)

		let deadSched = this.getIn(['schedules', scheduleId])
		let scheduleIsNoMore = this.set('schedules', this.schedules.delete(scheduleId))

		if (deadSched && deadSched.active) {
			let otherSchedKey = scheduleIsNoMore.findKey((sched) =>
				sched.year === deadSched.year &&
				sched.semester === deadSched.semester &&
				sched.id !== deadSched.id)

			if (otherSchedKey) {
				return scheduleIsNoMore.setIn(['schedules', otherSchedKey, 'active'], true)
			}
		}
		return scheduleIsNoMore
	}

	destroyMultipleSchedules(ids) {
		return this.withMutations((student) => {
			// console.groupCollapsed('destroyMultipleSchedules')
			Immutable.Seq(ids).forEach((id) => {
				// console.log('destroyMultipleSchedules', id)
				student = student.destroySchedule(id)
			})
			// console.groupEnd('destroyMultipleSchedules')
			return student
		})
	}

	moveCourse(fromScheduleId, toScheduleId, clbid) {
		// console.log(`moving course ${clbid} from schedule ${fromScheduleId} to schedule ${toScheduleId}`)
		return this.withMutations((student) => {
			student = student.setIn(['schedules', fromScheduleId], student.getIn(['schedules', fromScheduleId]).removeCourse(clbid))
			student = student.setIn(['schedules', toScheduleId], student.getIn(['schedules', toScheduleId]).addCourse(clbid))
		})
	}

	// area-of-study methods

	get areasByType() {
		return this.studies.groupBy(study => study.type || 'Unknown')
	}

	addArea(areaOfStudy) {
		let study = new Study(areaOfStudy)
		return this.setIn(['studies', study.id], study)
	}

	removeArea(id) {
		return this.set('studies', this.studies.delete(id))
	}

	removeMultipleAreas(ids) {
		return this.withMutations((student) => {
			Immutable.Seq(ids).forEach((id) => {
				student = student.removeArea(id)
			})
			return student
		})
	}

	// override methods

	addOverride(override) {
		return this.setIn(['overrides', override.what], override)
	}

	removeOverride(thingOverriden) {
		return this.set('overrides', this.overrides.delete(thingOverriden))
	}

	// fabrication methods

	addFabrication(fabrication) {
		return this.setIn(['fabrications', fabrication.id], fabrication)
	}

	removeFabrication(fabricationId) {
		return this.set('fabrications', this.fabrications.delete(fabricationId))
	}

	// getters

	get courses() {
		let allCourses = this.activeSchedules.map((schedule) => schedule.courses).toArray()
		let scheduleCoursePromises = Promise.all(allCourses)
		return scheduleCoursePromises.then((results) => flatten(results))
	}

	get creditCount() {
		return this.courses.then(countCredits)
	}

	// helpers

	data() {
		return Promise.props({
			courses: this.courses,
			fabrications: this.fabrications.toList().toJS(),
			overrides: this.overrides.toList().toJS(),
			studies: this.studies.toList().toJS(),
			creditsNeeded: this.creditsNeeded,
			graduation: this.graduation,
			matriculation: this.matriculation,
		})
	}

	encode() {
		return encodeURIComponent(JSON.stringify(this))
	}

	save() {
		let oldVersion = localStorage.getItem(this.id)
		let stringified = JSON.stringify(this)
		if (oldVersion !== stringified) {
			console.log(`saving student ${this.name} (${this.id})`)
			let student = this.set('dateLastModified', new Date())
			localStorage.setItem(student.id, stringified)
		}
	}
}

export default Student
<commit_msg>Implement student#checkGraduatability, with graduatability caching<commit_after>import Promise from 'bluebird'
import Immutable from 'immutable'
import {flatten, forEach, omit} from 'lodash'
import {v4 as uuid} from 'node-uuid'

import {version as currentVersionString} from '../../package.json'

import {randomChar, countCredits} from 'sto-helpers'

import Schedule from './schedule'
import Study from './study'

import checkStudentGraduatability from '../helpers/checkStudentGraduatability'

let StudentRecord = Immutable.Record({
	id: null,
	name: null,
	version: currentVersionString,

	creditsNeeded: 35,

	matriculation: 1894,
	graduation: 1898,

	dateLastModified: null,
	dateCreated: null,

	studies: Immutable.OrderedMap(),
	schedules: Immutable.OrderedMap(),
	overrides: Immutable.OrderedMap(),
	fabrications: Immutable.OrderedMap(),

	settings: Immutable.Map(),
})

class Student extends StudentRecord {
	constructor(encodedStudent={}) {
		// let startTime = present()
		// Don't pass the list params into the StudentRecord constructor; it creates them as JS objects,
		// instead of our custom Studies, Schedules, and such.
		let toRemove = ['studies', 'schedules', 'overrides', 'fabrications', 'settings']
		let filtered = omit(encodedStudent, toRemove)
		let immutableStudent = Immutable.fromJS(filtered)

		super(immutableStudent)

		// Then add them manually.
		if (encodedStudent) {
			return this.withMutations((student) => {
				student = student.set('id', encodedStudent.id || uuid())
				student = student.set('name', encodedStudent.name || 'Student ' + randomChar())
				student = student.set('dateCreated', encodedStudent.dateCreated || new Date())
				student = student.set('dateLastModified', encodedStudent.dateLastModified || new Date())

				forEach((encodedStudent.studies || []), study => {
					student = student.addArea(study)
				})

				forEach((encodedStudent.schedules || []), schedule => {
					student = student.addSchedule(schedule)
				})

				forEach((encodedStudent.overrides || []), override => {
					student = student.addOverride(override)
				})

				forEach((encodedStudent.fabrications || []), fabrication => {
					student = student.addFabrication(fabrication)
				})

				forEach((encodedStudent.settings || {}), (value, key) => {
					student = student.changeSetting(key, value)
				})

				student = student.set('version', currentVersionString)

				// console.log('it took', present() - startTime, 'ms to make a student')

				return student
			})
		}
	}

	changeName(newName) {
		return this.set('name', newName)
	}

	changeCreditsNeeded(newCreditsNeeded) {
		return this.set('creditsNeeded', newCreditsNeeded)
	}

	changeMatriculation(newMatriculation) {
		return this.set('matriculation', newMatriculation)
	}

	changeGraduation(newGraduation) {
		return this.set('graduation', newGraduation)
	}

	changeSetting(key, value) {
		return this.setIn(['settings', key], value)
	}

	// schedule methods

	get schedulesByYear() {
		return this.schedules.groupBy(sched => sched.year)
	}

	get activeSchedules() {
		return this.schedules.filter(sched => sched.active).sortBy(sched => sched.semester)
	}

	addSchedule(newSchedule) {
		let sched = new Schedule(newSchedule)
		return this.setIn(['schedules', sched.id], sched)
	}

	destroySchedule(scheduleId) {
		// console.log(`removing schedule ${scheduleId}`)

		let deadSched = this.getIn(['schedules', scheduleId])
		let scheduleIsNoMore = this.set('schedules', this.schedules.delete(scheduleId))

		if (deadSched && deadSched.active) {
			let otherSchedKey = scheduleIsNoMore.findKey((sched) =>
				sched.year === deadSched.year &&
				sched.semester === deadSched.semester &&
				sched.id !== deadSched.id)

			if (otherSchedKey) {
				return scheduleIsNoMore.setIn(['schedules', otherSchedKey, 'active'], true)
			}
		}
		return scheduleIsNoMore
	}

	destroyMultipleSchedules(ids) {
		return this.withMutations((student) => {
			// console.groupCollapsed('destroyMultipleSchedules')
			Immutable.Seq(ids).forEach((id) => {
				// console.log('destroyMultipleSchedules', id)
				student = student.destroySchedule(id)
			})
			// console.groupEnd('destroyMultipleSchedules')
			return student
		})
	}

	moveCourse(fromScheduleId, toScheduleId, clbid) {
		// console.log(`moving course ${clbid} from schedule ${fromScheduleId} to schedule ${toScheduleId}`)
		return this.withMutations((student) => {
			student = student.setIn(['schedules', fromScheduleId], student.getIn(['schedules', fromScheduleId]).removeCourse(clbid))
			student = student.setIn(['schedules', toScheduleId], student.getIn(['schedules', toScheduleId]).addCourse(clbid))
		})
	}

	// area-of-study methods

	get areasByType() {
		return this.studies.groupBy(study => study.type || 'Unknown')
	}

	addArea(areaOfStudy) {
		let study = new Study(areaOfStudy)
		return this.setIn(['studies', study.id], study)
	}

	removeArea(id) {
		return this.set('studies', this.studies.delete(id))
	}

	removeMultipleAreas(ids) {
		return this.withMutations((student) => {
			Immutable.Seq(ids).forEach((id) => {
				student = student.removeArea(id)
			})
			return student
		})
	}

	// override methods

	addOverride(override) {
		return this.setIn(['overrides', override.what], override)
	}

	removeOverride(thingOverriden) {
		return this.set('overrides', this.overrides.delete(thingOverriden))
	}

	// fabrication methods

	addFabrication(fabrication) {
		return this.setIn(['fabrications', fabrication.id], fabrication)
	}

	removeFabrication(fabricationId) {
		return this.set('fabrications', this.fabrications.delete(fabricationId))
	}

	// getters

	get courses() {
		let allCourses = this.activeSchedules.map((schedule) => schedule.courses).toArray()
		let scheduleCoursePromises = Promise.all(allCourses)
		return scheduleCoursePromises.then((results) => flatten(results))
	}

	get creditCount() {
		return this.courses.then(countCredits)
	}

	// helpers

	checkGraduatability() {
		if (!this.graduatability) {
			this.graduatability = checkStudentGraduatability(this)
		}
		return this.graduatability
	}

	data() {
		return Promise.props({
			courses: this.courses,
			fabrications: this.fabrications.toList().toJS(),
			overrides: this.overrides.toList().toJS(),
			studies: this.studies.toList().toJS(),
			creditsNeeded: this.creditsNeeded,
			graduation: this.graduation,
			matriculation: this.matriculation,
		})
	}

	encode() {
		return encodeURIComponent(JSON.stringify(this))
	}

	save() {
		let oldVersion = localStorage.getItem(this.id)
		let stringified = JSON.stringify(this)
		if (oldVersion !== stringified) {
			console.log(`saving student ${this.name} (${this.id})`)
			let student = this.set('dateLastModified', new Date())
			localStorage.setItem(student.id, stringified)
		}
	}
}

export default Student
<|endoftext|>


<commit_before>/*
	GEIESPICTURES - the
	Author: Marco Faustinelli (contacts@faustinelli.net)
	Web: http://faustinelli.net/
	     http://faustinelli.wordpress.com/
	Version: 0.1

	The MIT License - Copyright (c) 2016 Geiespictures Project
*/

var expect = chai.expect;

describe('a sound picture system requires', function () {

  it('a series of vector operations that allow to sum, subtract and scale vectors', function() {
//		expect(head(head(listlA_BA))).to.be.equal('a');
//		expect(isEmpty(tail(head(listlA_BA)))).to.be.ok;
//		expect(isEmpty(tail(EMPTY))).to.be.not.ok;

    var pippo = V.make_vect(-3,1);
    var pluto = V.make_vect(-2,3);
    var luigi = V.add_vect(pippo, pluto);
    var luigi2 = V.sub_vect(pippo, pluto);
    expect(luigi.c).to.be.equal('[-5,4]');
    expect(luigi2.c).to.be.equal('[-1,-2]');

  });
  it('definitions for fold left and right', function() {

    expect(L.fold((x,ys)=>x+ys,0,L.ArrayToList([1,3,5]))).to.be.equal(9);
    expect(L.foldl((acc,x)=>acc+x,0,L.ArrayToList([1,3,6]))).to.be.equal(10);

  });

});
describe('a sound picture system entails', function () {

  it('functions to handle frames', function() {
//		expect(head(head(listlA_BA))).to.be.equal('a');
//		expect(isEmpty(tail(head(listlA_BA)))).to.be.ok;
//		expect(isEmpty(tail(EMPTY))).to.be.not.ok;

    var origin = V.make_vect(-3,1);
    var edge1 = V.make_vect(5,3);
    var edge2 = V.make_vect(-2,3);
    var fram1 = P.make_frame(origin, edge1, edge2);

    expect(P.origin_frame(fram1).c).to.be.equal('[-3,1]');
    expect(P.edge1_frame(fram1).c).to.be.equal('[5,3]');
    expect(P.edge2_frame(fram1).c).to.be.equal('[-2,3]');

  });
  it('a coordinateMapper', function() {

    var origin = V.make_vect(-3,1);
    var edge1 = V.make_vect(5,3);
    var edge2 = V.make_vect(-2,3);
    var fram1 = P.make_frame(origin, edge1, edge2);

    var coordinateMapper1 = P.frame_coord_map(fram1);
    var unitVec = V.make_vect(1,1);
    var testVec = V.make_vect(2,2);
    var mappedUnit = coordinateMapper1(unitVec);
    var mappedTest = coordinateMapper1(testVec);

    expect(mappedUnit.c).to.be.equal('[0,7]');
    expect(mappedTest.c).to.be.equal('[3,13]');

  });

});
<commit_msg>Update mocha_geiespictures_tests.js<commit_after>/*
	GEIESPICTURES - The picture language from chapter 2 of SICP
	Author: Marco Faustinelli (contacts@faustinelli.net)
	Web: http://faustinelli.net/
	     http://faustinelli.wordpress.com/
	Version: 0.1 - Requires Geieslists 1.1 and Geiesvectors 0.1

	The MIT License - Copyright (c) 2016 Geiespictures Project
*/

var expect = chai.expect;
//		expect(head(head(listlA_BA))).to.be.equal('a');
//		expect(isEmpty(tail(head(listlA_BA)))).to.be.ok;
//		expect(isEmpty(tail(EMPTY))).to.be.not.ok;

var probe = () => { return {}; }

var fakeCtx = () => probe => { return {
    canvas: {
        clientWidth: 400,
        clientHeight: 200
    },
    beginPath: () => {},
    moveTo: (x,y) => { probe.startX = x; probe.startY = y; },
    lineTo: (x,y) => { probe.endX = x; probe.endY = y; },
    stroke: () => {}
    };
};

describe('a sound picture system requires', function () {

  it('a series of vector operations that allow to sum, subtract and scale vectors', function() {

    var pippo = V.make_vect(-3,1);
    var pluto = V.make_vect(-2,3);
    var luigi = V.add_vect(pippo, pluto);
    var luigi2 = V.sub_vect(pippo, pluto);
    expect(luigi.c).to.be.equal('[-5,4]');
    expect(luigi2.c).to.be.equal('[-1,-2]');

  });
  it('definitions for fold left and right', function() {

    expect(L.fold((x,ys)=>x+ys,0,L.ArrayToList([1,3,5]))).to.be.equal(9);
    expect(L.foldl((acc,x)=>acc+x,0,L.ArrayToList([1,3,6]))).to.be.equal(10);

  });

});
describe('a sound picture system entails', function () {

  it('functions to handle frames', function() {

    var origin = V.make_vect(-3,1);
    var edge1 = V.make_vect(5,3);
    var edge2 = V.make_vect(-2,3);
    var fram1 = P.make_frame(origin, edge1, edge2);

    expect(P.origin_frame(fram1).c).to.be.equal('[-3,1]');
    expect(P.edge1_frame(fram1).c).to.be.equal('[5,3]');
    expect(P.edge2_frame(fram1).c).to.be.equal('[-2,3]');

  });
  it('a coordinateMapper', function() {

    var origin = V.make_vect(-3,1);
    var edge1 = V.make_vect(5,3);
    var edge2 = V.make_vect(-2,3);
    var fram1 = P.make_frame(origin, edge1, edge2);

    var coordinateMapper1 = P.frame_coord_map(fram1);
    var unitVec = V.make_vect(1,1);
    var testVec = V.make_vect(2,2);
    var mappedUnit = coordinateMapper1(unitVec);
    var mappedTest = coordinateMapper1(testVec);

    expect(mappedUnit.c).to.be.equal('[0,7]');
    expect(mappedTest.c).to.be.equal('[3,13]');

  });
  it('a segments painter that maps frames into DOM canvas, adapting the coordinates', function() {

    var origin = V.make_vect(-100,25);
    var edge1 = V.make_vect(0,50);
    var edge2 = V.make_vect(-100,0);
    var fram1 = P.make_frame(origin, edge1, edge2);

    var testProbe = probe();
    var fakeContext = fakeCtx()(testProbe)

    // drawing a diagonal from frame origin to opposite corner
    P.segments_painter([P.make_segment(V.make_vect(0,0),V.make_vect(1,1))])(fram1)(fakeContext);

    expect(testProbe.startX).to.be.equal(100);
    expect(testProbe.startY).to.be.equal(75);
    expect(testProbe.endX).to.be.equal(0);
    expect(testProbe.endY).to.be.equal(25);

  });
});
<|endoftext|>