<commit_before>import React from 'react';
import Backbone from 'backbone';
import Router from 'react-router';
import stores from 'stores';
import Glyphicon from 'components/common/Glyphicon.react';
import actions from 'actions';
import ResourceActions from 'actions/ResourceActions';

let RouteHandler = Router.RouteHandler;

let ResourceRequest = React.createClass({
    mixins: [Router.State],

    getInitialState: function(){
      return {
        AUSearch: null,
        currentAllocation: null,
        currentQuota: null,
        quotaSearch: null,
        delta: 525600,
        expire: true,
        response: "",
        request: null,
        canSubmit: false,
        displayAdmin: false
      };
    },

    componentWillReceiveProps: function(){
        // If new props were received but the existing component was not unmounted, manually set initial state
        this.setState(this.getInitialState(), this.updateState);
    },

    componentDidMount: function(){
        stores.ResourceRequestStore.addChangeListener(this.updateState);
        stores.AllocationStore.addChangeListener(this.updateState);
        stores.QuotaStore.addChangeListener(this.updateState);

        stores.AllocationStore.getAll();
        stores.QuotaStore.getAll();

        // In case all stores have already been loaded, call updateState manually once.
        this.updateState();
    },

    componentWillUnmount: function(){
        stores.ResourceRequestStore.removeChangeListener(this.updateState);
        stores.AllocationStore.removeChangeListener(this.updateState);
        stores.QuotaStore.removeChangeListener(this.updateState);
    },

    updateState: function(){
        var request = this.state.request || stores.ResourceRequestStore.get(this.getParams().id);
        var quotaId = request.get('current_quota');
        var allocationId = request.get('current_allocation');
        var currentQuota = this.state.currentQuota || stores.QuotaStore.get(quotaId);
        var currentAllocation = this.state.currentAllocation || stores.AllocationStore.get(allocationId);

        // Wait until all data has been fetched from the stores
        if(!request || !currentQuota || !currentAllocation){
            return;
        }

        // Set quota search to current quota if state's quota doesn't exist yet
        function setQuota(){
            return{
                cpu: currentQuota.get('cpu'),
                memory: currentQuota.get('memory'),
                storage: currentQuota.get('storage'),
                storage_count: currentQuota.get('storage_count'),
                suspended_count: currentQuota.get('suspended_count')
            };
        };

        var quotaSearch = this.state.quotaSearch || (currentQuota && setQuota());
        var AUSearch = this.state.AUSearch || currentAllocation.get('threshold') / 60;

        this.setState({
            request: request,
            currentQuota: currentQuota,
            currentAllocation: currentAllocation,
            AUSearch: AUSearch,
            quotaSearch: quotaSearch
        });
    },

    canSubmit: function(){
        var allocationExists = stores.AllocationStore.findWhere({
            threshold: this.state.AUSearch * 60,
            delta: this.state.delta
        }).length == 1;
        var quotaExists = stores.QuotaStore.findWhere(this.state.quotaSearch).length == 1;
        return this.state.response && allocationExists && quotaExists
    },

    handleDisplayChange: function (event){
      this.setState({displayAdmin: !this.state.displayAdmin});
    },

    handleResponseChange: function (event) {
      var response = event.target.value;
      if (response) this.setState({response: response});
    },

    handleQuotaChange: function (event) {
      var quota = event.target.value;
      if (quota) this.setState({quota: quota});
    },

    handleAllocationChange: function (event) {
      var allocation = event.target.value;
      if (allocation) this.setState({allocation: allocation});
    },

    handleApproval: function(e){
        e.preventDefault();
        var resourceRequest = stores.ResourceRequestStore.get(this.getParams().id),
            quota = stores.QuotaStore.findWhere(this.state.quotaSearch).models[0].id,
            allocation = stores.AllocationStore.findWhere({
                threshold: this.state.AUSearch * 60,
                delta: this.state.delta
            }).models[0].id,
            status = stores.StatusStore.findOne({name: "approved"});

        ResourceActions.update({
          request: resourceRequest,
          response: this.state.response,
          quota: quota,
          allocation: allocation,
          status: status.id
        });

    },

    handleDenial: function(e){
      e.preventDefault();
      var resourceRequest = stores.ResourceRequestStore.get(this.getParams().id),
        status = stores.StatusStore.findOne({name: "rejected"});

      ResourceActions.update({
        request: resourceRequest,
        response: this.state.response,
        status: status.id
      });
    },

    handleThresholdSearchChange: function(e){
      this.setState({
        AUSearch: e.target.value
      });
    },

    makeNewQuota: function(e){
        e.preventDefault();
        actions.QuotaActions.create(this.state.quotaSearch);
    },

    makeNewAllocation: function(e){
        e.preventDefault();
        actions.AllocationActions.create({"threshold": this.state.AUSearch * 60, "delta": this.state.delta});
    },

    onExpireChange: function(e){
      // If expire is currently true, we want it to be false. Set delta to -1 for non expiring AU, standard 525600 for expiring.
      this.state.expire ? this.setState({delta: -1}) : this.setState({delta: 525600});
      this.setState({expire: !this.state.expire});
    },

    renderAllocationStatus: function(){
        if(stores.AllocationStore.findWhere({"threshold": parseInt(this.state.AUSearch) * 60, "delta": this.state.delta}).length < 1){
            return(

Allocation with {this.state.AUSearch} AU expiring: {this.state.expire ? "true":"false"} does not exist. Click here to create it.

            );
        }
        return(

                <Glyphicon name="ok" /> Allocation exists

        );
    },

    renderQuotaStatus: function(){
        if(stores.QuotaStore.findWhere(this.state.quotaSearch).length < 1){
            var quota = this.state.quotaSearch;
            return(

                    Quota does not exist. Click here to create it.

            );
        }
        return(

                <Glyphicon name="ok" /> Quota exists

        );
    },

    onQuotaSearchChange: function(e){
        var currentQuotaSearch = this.state.quotaSearch;
        currentQuotaSearch[e.target.id] = Number(e.target.value);
        this.setState({
            quotaSearch: currentQuotaSearch
        });
    },

    renderAdminDetails: function(){
        var request = this.state.request,
            currentQuota = this.state.currentQuota,
            currentAllocation = this.state.currentAllocation,
            quotaSearch = this.state.quotaSearch,
            statuses = stores.StatusStore.getAll(),
            currentQuotaString = 'N/A',
            currentAllocationString = 'N/A';

        if (!request || !currentQuota || !currentAllocation || !statuses || !quotaSearch) return
;

        var currentCPU = "  CPU: " + currentQuota.get('cpu'),
          currentMemory = "  Memory: " + currentQuota.get('memory'),
          currentStorage = "  Storage: " + currentQuota.get('storage'),
          currentStorageCount = "  Storage Count: " + currentQuota.get('storage_count'),
          currentSuspendedCount = "  Suspended: " + currentQuota.get('suspended_count'),
          currentThreshold = "  Threshold: " + currentAllocation.get('threshold') + " (" + (currentAllocation.get('threshold') / 60) + " AU)",
          currentDelta = "  Delta: " + currentAllocation.get('delta'),
          currentQuotaString = currentCPU + currentMemory + currentStorage + currentStorageCount + currentSuspendedCount,
          currentAllocationString = currentThreshold + currentDelta;

        return(

For user:

{request.get('user').username}

Created by:

{request.get('created_by').username}

Request:
{request.get('request')}

Description:
{request.get('description')}

Admin message:
{request.get('admin_message')}

Current quota:
{currentQuotaString}

New quota:

                CPU:
                <input className="form-control" id="cpu" type="number" value={this.state.quotaSearch.cpu} onChange={this.onQuotaSearchChange} />
                Memory (GB):
                <input className="form-control" id="memory" type="number" value={this.state.quotaSearch.memory} onChange={this.onQuotaSearchChange} />
                Storage:
                <input className="form-control" id="storage" type="number" value={this.state.quotaSearch.storage} onChange={this.onQuotaSearchChange} />
                Storage Count:
                <input className="form-control" id="storage_count" type="number" value={this.state.quotaSearch.storage_count} onChange={this.onQuotaSearchChange} />
                Suspended Count:
                <input className="form-control" id="suspended_count" type="number" value={this.state.quotaSearch.suspended_count} onChange={this.onQuotaSearchChange} />
                {this.renderQuotaStatus()}

Current allocation:
{currentAllocationString}

New allocation:

                    AU:
                    <input id="au" className="form-control" type="number" value={this.state.AUSearch} onChange={this.handleThresholdSearchChange} />

Expiring allocation?

                  <input type="radio" name="expire" checked={!!this.state.expire} onChange={this.onExpireChange} value="Yes"/>
                  <input type="radio" name="expire" checked={!!this.state.expire} onChange={this.onExpireChange} value="No"/>

                {this.renderAllocationStatus()}

Response:

                <textarea type="text" form="admin" value={this.state.value} onChange={this.handleResponseChange}/>

                <button disabled={!this.canSubmit()} onClick={this.handleApproval} type="button" className="btn btn-default btn-sm">Approve</button>
                <button onClick={this.handleDenial} type="button" className="btn btn-default btn-sm">Deny</button>

        );
    },

    render: function () {
      return (

          {this.renderAdminDetails()}
          <RouteHandler />

      );
    }
});

export default ResourceRequest;
<commit_msg>Fix test if quota/alloc exists<commit_after>import React from 'react';
import Backbone from 'backbone';
import Router from 'react-router';
import stores from 'stores';
import Glyphicon from 'components/common/Glyphicon.react';
import actions from 'actions';
import ResourceActions from 'actions/ResourceActions';

let RouteHandler = Router.RouteHandler;

let ResourceRequest = React.createClass({
    mixins: [Router.State],

    getInitialState: function(){
      return {
        AUSearch: null,
        currentAllocation: null,
        currentQuota: null,
        quotaSearch: null,
        delta: 525600,
        expire: true,
        response: "",
        request: null,
        canSubmit: false,
        displayAdmin: false
      };
    },

    componentWillReceiveProps: function(){
        // If new props were received but the existing component was not unmounted, manually set initial state
        this.setState(this.getInitialState(), this.updateState);
    },

    componentDidMount: function(){
        stores.ResourceRequestStore.addChangeListener(this.updateState);
        stores.AllocationStore.addChangeListener(this.updateState);
        stores.QuotaStore.addChangeListener(this.updateState);

        stores.AllocationStore.getAll();
        stores.QuotaStore.getAll();

        // In case all stores have already been loaded, call updateState manually once.
        this.updateState();
    },

    componentWillUnmount: function(){
        stores.ResourceRequestStore.removeChangeListener(this.updateState);
        stores.AllocationStore.removeChangeListener(this.updateState);
        stores.QuotaStore.removeChangeListener(this.updateState);
    },

    updateState: function(){
        var request = this.state.request || stores.ResourceRequestStore.get(this.getParams().id);
        var quotaId = request.get('current_quota');
        var allocationId = request.get('current_allocation');
        var currentQuota = this.state.currentQuota || stores.QuotaStore.get(quotaId);
        var currentAllocation = this.state.currentAllocation || stores.AllocationStore.get(allocationId);

        // Wait until all data has been fetched from the stores
        if(!request || !currentQuota || !currentAllocation){
            return;
        }

        // Set quota search to current quota if state's quota doesn't exist yet
        function setQuota(){
            return{
                cpu: currentQuota.get('cpu'),
                memory: currentQuota.get('memory'),
                storage: currentQuota.get('storage'),
                storage_count: currentQuota.get('storage_count'),
                suspended_count: currentQuota.get('suspended_count')
            };
        };

        var quotaSearch = this.state.quotaSearch || (currentQuota && setQuota());
        var AUSearch = this.state.AUSearch || currentAllocation.get('threshold') / 60;

        this.setState({
            request: request,
            currentQuota: currentQuota,
            currentAllocation: currentAllocation,
            AUSearch: AUSearch,
            quotaSearch: quotaSearch
        });
    },

    canSubmit: function(){
        var allocationExists = stores.AllocationStore.findWhere({
            threshold: this.state.AUSearch * 60,
            delta: this.state.delta
        }).length > 0;
        var quotaExists = stores.QuotaStore.findWhere(this.state.quotaSearch).length > 0;
        return this.state.response && allocationExists && quotaExists
    },

    handleDisplayChange: function (event){
      this.setState({displayAdmin: !this.state.displayAdmin});
    },

    handleResponseChange: function (event) {
      var response = event.target.value;
      if (response) this.setState({response: response});
    },

    handleQuotaChange: function (event) {
      var quota = event.target.value;
      if (quota) this.setState({quota: quota});
    },

    handleAllocationChange: function (event) {
      var allocation = event.target.value;
      if (allocation) this.setState({allocation: allocation});
    },

    handleApproval: function(e){
        e.preventDefault();
        var resourceRequest = stores.ResourceRequestStore.get(this.getParams().id),
            quota = stores.QuotaStore.findWhere(this.state.quotaSearch).models[0].id,
            allocation = stores.AllocationStore.findWhere({
                threshold: this.state.AUSearch * 60,
                delta: this.state.delta
            }).models[0].id,
            status = stores.StatusStore.findOne({name: "approved"});

        ResourceActions.update({
          request: resourceRequest,
          response: this.state.response,
          quota: quota,
          allocation: allocation,
          status: status.id
        });

    },

    handleDenial: function(e){
      e.preventDefault();
      var resourceRequest = stores.ResourceRequestStore.get(this.getParams().id),
        status = stores.StatusStore.findOne({name: "rejected"});

      ResourceActions.update({
        request: resourceRequest,
        response: this.state.response,
        status: status.id
      });
    },

    handleThresholdSearchChange: function(e){
      this.setState({
        AUSearch: e.target.value
      });
    },

    makeNewQuota: function(e){
        e.preventDefault();
        actions.QuotaActions.create(this.state.quotaSearch);
    },

    makeNewAllocation: function(e){
        e.preventDefault();
        actions.AllocationActions.create({"threshold": this.state.AUSearch * 60, "delta": this.state.delta});
    },

    onExpireChange: function(e){
      // If expire is currently true, we want it to be false. Set delta to -1 for non expiring AU, standard 525600 for expiring.
      this.state.expire ? this.setState({delta: -1}) : this.setState({delta: 525600});
      this.setState({expire: !this.state.expire});
    },

    renderAllocationStatus: function(){
        if(stores.AllocationStore.findWhere({"threshold": parseInt(this.state.AUSearch) * 60, "delta": this.state.delta}).length < 1){
            return(

Allocation with {this.state.AUSearch} AU expiring: {this.state.expire ? "true":"false"} does not exist. Click here to create it.

            );
        }
        return(

                <Glyphicon name="ok" /> Allocation exists

        );
    },

    renderQuotaStatus: function(){
        if(stores.QuotaStore.findWhere(this.state.quotaSearch).length < 1){
            var quota = this.state.quotaSearch;
            return(

                    Quota does not exist. Click here to create it.

            );
        }
        return(

                <Glyphicon name="ok" /> Quota exists

        );
    },

    onQuotaSearchChange: function(e){
        var currentQuotaSearch = this.state.quotaSearch;
        currentQuotaSearch[e.target.id] = Number(e.target.value);
        this.setState({
            quotaSearch: currentQuotaSearch
        });
    },

    renderAdminDetails: function(){
        var request = this.state.request,
            currentQuota = this.state.currentQuota,
            currentAllocation = this.state.currentAllocation,
            quotaSearch = this.state.quotaSearch,
            statuses = stores.StatusStore.getAll(),
            currentQuotaString = 'N/A',
            currentAllocationString = 'N/A';

        if (!request || !currentQuota || !currentAllocation || !statuses || !quotaSearch) return
;

        var currentCPU = "  CPU: " + currentQuota.get('cpu'),
          currentMemory = "  Memory: " + currentQuota.get('memory'),
          currentStorage = "  Storage: " + currentQuota.get('storage'),
          currentStorageCount = "  Storage Count: " + currentQuota.get('storage_count'),
          currentSuspendedCount = "  Suspended: " + currentQuota.get('suspended_count'),
          currentThreshold = "  Threshold: " + currentAllocation.get('threshold') + " (" + (currentAllocation.get('threshold') / 60) + " AU)",
          currentDelta = "  Delta: " + currentAllocation.get('delta'),
          currentQuotaString = currentCPU + currentMemory + currentStorage + currentStorageCount + currentSuspendedCount,
          currentAllocationString = currentThreshold + currentDelta;

        return(

For user:

{request.get('user').username}

Created by:

{request.get('created_by').username}

Request:
{request.get('request')}

Description:
{request.get('description')}

Admin message:
{request.get('admin_message')}

Current quota:
{currentQuotaString}

New quota:

                CPU:
                <input className="form-control" id="cpu" type="number" value={this.state.quotaSearch.cpu} onChange={this.onQuotaSearchChange} />
                Memory (GB):
                <input className="form-control" id="memory" type="number" value={this.state.quotaSearch.memory} onChange={this.onQuotaSearchChange} />
                Storage:
                <input className="form-control" id="storage" type="number" value={this.state.quotaSearch.storage} onChange={this.onQuotaSearchChange} />
                Storage Count:
                <input className="form-control" id="storage_count" type="number" value={this.state.quotaSearch.storage_count} onChange={this.onQuotaSearchChange} />
                Suspended Count:
                <input className="form-control" id="suspended_count" type="number" value={this.state.quotaSearch.suspended_count} onChange={this.onQuotaSearchChange} />
                {this.renderQuotaStatus()}

Current allocation:
{currentAllocationString}

New allocation:

                    AU:
                    <input id="au" className="form-control" type="number" value={this.state.AUSearch} onChange={this.handleThresholdSearchChange} />

Expiring allocation?

                  <input type="radio" name="expire" checked={!!this.state.expire} onChange={this.onExpireChange} value="Yes"/>
                  <input type="radio" name="expire" checked={!!this.state.expire} onChange={this.onExpireChange} value="No"/>

                {this.renderAllocationStatus()}

Response:

                <textarea type="text" form="admin" value={this.state.value} onChange={this.handleResponseChange}/>

                <button disabled={!this.canSubmit()} onClick={this.handleApproval} type="button" className="btn btn-default btn-sm">Approve</button>
                <button onClick={this.handleDenial} type="button" className="btn btn-default btn-sm">Deny</button>

        );
    },

    render: function () {
      return (

          {this.renderAdminDetails()}
          <RouteHandler />

      );
    }
});

export default ResourceRequest;
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';

export default class AutoRefreshTimer extends React.Component {
    static propTypes = {
        fontSizeScale: PropTypes.number,
        fontColor: PropTypes.string,
        textAlign: PropTypes.string,
        getState: PropTypes.func,
        onDoubleClick: PropTypes.func,
    };

    static getTimerStyles(fontSizeScale, fontColor) {
        let timeFontSize = 64;
        let fractionFontSize = 36;
        if (fontSizeScale && !fontSizeScale.isNaN && fontSizeScale > 0) {
            timeFontSize *= fontSizeScale;
            fractionFontSize *= fontSizeScale;
        }
        const color = `#${fontColor || 'ea7500'}`;
        const timeStyle = { fontSize: `${timeFontSize}pt`, color };
        const fractionStyle = { fontSize: `${fractionFontSize}pt`, color };
        return { timeStyle, fractionStyle }
    }

    constructor(props) {
        super(props);

        let justifyContent = 'center';
        if (this.props.textAlign === 'left') {
            justifyContent = 'flex-start';
        } else if (this.props.textAlign === 'right') {
            justifyContent = 'flex-end';
        }

        this.state = {
            timerTime: '',
            timerFraction: '',
            ...AutoRefreshTimer.getTimerStyles(
                this.props.fontSizeScale, this.props.fontColor),
            justifyContent,
        };

        this.runUpdate = this.runUpdate.bind(this);
    }

    componentWillReceiveProps(nextProps) {
        this.setState({
            ...AutoRefreshTimer.getTimerStyles(
                nextProps.fontSizeScale, nextProps.fontColor),
        })
    }

    componentWillMount() {
        this.runUpdate();
    }

    updateTimer() {
        const timerState = this.props.getState();
        this.setState({
            timerTime: timerState.time,
            timerFraction: timerState.fraction,
        });
    }

    runUpdate() {
        this.updateTimer();
        requestAnimationFrame(this.runUpdate);
    }

    render() {
        const { timeStyle, timerTime, fractionStyle, timerFraction, justifyContent } = this.state;
        return (

                    {timerTime}
                    {timerFraction}

        );
    }
}
<commit_msg>Fix textAlign on other browsers<commit_after>import React from 'react';
import PropTypes from 'prop-types';

export default class AutoRefreshTimer extends React.Component {
    static propTypes = {
        fontSizeScale: PropTypes.number,
        fontColor: PropTypes.string,
        textAlign: PropTypes.string,
        getState: PropTypes.func,
        onDoubleClick: PropTypes.func,
    };

    static getTimerStyles(fontSizeScale, fontColor) {
        let timeFontSize = 64;
        let fractionFontSize = 36;
        if (fontSizeScale && !fontSizeScale.isNaN && fontSizeScale > 0) {
            timeFontSize *= fontSizeScale;
            fractionFontSize *= fontSizeScale;
        }
        const color = `#${fontColor || 'ea7500'}`;
        const timeStyle = { fontSize: `${timeFontSize}pt`, color };
        const fractionStyle = { fontSize: `${fractionFontSize}pt`, color };
        return { timeStyle, fractionStyle }
    }

    constructor(props) {
        super(props);

        this.state = {
            timerTime: '',
            timerFraction: '',
            ...AutoRefreshTimer.getTimerStyles(
                this.props.fontSizeScale, this.props.fontColor),
            timerContainerStyle: this.timerContainerStyle,
        };

        this.runUpdate = this.runUpdate.bind(this);
    }

    componentWillReceiveProps(nextProps) {
        this.setState({
            ...AutoRefreshTimer.getTimerStyles(
                nextProps.fontSizeScale, nextProps.fontColor),
        })
    }

    componentWillMount() {
        this.runUpdate();
    }

    get timerContainerStyle() {
        let justifyContent = 'center';
        if (this.props.textAlign === 'left') {
            justifyContent = 'flex-start';
        } else if (this.props.textAlign === 'right') {
            justifyContent = 'flex-end';
        }
        const margin = justifyContent === 'center' ? 'auto' : 0;
        return { justifyContent, marginLeft: margin, marginRight: margin };
    }

    updateTimer() {
        const timerState = this.props.getState();
        this.setState({
            timerTime: timerState.time,
            timerFraction: timerState.fraction,
        });
    }

    runUpdate() {
        this.updateTimer();
        requestAnimationFrame(this.runUpdate);
    }

    render() {
        const { timeStyle, timerTime, fractionStyle, timerFraction, timerContainerStyle } = this.state;
        return (

                    {timerTime}
                    {timerFraction}

        );
    }
}
<|endoftext|>


<commit_before>define(function (require) {

  var React = require('react/addons'),
    Backbone = require('backbone'),
    stores = require('stores'),
    Router = require('react-router'),
    moment = require('moment'),
    Showdown = require('showdown'),
    ProjectResource = require('./ProjectResource.react');

  return React.createClass({
    displayName: "Project",

    propTypes: {
      project: React.PropTypes.instanceOf(Backbone.Model).isRequired,
      onClick: React.PropTypes.func,
      className: React.PropTypes.string,
      useRouter: React.PropTypes.bool
    },
    clicked: function () {
      //Not required by default.. Add-on implementation.
      if (!this.props.onClick) {
        return;
      }
      return this.props.onClick(this.props.project);
    },
    renderForRouter: function () {
      var project = this.props.project;
      return (

          <Router.Link to="project-resources" params={{projectId: project.id}}>
            {this.renderBody()}
          </Router.Link>

);
    },
    renderForClick: function () {
      var project = this.props.project;
      return (

          {this.renderBody()}

);
    },
    render: function () {
      var project = this.props.project,
        projectExternalLinks = stores.ProjectExternalLinkStore.getExternalLinksFor(project),
        projectInstances = stores.ProjectInstanceStore.getInstancesFor(project),
        projectImages = stores.ProjectImageStore.getImagesFor(project),
        projectVolumes = stores.ProjectVolumeStore.getVolumesFor(project);

      if (!project.id || !projectExternalLinks || !projectInstances || !projectVolumes || !projectImages) {
        return (

{project.get('name')}

        );
      }

      if (this.props.useRouter == false) {
        return this.renderForClick();
      } else {
        return this.renderForRouter();
      }
    },
    renderBody: function () {
      var project = this.props.project,
        converter = new Showdown.Converter(),
        description = project.get('description'),
        descriptionHtml = converter.makeHtml(description),
        projectExternalLinks = stores.ProjectExternalLinkStore.getExternalLinksFor(project),
        projectInstances = stores.ProjectInstanceStore.getInstancesFor(project),
        projectVolumes = stores.ProjectVolumeStore.getVolumesFor(project),
        projectImages = stores.ProjectImageStore.getImagesFor(project),
        projectCreationDate = moment(project.get('start_date')).format("MMM D, YYYY hh:mm a");

      return (

{project.get('name')}

            <time>{"Created " + projectCreationDate}</time>

            <ProjectResource icon={"tasks"}
                             count={projectInstances.length}
                             resourceType={"instances"}
              />
            <ProjectResource icon={"hdd"}
                             count={projectVolumes.length}
                             resourceType={"volumes"}
              />
            <ProjectResource icon={"floppy-disk"}
                             count={projectImages.length}
                             resourceType={"images"}
              />
            <ProjectResource icon={"globe"}
                             count={projectExternalLinks.length}
                             resourceType={"links"}
              />

      );
    }
  });

});
<commit_msg>make projects into bootstrap columns<commit_after>define(function (require) {

  var React = require('react/addons'),
    Backbone = require('backbone'),
    stores = require('stores'),
    Router = require('react-router'),
    moment = require('moment'),
    Showdown = require('showdown'),
    ProjectResource = require('./ProjectResource.react');

  return React.createClass({
    displayName: "Project",

    propTypes: {
      project: React.PropTypes.instanceOf(Backbone.Model).isRequired,
      onClick: React.PropTypes.func,
      className: React.PropTypes.string,
      useRouter: React.PropTypes.bool
    },
    clicked: function () {
      //Not required by default.. Add-on implementation.
      if (!this.props.onClick) {
        return;
      }
      return this.props.onClick(this.props.project);
    },
    renderForRouter: function () {
      var project = this.props.project;
      return (

            <Router.Link to="project-resources"
              params={{projectId: project.id}}
              style={{color: "inherit"}}
            >
              {this.renderBody()}
            </Router.Link>

);
    },
    renderForClick: function () {
      var project = this.props.project;
      return (

            {this.renderBody()}

);
    },
    render: function () {
      var project = this.props.project,
        projectExternalLinks = stores.ProjectExternalLinkStore.getExternalLinksFor(project),
        projectInstances = stores.ProjectInstanceStore.getInstancesFor(project),
        projectImages = stores.ProjectImageStore.getImagesFor(project),
        projectVolumes = stores.ProjectVolumeStore.getVolumesFor(project);

      if (!project.id || !projectExternalLinks || !projectInstances || !projectVolumes || !projectImages) {
        return (

{project.get('name')}

        );
      }

      if (this.props.useRouter == false) {
        return this.renderForClick();
      } else {
        return this.renderForRouter();
      }
    },
    renderBody: function () {
      var project = this.props.project,
        converter = new Showdown.Converter(),
        description = project.get('description'),
        descriptionHtml = converter.makeHtml(description),
        projectExternalLinks = stores.ProjectExternalLinkStore.getExternalLinksFor(project),
        projectInstances = stores.ProjectInstanceStore.getInstancesFor(project),
        projectVolumes = stores.ProjectVolumeStore.getVolumesFor(project),
        projectImages = stores.ProjectImageStore.getImagesFor(project),
        projectCreationDate = moment(project.get('start_date')).format("MMM D, YYYY hh:mm a");

      return (

{project.get('name')}

            <time>{"Created " + projectCreationDate}</time>

            <ProjectResource icon={"tasks"}
                             count={projectInstances.length}
                             resourceType={"instances"}
              />
            <ProjectResource icon={"hdd"}
                             count={projectVolumes.length}
                             resourceType={"volumes"}
              />
            <ProjectResource icon={"floppy-disk"}
                             count={projectImages.length}
                             resourceType={"images"}
              />
            <ProjectResource icon={"globe"}
                             count={projectExternalLinks.length}
                             resourceType={"links"}
              />

      );
    }
  });

});
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 * Copyright 2017 The FOAM Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

foam.CLASS({
  package: 'foam.comics',
  name: 'DAOControllerView',
  extends: 'foam.u2.View',

  requires: [
    'foam.comics.DAOController',
    'foam.u2.view.ScrollTableView'
  ],

  imports: [
    'stack',
    'summaryView? as importedSummaryView',
    'data? as importedData'
  ],

  exports: [
    'data.selection as selection',
    'data.data as dao'
  ],

  properties: [
    {
      class: 'FObjectProperty',
      of: 'foam.comics.DAOController',
      name: 'data',
      expression: function(importedData) { return importedData; },
    },
    {
      name: 'cls',
      expression: function(data) { return data.cls_; }
    },
    {
      name: 'summaryView',
      factory: function() {
        return this.importedSummaryView$ ? this.importedSummaryView : { class: 'foam.u2.view.ScrollTableView' };
      }
    },
    {
      class: 'String',
      name: 'title',
      expression: function(data$of) {
        return 'Browse ' + data$of.name;
      }
    }
  ],

  reactions: [
    [ 'data', 'action.create', 'onCreate' ],
    [ 'data', 'edit', 'onEdit' ],
    [ 'data', 'action.findRelatedObject', 'onFindRelated' ],
    [ 'data', 'finished', 'onFinished']
  ],

  methods: [
    function initE() {
      this.
        start('table').
          start('tr').
            start('td').style({ display: 'block' }).add(this.cls.PREDICATE).end().
            start('td').style({ 'vertical-align': 'top', 'width': '100%' }).
                tag(this.summaryView, {data$: this.data.filteredDAO$}).
            end().
          end().
          start('tr').
            style({background: 'rgba(0,0,0,0)'}).
            show(this.mode$.map(function(m) { return m == foam.u2.DisplayMode.RW; })).
            tag('td').
            start('td').style({'padding-left':'26px'}).add(this.cls.getAxiomsByClass(foam.core.Action)).end().
          end().
        end();
    }
  ],

  listeners: [
    function onCreate() {
      this.stack.push({
        class: 'foam.comics.DAOCreateControllerView'
      }, this);
    },

    function onEdit(s, edit, id) {
      this.stack.push({
        class: 'foam.comics.DAOUpdateControllerView',
        key: id
      }, this);
    },

    function onFindRelated() {
      var data = this.DAOController.create({
        data: this.data.relationship.targetDAO,
        addEnabled: true,
        relationship: this.data.relationship
      });

      this.stack.push({
        class: 'foam.comics.DAOControllerView',
        data: data
      }, this);
    },

    function onFinished() {
      this.stack.back();
    }
  ]
});
<commit_msg>Fix DAOControllerView title.<commit_after>/**
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 * Copyright 2017 The FOAM Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

foam.CLASS({
  package: 'foam.comics',
  name: 'DAOControllerView',
  extends: 'foam.u2.View',

  requires: [
    'foam.comics.DAOController',
    'foam.u2.view.ScrollTableView'
  ],

  imports: [
    'stack',
    'summaryView? as importedSummaryView',
    'data? as importedData'
  ],

  exports: [
    'data.selection as selection',
    'data.data as dao'
  ],

  properties: [
    {
      class: 'FObjectProperty',
      of: 'foam.comics.DAOController',
      name: 'data',
      expression: function(importedData) { return importedData; },
    },
    {
      name: 'cls',
      expression: function(data) { return data.cls_; }
    },
    {
      name: 'summaryView',
      factory: function() {
        return this.importedSummaryView$ ? this.importedSummaryView : { class: 'foam.u2.view.ScrollTableView' };
      }
    },
    {
      class: 'String',
      name: 'title',
      expression: function(data$data$of) {
        return 'Browse ' + data$data$of.name;
      }
    }
  ],

  reactions: [
    [ 'data', 'action.create', 'onCreate' ],
    [ 'data', 'edit', 'onEdit' ],
    [ 'data', 'action.findRelatedObject', 'onFindRelated' ],
    [ 'data', 'finished', 'onFinished']
  ],

  methods: [
    function initE() {
      this.
        start('table').
          start('tr').
            start('td').style({ display: 'block' }).add(this.cls.PREDICATE).end().
            start('td').style({ 'vertical-align': 'top', 'width': '100%' }).
                tag(this.summaryView, {data$: this.data.filteredDAO$}).
            end().
          end().
          start('tr').
            style({background: 'rgba(0,0,0,0)'}).
            show(this.mode$.map(function(m) { return m == foam.u2.DisplayMode.RW; })).
            tag('td').
            start('td').style({'padding-left':'26px'}).add(this.cls.getAxiomsByClass(foam.core.Action)).end().
          end().
        end();
    }
  ],

  listeners: [
    function onCreate() {
      this.stack.push({
        class: 'foam.comics.DAOCreateControllerView'
      }, this);
    },

    function onEdit(s, edit, id) {
      this.stack.push({
        class: 'foam.comics.DAOUpdateControllerView',
        key: id
      }, this);
    },

    function onFindRelated() {
      var data = this.DAOController.create({
        data: this.data.relationship.targetDAO,
        addEnabled: true,
        relationship: this.data.relationship
      });

      this.stack.push({
        class: 'foam.comics.DAOControllerView',
        data: data
      }, this);
    },

    function onFinished() {
      this.stack.back();
    }
  ]
});
<|endoftext|>


<commit_before>import '../AST'
import '../Errors/StoneCompilerError'

export function compileExtends(context, args) {
	if(context.isLayout === true) {
		throw new StoneCompilerError(context, '@extends may only be called once per view.')
	}

	args = context.parseArguments(args)

	context.isLayout = true
	context.hasLayoutContext = args.length > 1

	let code = `const __extendsLayout = ${AST.stringify(args[0])};`

	if(context.hasLayoutContext) {
		code += `\nconst __extendsContext = ${AST.stringify(args[1])};`
	}

	return code
}

export function compileSection(context, args) {
	args = context.parseArguments(args)

	if(args.length === 1) {
		args = AST.stringify(args[0])
		context.sections.push(args)
		return this._compileSection(context, args, `function() {\nlet output = '';`)
	}

	if(args.length !== 2) {
		throw new StoneCompilerError(context, 'Invalid section block')
	}

	return this._compileSection(
		context,
		AST.stringify(args[0]),
		`function() { return escape(${AST.stringify(args[1])}); });`
	)
}

export function _compileSection(context, name, code) {
	return `(_sections[${name}] = (_sections[${name}] || [ ])).unshift(${code}\n`
}

/**
 * Ends the current section and returns output
 * @return {string} Output from the section
 */
export function compileEndsection(context) {
	context.sections.pop()
	return 'return output;\n});'
}

/**
 * Ends the current section and yields it for display
 * @return {string} Output from the section
 */
export function compileShow(context) {
	const section = context.sections[context.sections.length - 1]
	return `${this.compileEndsection(context)};\n${this.compileYield(context, section)}`
}

/**
 * Compiles the yield directive to output a section
 *
 * @param  {object} context Context for the compilation
 * @param  {string} section Name of the section to yield
 * @return {string}         Code to render the section
 */
export function compileYield(context, section) {
	let code = ''

	if(section.indexOf(',') >= 0) {
		const sectionName = section.split(/,/)[0]
		code = `${this.compileSection(context, section)}\n`
		section = sectionName
	}

	context.validateSyntax(section)
	return `${code}output += (_sections[${section}] || [ ]).length > 0 ? (_sections[${section}].pop())() : '';`
}

/**
 * Renders content from the section section
 * @return {string} Code to render the super section
 */
export function compileSuper(context) {
	// Due to how sections work, we can cheat by just calling yeild
	// which will pop off the next chunk of content in this section
	// and render it within ours
	return this.compileYield(context, context.sections[context.sections.length - 1])
}

/**
 * Alias of compileSuper for compatibility with Blade
 * @return {string} Code to render the super section
 */
export function compileParent(context) {
	return this.compileSuper(context)
}

/**
 * Convenience directive to determine if a section has content
 * @return {string} If statement that determines if a section has content
 */
export function compileHassection(context, section) {
	context.validateSyntax(section)
	return `if((_sections[${section}] || [ ]).length > 0) {`
}

/**
 * Renders content from a subview
 *
 * @param  {object} context Context for the compilation
 * @param  {string} view    Subview to include
 * @return {string} Code to render the subview
 */
export function compileInclude(context, view) {
	context.validateSyntax(view)
	return `output += (_.$stone.include(_, _sections, ${view}));\n`
}

/**
 * Compiles each directive to call the runtime and output
 * the result.
 *
 * @param  object context Context for the compilation
 * @param  string args    Arguments to pass through to runtime
 * @return string         Code to render the each block
 */
export function compileEach(context, args) {
	context.validateSyntax(`each(${args})`)
	return `output += (_.$stone.each(_, ${args}));\n`
}
<commit_msg>Removed extra semicolon in @show<commit_after>import '../AST'
import '../Errors/StoneCompilerError'

export function compileExtends(context, args) {
	if(context.isLayout === true) {
		throw new StoneCompilerError(context, '@extends may only be called once per view.')
	}

	args = context.parseArguments(args)

	context.isLayout = true
	context.hasLayoutContext = args.length > 1

	let code = `const __extendsLayout = ${AST.stringify(args[0])};`

	if(context.hasLayoutContext) {
		code += `\nconst __extendsContext = ${AST.stringify(args[1])};`
	}

	return code
}

export function compileSection(context, args) {
	args = context.parseArguments(args)

	if(args.length === 1) {
		args = AST.stringify(args[0])
		context.sections.push(args)
		return this._compileSection(context, args, `function() {\nlet output = '';`)
	}

	if(args.length !== 2) {
		throw new StoneCompilerError(context, 'Invalid section block')
	}

	return this._compileSection(
		context,
		AST.stringify(args[0]),
		`function() { return escape(${AST.stringify(args[1])}); });`
	)
}

export function _compileSection(context, name, code) {
	return `(_sections[${name}] = (_sections[${name}] || [ ])).unshift(${code}\n`
}

/**
 * Ends the current section and returns output
 * @return {string} Output from the section
 */
export function compileEndsection(context) {
	context.sections.pop()
	return 'return output;\n});'
}

/**
 * Ends the current section and yields it for display
 * @return {string} Output from the section
 */
export function compileShow(context) {
	const section = context.sections[context.sections.length - 1]
	return `${this.compileEndsection(context)}\n${this.compileYield(context, section)}`
}

/**
 * Compiles the yield directive to output a section
 *
 * @param  {object} context Context for the compilation
 * @param  {string} section Name of the section to yield
 * @return {string}         Code to render the section
 */
export function compileYield(context, section) {
	let code = ''

	if(section.indexOf(',') >= 0) {
		const sectionName = section.split(/,/)[0]
		code = `${this.compileSection(context, section)}\n`
		section = sectionName
	}

	context.validateSyntax(section)
	return `${code}output += (_sections[${section}] || [ ]).length > 0 ? (_sections[${section}].pop())() : '';`
}

/**
 * Renders content from the section section
 * @return {string} Code to render the super section
 */
export function compileSuper(context) {
	// Due to how sections work, we can cheat by just calling yeild
	// which will pop off the next chunk of content in this section
	// and render it within ours
	return this.compileYield(context, context.sections[context.sections.length - 1])
}

/**
 * Alias of compileSuper for compatibility with Blade
 * @return {string} Code to render the super section
 */
export function compileParent(context) {
	return this.compileSuper(context)
}

/**
 * Convenience directive to determine if a section has content
 * @return {string} If statement that determines if a section has content
 */
export function compileHassection(context, section) {
	context.validateSyntax(section)
	return `if((_sections[${section}] || [ ]).length > 0) {`
}

/**
 * Renders content from a subview
 *
 * @param  {object} context Context for the compilation
 * @param  {string} view    Subview to include
 * @return {string} Code to render the subview
 */
export function compileInclude(context, view) {
	context.validateSyntax(view)
	return `output += (_.$stone.include(_, _sections, ${view}));\n`
}

/**
 * Compiles each directive to call the runtime and output
 * the result.
 *
 * @param  object context Context for the compilation
 * @param  string args    Arguments to pass through to runtime
 * @return string         Code to render the each block
 */
export function compileEach(context, args) {
	context.validateSyntax(`each(${args})`)
	return `output += (_.$stone.each(_, ${args}));\n`
}
<|endoftext|>


<commit_before>const Moment = require('moment');
const ClockException = require('./ClockException');

class Clock
{
    /**
    * Creates a Clock instance.
    *
    * @return {Clock}
    */
    constructor()
    {
        // Default fallback format
        this.setFormat('HH:mm:ss');

        // Start the clock
        this.start();
    }

    /**
     * Starts the clock.
     */
    start()
    {
        if (typeof this._onTick !== 'function') {
            this._onTick = () => {};
        }

        this.intervalId = setInterval(
            () => this._onTick(this),
            1000
        );
    }

    /**
     * Stops the clock.
     */
    stop()
    {
        if (this.intervalId) {
            this.intervalId = clearInterval(this.intervalId);
        }
    }

    /**
     * Sets the callback function which will be called on each tick.
     *
     * @param {function} callback Tick function to call.
     */
    onTick(callback)
    {
        this._onTick = callback;
    }

    /**
     * Returns the currently set clock format.
     *
     * @return {string}
     */
    getFormat()
    {
        return this._format;
    }

    /**
     * Sets the clock format.
     *
     * @see http://momentjs.com/docs/#/displaying/format/
     * @param {string} format Clock format.
     */
    setFormat(format)
    {
        const type = typeof format;

        if (type !== 'string') {
            throw new ClockException(`Format needs to be of type "string", instead "${type}" was given.`);
        }

        this._format = format;
    }

    /**
     * Returns the status of the clock as a string in the currently set format.
     *
     * @return {string}
     */
    toString()
    {
        return Moment().format(this.getFormat());
    }
}

module.exports = Clock;
<commit_msg>clock label is now locale aware. fixes #1<commit_after>const Moment = require('moment');
const ClockException = require('./ClockException');

class Clock
{
    /**
    * Creates a Clock instance.
    *
    * @return {Clock}
    */
    constructor(app)
    {
        // Remember app instance
        this.app = app;

        // Set locale for Moment.js
        Moment.locale(this.app.getLocale());

        // Default fallback format
        this.setFormat('HH:mm:ss');

        // Start the clock
        this.start();
    }

    /**
     * Starts the clock.
     */
    start()
    {
        if (typeof this._onTick !== 'function') {
            this._onTick = () => {};
        }

        this.intervalId = setInterval(
            () => this._onTick(this),
            1000
        );
    }

    /**
     * Stops the clock.
     */
    stop()
    {
        if (this.intervalId) {
            this.intervalId = clearInterval(this.intervalId);
        }
    }

    /**
     * Sets the callback function which will be called on each tick.
     *
     * @param {function} callback Tick function to call.
     */
    onTick(callback)
    {
        this._onTick = callback;
    }

    /**
     * Returns the currently set clock format.
     *
     * @return {string}
     */
    getFormat()
    {
        return this._format;
    }

    /**
     * Sets the clock format.
     *
     * @see http://momentjs.com/docs/#/displaying/format/
     * @param {string} format Clock format.
     */
    setFormat(format)
    {
        const type = typeof format;

        if (type !== 'string') {
            throw new ClockException(`Format needs to be of type "string", instead "${type}" was given.`);
        }

        this._format = format;
    }

    /**
     * Returns the status of the clock as a string in the currently set format.
     *
     * @return {string}
     */
    toString()
    {
        return Moment().format(this.getFormat());
    }
}

module.exports = Clock;
<|endoftext|>


<commit_before>// ===== imports =====

var sounds   = require("./sounds");
var process  = require("./process");
var formulas = require("./formulas");
var harmony  = require("./harmony");
var identify = require("./identify");

// ===== declarations =====

var equals = process.arraysEqual;  // checks equality for arrays

var A  = new sounds.Note("A",  440.000, 4, "La");
var Bb = new sounds.Note("Bb", 466.164, 4);
var B  = new sounds.Note("B",  493.883, 4, "Si");
var C  = new sounds.Note("C",  523.251, 5, "Do");
var Db = new sounds.Note("Db", 554.365, 5);
var D  = new sounds.Note("D",  587.330, 5, "Re");
var Eb = new sounds.Note("Eb", 622.254, 5);
var E  = new sounds.Note("E",  659.255, 5);
var F  = new sounds.Note("F",  698.456, 5);
var Gb = new sounds.Note("Gb", 739.989, 5);
var G  = new sounds.Note("G",  783.991, 5, "Sol");
var Ab = new sounds.Note("Ab", 830.609, 5);

var A2  = new sounds.Note("A",  440.000, 4);
var A3  = new sounds.Note("A",  440.000, 4);

var ABC  = new sounds.NoteCollection([A, B, C], "ABC");
var GAbC = new sounds.NoteCollection([G, Ab, C], "GAbC");
var BCD  = new sounds.NoteCollection([B, C, D], "BCD"); // use only at NoteCollection mutation

var CEG  = new sounds.Chord([C, E, G], "C major");
var ACE  = new sounds.Chord([A, C, E], "A minor");
var FAC  = new sounds.Chord([F, A, C], "F major");
var DFA  = new sounds.Chord([D, F, A], "D minor");
var CFG  = new sounds.Chord([C, F, G], "C sus4");
var CDG  = new sounds.Chord([C, D, G], "C sus2");
var ACEb = new sounds.Chord([A, C, Eb], "A dim");
var CEAb = new sounds.Chord([C, E, Ab], "C aug");
var CEGB = new sounds.Chord([C, E, G, B], "C major 7");
var ACEG = new sounds.Chord([A, C, E, G], "A minor 7");
var FACE = new sounds.Chord([F, A, C, E], "F major 7");
var DFAC = new sounds.Chord([D, F, A, C], "D minor 7");

var CM  = new sounds.Scale([C, D, E, F, G, A, B], "C ionian");

var chc1 = new sounds.ChordCollection([CEG, ACE, FAC], "chc1");
var h1   = new sounds.Harmony([CEG, ACE, FAC], "h1");

// ===== assertions =====

// Note getters
console.assert(A.name   === "A");
console.assert(A.freq   === 440.000);
console.assert(A.octave === 4);
console.assert(A.toString() === "name=A name2=La freq=440 octave=4");

// NoteCollection getters
console.assert(ABC.size  === 3);
console.assert(ABC.name  === "ABC");
console.assert(ABC.name2 === "");
console.assert(JSON.stringify(ABC.getNotes())   === JSON.stringify([A, B, C]));
console.assert(JSON.stringify(ABC.toIndexes())  === JSON.stringify([1, 3, 4]));
console.assert(JSON.stringify(GAbC.toIndexes()) === JSON.stringify([11, 0, 4]));
console.assert(ABC.toFormula().toString() === [2, 1, 9].toString());

// NoteCollection mutation
BCD.addNote(E);
console.assert(JSON.stringify(BCD.getNotes()) === JSON.stringify([B, C, D, E]));
console.assert(BCD.size === 4);
BCD.reset();
console.assert(BCD.size === 3);
console.assert(JSON.stringify(BCD.getNotes()) === JSON.stringify([B, C, D]));
BCD.setNotes([D, E]);
console.assert(BCD.size === 2);
console.assert(JSON.stringify(BCD.getNotes()) === JSON.stringify([D, E]));
BCD.reset();
console.assert(BCD.size === 3);
console.assert(JSON.stringify(BCD.getNotes()) === JSON.stringify([B, C, D]));
BCD.reverse();
console.assert(BCD.size === 3);
console.assert(JSON.stringify(BCD.getNotes()) === JSON.stringify([D, C, B]));
BCD.rotate();
console.assert(JSON.stringify(BCD.getNotes()) === JSON.stringify([C, B, D]));
BCD.removeNoteAt(1);
console.assert(JSON.stringify(BCD.getNotes()) === JSON.stringify([C, D]));
console.assert(BCD.size === 2);
console.assert(BCD.getNotesAsString() === "C D ");
console.assert(BCD.getOriginalNotesAsString() === "B C D ");
console.assert(BCD.getFreqsAsString() === "523.251 587.33 ");
BCD.setName("C D");
BCD.setName2("secondary name");
console.assert(BCD.getName() === "C D");
console.assert(BCD.getName2() === "secondary name");
BCD.setNotes([C, A, B, A2, A3, G, C, A2]);
console.assert(BCD.getNotesAsString() === "C A B A A G C A ");
console.assert(BCD.size === 8);
BCD.removeNotesWithName("A");
console.assert(BCD.getNotesAsString() === "C B G C ");
console.assert(BCD.size === 4);
BCD.setNotes([C, A, B, A2, A3, G, C, A2]);
BCD.removeNotesWithFreq(440);
console.assert(BCD.getNotesAsString() === "C B G C ");
console.assert(BCD.size === 4);
BCD.removeNotesWithName2("Do");
console.assert(BCD.getNotesAsString() === "B G ");
console.assert(BCD.size === 2);
BCD.reset();
console.assert(BCD.size === 3);
console.assert(JSON.stringify(BCD.getNotes()) === JSON.stringify([B, C, D]));
BCD.rotateBack();
console.assert(JSON.stringify(BCD.getNotes()) === JSON.stringify([D, B, C]));
BCD.rotateBack();
console.assert(JSON.stringify(BCD.getNotes()) === JSON.stringify([C, D, B]));
BCD.rotateBack();
console.assert(JSON.stringify(BCD.getNotes()) === JSON.stringify([B, C, D]));
BCD.rotate();
console.assert(JSON.stringify(BCD.getNotes()) === JSON.stringify([C, D, B]));
BCD.rotate();
console.assert(JSON.stringify(BCD.getNotes()) === JSON.stringify([D, B, C]));
BCD.rotate();
console.assert(JSON.stringify(BCD.getNotes()) === JSON.stringify([B, C, D]));
BCD.setNotes([A, B, C, D, E, F, G]);
console.assert(BCD.getSize() === 7, "43");
console.assert(JSON.stringify(BCD.getNotes()) === JSON.stringify([A, B, C, D, E, F, G]));
BCD.removeNotesWithFreqRange(490, 700);
console.assert(BCD.getSize() === 2, "45");
console.assert(JSON.stringify(BCD.getNotes()) === JSON.stringify([A, G]));

// Chord access and mutation
console.assert(JSON.stringify(CEG.toIndexes()) === JSON.stringify([4, 8, 11]));
console.assert(CEG.getNotesAsString() === "C E G ");
CEG.invert(1);
console.assert(CEG.getNotesAsString() === "E G C ");
CEG.invert(1);
console.assert(CEG.getNotesAsString() === "G C E ");
CEG.invert(1);
console.assert(CEG.getNotesAsString() === "C E G ");
CEG.invert(-1);
console.assert(CEG.getNotesAsString() === "G C E ");
CEG.invert(-1);
console.assert(CEG.getNotesAsString() === "E G C ");
CEG.invert(-1);
console.assert(CEG.getNotesAsString() === "C E G ");
CEG.invert(2);
console.assert(CEG.getNotesAsString() === "G C E ");
CEG.invert(-1);
console.assert(CEG.getNotesAsString() === "E G C ");
CEG.invert(-2);
console.assert(CEG.getNotesAsString() === "G C E ");
CEG.reset();
console.assert(CEG.getNotesAsString() === "C E G ");

// Scale access and mutation
console.assert(CM.toFormula().toString() === [2,2,1,2,2,2,1].toString());

// ChordCollection access and mutation
console.assert(chc1.getChordsNames() === "C major A minor F major ");

// Harmony access and mutation
console.assert(h1.getChordsNames() === "C major A minor F major ");

// NoteCollection.toFormula()
console.assert(CEG.toFormula().toString()  === [4 ,3, 5].toString());
console.assert(FAC.toFormula().toString()  === [4, 3, 5].toString());
console.assert(ACE.toFormula().toString()  === [3, 4, 5].toString());
console.assert(DFA.toFormula().toString()  === [3, 4, 5].toString());
console.assert(CEGB.toFormula().toString() === [4, 3, 4, 1].toString());
console.assert(FACE.toFormula().toString() === [4, 3, 4, 1].toString());
console.assert(ACEG.toFormula().toString() === [3, 4, 3, 2].toString());
console.assert(DFAC.toFormula().toString() === [3, 4, 3, 2].toString());
console.assert(CFG.toFormula().toString()  === [5, 2, 5].toString());
console.assert(CDG.toFormula().toString()  === [2, 5, 5].toString());

// Process stepCount
console.assert(process.stepCount(A, Bb) === 1);
console.assert(process.stepCount(A, B)  === 2);
console.assert(process.stepCount(G, Ab) === 1);
console.assert(process.stepCount(D, Db) === 11);

// Process scalize
console.assert(process.scalize(C, formulas.MAJOR).getNotesAsString() === "C D E F G A B ");
console.assert(process.scalize(C, formulas.MINOR).getNotesAsString() === "C D Eb F G Ab Bb ");

// Process harmonize
console.assert(harmony.harmonize(CM, 3).getChordsNotesAsString() ===
    "< C E G > < D F A > < E G B > < F A C > < G B D > < A C E > < B D F > ");

// Process buildInversions
var CEGinvs  = process.buildInversions(CEG);
var CEGinvs2 = [];
for (var i = 0; i < CEGinvs.length; i++)
    CEGinvs2[i] = CEGinvs[i].getNotesAsString();
console.assert(equals(CEGinvs2, ['C E G ', 'E G C ', 'G C E ']));

// Process identifyTriad
console.assert(equals(identify.identifyChord(CEG),  ['C maj' ]));
console.assert(equals(identify.identifyChord(CEAb), ['C aug', 'E aug', 'Ab aug']));
console.assert(equals(identify.identifyChord(CDG),  ['C sus2', 'G sus4']));
console.assert(equals(identify.identifyChord(CFG),  ['C sus4', 'F sus2']));
console.assert(equals(identify.identifyChord(ACE),  ['A min' ]));
console.assert(equals(identify.identifyChord(ACEb), ['A dim' ] ));

// Process arraysEqual
console.assert(equals([1, 2], [1, 2])    === true);
console.assert(equals([1, 3, 4], [1, 2]) === false);

<commit_msg>more tests<commit_after>// ===== imports =====

var sounds   = require("./sounds");
var process  = require("./process");
var formulas = require("./formulas");
var harmony  = require("./harmony");
var identify = require("./identify");

// ===== declarations =====

var equals = process.arraysEqual;  // checks equality for arrays

var A  = new sounds.Note("A",  440.000, 4, "La");
var Bb = new sounds.Note("Bb", 466.164, 4);
var B  = new sounds.Note("B",  493.883, 4, "Si");
var C  = new sounds.Note("C",  523.251, 5, "Do");
var Db = new sounds.Note("Db", 554.365, 5);
var D  = new sounds.Note("D",  587.330, 5, "Re");
var Eb = new sounds.Note("Eb", 622.254, 5);
var E  = new sounds.Note("E",  659.255, 5);
var F  = new sounds.Note("F",  698.456, 5);
var Gb = new sounds.Note("Gb", 739.989, 5);
var G  = new sounds.Note("G",  783.991, 5, "Sol");
var Ab = new sounds.Note("Ab", 830.609, 5);

var A2  = new sounds.Note("A",  440.000, 4);
var A3  = new sounds.Note("A",  440.000, 4);

var ABC  = new sounds.NoteCollection([A, B, C], "ABC");
var GAbC = new sounds.NoteCollection([G, Ab, C], "GAbC");
var BCD  = new sounds.NoteCollection([B, C, D], "BCD"); // use only at NoteCollection mutation

var CEG  = new sounds.Chord([C, E, G], "C major");
var EGC  = new sounds.Chord([E, G, C], "C major inv");
var CGE  = new sounds.Chord([C, G, E], "C major");
var ACE  = new sounds.Chord([A, C, E], "A minor");
var FAC  = new sounds.Chord([F, A, C], "F major");
var DFA  = new sounds.Chord([D, F, A], "D minor");
var CFG  = new sounds.Chord([C, F, G], "C sus4");
var CDG  = new sounds.Chord([C, D, G], "C sus2");
var ACEb = new sounds.Chord([A, C, Eb], "A dim");
var CEAb = new sounds.Chord([C, E, Ab], "C aug");
var CEGB = new sounds.Chord([C, E, G, B], "C major 7");
var ACEG = new sounds.Chord([A, C, E, G], "A minor 7");
var FACE = new sounds.Chord([F, A, C, E], "F major 7");
var DFAC = new sounds.Chord([D, F, A, C], "D minor 7");

var CM  = new sounds.Scale([C, D, E, F, G, A, B], "C ionian");

var chc1 = new sounds.ChordCollection([CEG, ACE, FAC], "chc1");
var h1   = new sounds.Harmony([CEG, ACE, FAC], "h1");

// ===== assertions =====

// Note getters
console.assert(A.name   === "A");
console.assert(A.freq   === 440.000);
console.assert(A.octave === 4);
console.assert(A.toString() === "name=A name2=La freq=440 octave=4");

// NoteCollection getters
console.assert(ABC.size  === 3);
console.assert(ABC.name  === "ABC");
console.assert(ABC.name2 === "");
console.assert(JSON.stringify(ABC.getNotes())   === JSON.stringify([A, B, C]));
console.assert(JSON.stringify(ABC.toIndexes())  === JSON.stringify([1, 3, 4]));
console.assert(JSON.stringify(GAbC.toIndexes()) === JSON.stringify([11, 0, 4]));
console.assert(ABC.toFormula().toString() === [2, 1, 9].toString());

// NoteCollection mutation
BCD.addNote(E);
console.assert(JSON.stringify(BCD.getNotes()) === JSON.stringify([B, C, D, E]));
console.assert(BCD.size === 4);
BCD.reset();
console.assert(BCD.size === 3);
console.assert(JSON.stringify(BCD.getNotes()) === JSON.stringify([B, C, D]));
BCD.setNotes([D, E]);
console.assert(BCD.size === 2);
console.assert(JSON.stringify(BCD.getNotes()) === JSON.stringify([D, E]));
BCD.reset();
console.assert(BCD.size === 3);
console.assert(JSON.stringify(BCD.getNotes()) === JSON.stringify([B, C, D]));
BCD.reverse();
console.assert(BCD.size === 3);
console.assert(JSON.stringify(BCD.getNotes()) === JSON.stringify([D, C, B]));
BCD.rotate();
console.assert(JSON.stringify(BCD.getNotes()) === JSON.stringify([C, B, D]));
BCD.removeNoteAt(1);
console.assert(JSON.stringify(BCD.getNotes()) === JSON.stringify([C, D]));
console.assert(BCD.size === 2);
console.assert(BCD.getNotesAsString() === "C D ");
console.assert(BCD.getOriginalNotesAsString() === "B C D ");
console.assert(BCD.getFreqsAsString() === "523.251 587.33 ");
BCD.setName("C D");
BCD.setName2("secondary name");
console.assert(BCD.getName() === "C D");
console.assert(BCD.getName2() === "secondary name");
BCD.setNotes([C, A, B, A2, A3, G, C, A2]);
console.assert(BCD.getNotesAsString() === "C A B A A G C A ");
console.assert(BCD.size === 8);
BCD.removeNotesWithName("A");
console.assert(BCD.getNotesAsString() === "C B G C ");
console.assert(BCD.size === 4);
BCD.setNotes([C, A, B, A2, A3, G, C, A2]);
BCD.removeNotesWithFreq(440);
console.assert(BCD.getNotesAsString() === "C B G C ");
console.assert(BCD.size === 4);
BCD.removeNotesWithName2("Do");
console.assert(BCD.getNotesAsString() === "B G ");
console.assert(BCD.size === 2);
BCD.reset();
console.assert(BCD.size === 3);
console.assert(JSON.stringify(BCD.getNotes()) === JSON.stringify([B, C, D]));
BCD.rotateBack();
console.assert(JSON.stringify(BCD.getNotes()) === JSON.stringify([D, B, C]));
BCD.rotateBack();
console.assert(JSON.stringify(BCD.getNotes()) === JSON.stringify([C, D, B]));
BCD.rotateBack();
console.assert(JSON.stringify(BCD.getNotes()) === JSON.stringify([B, C, D]));
BCD.rotate();
console.assert(JSON.stringify(BCD.getNotes()) === JSON.stringify([C, D, B]));
BCD.rotate();
console.assert(JSON.stringify(BCD.getNotes()) === JSON.stringify([D, B, C]));
BCD.rotate();
console.assert(JSON.stringify(BCD.getNotes()) === JSON.stringify([B, C, D]));
BCD.setNotes([A, B, C, D, E, F, G]);
console.assert(BCD.getSize() === 7, "43");
console.assert(JSON.stringify(BCD.getNotes()) === JSON.stringify([A, B, C, D, E, F, G]));
BCD.removeNotesWithFreqRange(490, 700);
console.assert(BCD.getSize() === 2, "45");
console.assert(JSON.stringify(BCD.getNotes()) === JSON.stringify([A, G]));

// Chord access and mutation
console.assert(JSON.stringify(CEG.toIndexes()) === JSON.stringify([4, 8, 11]));
console.assert(CEG.getNotesAsString() === "C E G ");
CEG.invert(1);
console.assert(CEG.getNotesAsString() === "E G C ");
CEG.invert(1);
console.assert(CEG.getNotesAsString() === "G C E ");
CEG.invert(1);
console.assert(CEG.getNotesAsString() === "C E G ");
CEG.invert(-1);
console.assert(CEG.getNotesAsString() === "G C E ");
CEG.invert(-1);
console.assert(CEG.getNotesAsString() === "E G C ");
CEG.invert(-1);
console.assert(CEG.getNotesAsString() === "C E G ");
CEG.invert(2);
console.assert(CEG.getNotesAsString() === "G C E ");
CEG.invert(-1);
console.assert(CEG.getNotesAsString() === "E G C ");
CEG.invert(-2);
console.assert(CEG.getNotesAsString() === "G C E ");
CEG.reset();
console.assert(CEG.getNotesAsString() === "C E G ");

// Scale access and mutation
console.assert(CM.toFormula().toString() === [2,2,1,2,2,2,1].toString());

// ChordCollection access and mutation
console.assert(chc1.getChordsNames() === "C major A minor F major ");

// Harmony access and mutation
console.assert(h1.getChordsNames() === "C major A minor F major ");

// NoteCollection.toFormula()
console.assert(CEG.toFormula().toString()  === [4 ,3, 5].toString());
console.assert(FAC.toFormula().toString()  === [4, 3, 5].toString());
console.assert(ACE.toFormula().toString()  === [3, 4, 5].toString());
console.assert(DFA.toFormula().toString()  === [3, 4, 5].toString());
console.assert(CEGB.toFormula().toString() === [4, 3, 4, 1].toString());
console.assert(FACE.toFormula().toString() === [4, 3, 4, 1].toString());
console.assert(ACEG.toFormula().toString() === [3, 4, 3, 2].toString());
console.assert(DFAC.toFormula().toString() === [3, 4, 3, 2].toString());
console.assert(CFG.toFormula().toString()  === [5, 2, 5].toString());
console.assert(CDG.toFormula().toString()  === [2, 5, 5].toString());

// Process stepCount
console.assert(process.stepCount(A, Bb) === 1);
console.assert(process.stepCount(A, B)  === 2);
console.assert(process.stepCount(G, Ab) === 1);
console.assert(process.stepCount(D, Db) === 11);

// Process scalize
console.assert(process.scalize(C, formulas.MAJOR).getNotesAsString() === "C D E F G A B ");
console.assert(process.scalize(C, formulas.MINOR).getNotesAsString() === "C D Eb F G Ab Bb ");

// harmony.harmonize
console.assert(harmony.harmonize(CM, 3).getChordsNotesAsString() ===
    "< C E G > < D F A > < E G B > < F A C > < G B D > < A C E > < B D F > ");

// Process buildInversions
var CEGinvs  = process.buildInversions(CEG);
var CEGinvs2 = [];
for (var i = 0; i < CEGinvs.length; i++)
    CEGinvs2[i] = CEGinvs[i].getNotesAsString();
console.assert(equals(CEGinvs2, ['C E G ', 'E G C ', 'G C E ']));

// identify.identifyChord
console.assert(equals(identify.identifyChord(CEG),  ['C maj' ]));
console.assert(equals(identify.identifyChord(EGC),  ['C maj' ]));
console.assert(equals(identify.identifyChord(CEAb), ['C aug', 'E aug', 'Ab aug']));
console.assert(equals(identify.identifyChord(CDG),  ['C sus2', 'G sus4']));
console.assert(equals(identify.identifyChord(CFG),  ['C sus4', 'F sus2']));
console.assert(equals(identify.identifyChord(ACE),  ['A min' ]));
console.assert(equals(identify.identifyChord(ACEb), ['A dim' ] ));

// Process arraysEqual
console.assert(equals([1, 2], [1, 2])    === true);
console.assert(equals([1, 3, 4], [1, 2]) === false);

console.log(process.permute(["C", "E", "G"]));
console.log(identify.identifyChord(CGE));

<|endoftext|>


<commit_before>"use strict";
/* global describe, it, beforeEach, afterEach */
/* jshint unused:false */

var expect = require( "must" );
var subject = require( "./index" );
var sinon = require( "sinon" );

describe( "-- prepost --", function(){
  describe( "spec file", function(){
    it( "should be found", function(){
      expect( true ).to.be.true();
    } );
  } );
  describe( "module", function(){
    it( "should expose a `mixin` function", function(){
      expect( subject.mixin ).to.be.a.function();
    } );
    it( "should expose a `create` function", function(){
      expect( subject.create ).to.be.a.function();
    } );
  } );
  describe( "#mixin", function(){
    it( "should add prepost functions to an existing object", function(){
      var instance = {};
      subject.mixin( instance );
      expect( instance ).to.have.keys( [ "pre", "post", "addHook", "hooks", "register", "__prepost" ] );
    } );
  } );
  describe( "#create", function(){
    it( "should return a prepost object", function(){
      var instance = subject.create();
      expect( instance ).to.have.keys( [ "pre", "post", "addHook", "hooks", "register", "__prepost" ] );
    } );
  } );
  describe( "instance", function(){
    var instance;
    beforeEach( function(){
      instance = subject.create();
    } );
    describe( "#register", function(){
      it( "should throw an error for anything else but `pre` or `post`", function(){
        expect( function(){
          instance.register( "nope:not valid!" );
        } ).to.throw( /pre|post/ );
      } );
      it( "should register a type:event hook", function(){
        var hook = function(){
        };
        instance.register( "pre:test" );
        instance.pre( "test", hook );
        instance.hooks( "pre:test", function( callback,
                                              next ){
          expect( callback ).to.equal( hook );
          next();
        } );
      } );
      it( "should accept multiple type:event hooks", function(){
        var hook = function(){
        };
        instance.register("pre:test", "post:test");
        instance.post( "test", hook );
        instance.hooks( "post:test", function( callback,
                                              next ){
          expect( callback ).to.equal( hook );
          next();
        } );
      } );
      it( "should accept an array of type:event hooks", function(){
        var hook = function(){
        };
        instance.register(["pre:test", "post:test"]);
        instance.post( "test", hook );
        instance.hooks( "post:test", function( callback,
                                              next ){
          expect( callback ).to.equal( hook );
          next();
        } );
      } );
    } );
  } );
} );

<commit_msg>UPdate tests<commit_after>"use strict";
/* global describe, it, beforeEach, afterEach */
/* jshint unused:false */

var expect = require( "must" );
var subject = require( "./index" );
var sinon = require( "sinon" );

var MEMBERS = [ "pre", "post", "hookup", "hooks", "hookable", "unhook", "emit", "hooked", "__hooks" ];
var PRE_TEST = "pre:test";
var POST_TEST = "post:test";

describe( "-- prepost --", function(){
	describe( "spec file", function(){
		it( "should be found", function(){
			expect( true ).to.be.true();
		} );
	} );
	describe( "module", function(){
		it( "should expose a `mixin` function", function(){
			expect( subject.mixin ).to.be.a.function();
		} );
		it( "should expose a `create` function", function(){
			expect( subject.create ).to.be.a.function();
		} );
	} );
	describe( "#mixin", function(){
		it( "should add prepost functions to an existing object", function(){
			var instance = {};
			subject.mixin( instance );
			expect( instance ).to.have.keys( MEMBERS );
		} );
	} );
	describe( "#create", function(){
		it( "should return a prepost object", function(){
			var instance = subject.create();
			expect( instance ).to.have.keys( MEMBERS );
		} );
	} );
	describe( "instance", function(){
		var instance;
		beforeEach( function(){
			instance = subject.create();
		} );
		describe( "#hookable", function(){
			var hook;
			beforeEach( function(){
				hook = sinon.spy();
			} );
			it( "should throw an error for anything else but `pre` or `post`", function(){
				expect( function(){
					instance.hookable( "nope:not valid!" );
				} ).to.throw( /pre|post/ );
			} );
			it( "should register a type:event hook", function(){
				instance.hookable( PRE_TEST )
					.hookup( PRE_TEST, hook )
					.emit( PRE_TEST );
				expect( hook.callCount ).to.equal( 1 );
			} );
			it( "should accept multiple type:event hooks", function(){
				instance.hookable( POST_TEST, PRE_TEST )
					.hookup( PRE_TEST, hook )
					.emit( PRE_TEST );
				expect( hook.callCount ).to.equal( 1 );
			} );
			it( "should accept an array of type:event hooks", function(){
				instance.hookable( [ POST_TEST, PRE_TEST ] )
					.hookup( PRE_TEST, hook )
					.emit( PRE_TEST );
				expect( hook.callCount ).to.equal( 1 );
			} );
			it( "should accept an non-typed string and register both hooks", function(){
				instance.hookable( "test" )
					.hookup( PRE_TEST, hook )
					.hookup( POST_TEST, hook )
					.emit( PRE_TEST )
					.emit( POST_TEST );
				expect( hook.callCount ).to.equal( 2 );
			} )
		} );
		describe( "#hookup", function(){
			var hook;
			beforeEach( function(){
				hook = sinon.spy();
				instance.hookable( "test" );
			} );
			it( "should register a hook to a single event", function(){
				instance.hookup( PRE_TEST, hook )
					.emit( PRE_TEST )
					.emit( POST_TEST );
				expect( hook.callCount ).to.equal( 1 );
			} );
			it( "should register multiple hooks", function(){
				instance.hookup( PRE_TEST, hook, hook, hook )
					.emit( PRE_TEST );
				expect( hook.callCount ).to.equal( 3 );
			} );
			it( "should register an array of hooks to a single event", function(){
				var hooks = [ hook, hook, hook ];
				instance.hookup( PRE_TEST, hooks )
					.emit( PRE_TEST );
				expect( hook.callCount ).to.equal( hooks.length );
			} );
		} );
		describe( "#hooks", function(){
			var hook;
			beforeEach( function(){
				hook = sinon.spy();
				instance.hookable( "test" );
			} );
			it( "should iterate over all hooks and pass them to `iteratee`", function( done ){
				instance.hookup( PRE_TEST, hook, hook, hook );
				var stub = sinon.stub();
				stub.callsArgAsync( 1 ); // calls `next`
				instance.hooks( PRE_TEST, stub, function(){
					expect( stub.callCount ).to.equal( 3 );
					expect( stub.alwaysCalledWith( hook ) ); //`hook` is first argument
					done();
				} );
			} );
			it( "should execute `iteratee` in scope `context`", function(){
				instance.hookup( PRE_TEST, hook );
				var context = {};
				instance.hooks( context, PRE_TEST, function( hook,
															 next ){
					expect( this ).to.equal( context );
					next();
				} );
			} )
		} );
		describe( "#emit", function(){
			var hook,
				passed,
				foo = {},
				bar = {};
			beforeEach( function(){
				passed = {
					scope: undefined,
					args : undefined,
					async : false
				};
				hook = function( foo,
								 bar,
								 next ){
					passed.args = [ foo, bar ];
					passed.scope = this;
					setTimeout( function(){
						passed.async = true;
						next();
					}, 0 );
				};
				instance.hookable( "test" );
				instance.hookup( PRE_TEST, hook );
			} );
			it( "should pass `...parameters` to hooks", function(){
				instance.emit( PRE_TEST, foo, bar );
				expect( passed.args ).to.eql( [ foo, bar ] );
			} );
			it( "should pass `parameters[]` to hooks", function(){
				instance.emit( PRE_TEST, [ foo, bar ] );
				expect( passed.args ).to.eql( [ foo, bar ] );
			} );
			it( "execute hooks in scope `context`", function(){
				instance.hookup( PRE_TEST, hook );
				var context = {};
				instance.emit( context, PRE_TEST, [ foo, bar ] );
				expect( passed.scope ).to.equal( context );
			} );
			it( "should call `callback` when all hooks have been called", function( done ){
				instance.emit( PRE_TEST, [ foo, bar ], function(){
					expect( passed.async ).to.be.true();
					done();
				} );
			} );
		} );
	} );
} );

<|endoftext|>