<commit_before>import classNames from 'classnames';
import {Confirm, Table} from 'reactjs-components';
import {Link} from 'react-router';
import moment from 'moment';
import mixin from 'reactjs-mixin';
/* eslint-disable no-unused-vars */
import React from 'react';
/* eslint-enable no-unused-vars */
import {StoreMixin} from 'mesosphere-shared-reactjs';

import AlertPanel from '../../components/AlertPanel';
import DCOSStore from '../../stores/DCOSStore';
import defaultServiceImage from '../../../img/services/icon-service-default-small@2x.png';
import Icon from '../../components/Icon';
import MarathonActions from '../../events/MarathonActions';
import NestedServiceLinks from '../../components/NestedServiceLinks';
import ResourceTableUtil from '../../utils/ResourceTableUtil';
import StringUtil from '../../utils/StringUtil';

const columnHeading = ResourceTableUtil.renderHeading({
  id: 'AFFECTED SERVICES',
  startTime: 'STARTED',
  location: 'LOCATION',
  status: 'STATUS',
  action: ''
});
const COLLAPSING_COLUMNS = ['location', 'startTime', 'action'];
const METHODS_TO_BIND = [
  'renderAffectedServices',
  'renderAffectedServicesList',
  'renderLocation',
  'renderStartTime',
  'renderStatus',
  'renderAction',
  'handleRollbackClick',
  'handleRollbackCancel',
  'handleRollbackConfirm',
  'onMarathonStoreDeploymentRollbackSuccess',
  'onMarathonStoreDeploymentRollbackError'
];

// collapsing columns are tightly coupled to the left-align caret property;
// this wrapper allows ordinary columns to collapse.
function columnClassNameGetter(prop, sortBy, row) {
  let classSet = ResourceTableUtil.getClassName(prop, sortBy, row);
  if (COLLAPSING_COLUMNS.includes(prop)) {
    return classNames(classSet, 'hidden-mini');
  }
  return classSet;
}

class DeploymentsTab extends mixin(StoreMixin) {

  constructor() {
    super(...arguments);

    this.state = {};
    this.store_listeners = [
      {name: 'dcos', events: ['change']},
      {
        name: 'marathon',
        events: ['deploymentRollbackSuccess', 'deploymentRollbackError']
      }
    ];

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

  onMarathonStoreDeploymentRollbackSuccess(data) {
    let {deploymentToRollback} = this.state;
    if (deploymentToRollback != null &&
        deploymentToRollback.getId() === data.originalDeploymentID) {
      this.setState({
        awaitingRevertDeploymentResponse: false,
        deploymentToRollback: null,
        deploymentRollbackError: null
      });
    }
  }

  onMarathonStoreDeploymentRollbackError(data) {
    let {deploymentToRollback} = this.state;
    if (deploymentToRollback != null &&
        deploymentToRollback.getId() === data.originalDeploymentID) {
      this.setState({
        awaitingRevertDeploymentResponse: false,
        deploymentRollbackError: data.error
      });
    }
  }

  renderAffectedServices(prop, deployment) {
    return (

{deployment.getId()}

        {this.renderAffectedServicesList(deployment.getAffectedServices())}
        {this.renderStaleServicesList(deployment.getStaleServiceIds())}

    );
  }

  renderStaleServicesList(serviceIds) {
    return serviceIds.map(function (serviceId, index) {
      return (
        <dd key={`stale_${index}`}>

          {serviceId}

      );
    });
  }

  renderAffectedServicesList(services) {
    return services.map(function (service, index) {
      const id = encodeURIComponent(service.getId());
      const image = service.getImages()['icon-small'];

      return (
        <dd key={`service_${index}`}>
          <Link to="services-detail" params={{id}} className="deployment-service-name">

            {service.getName()}
          </Link>

      );
    });
  }

  renderLocation(prop, deployment) {
    const services = deployment.getAffectedServices();
    const items = services.map(function (service, index) {
      return (

          <NestedServiceLinks
            serviceID={service.getId()}
            className="deployment-breadcrumb"
            majorLinkClassName="deployment-breadcrumb-service-id"
            minorLinkWrapperClassName="deployment-breadcrumb-crumb" />

      );
    });

    return (

        {items}

    );
  }

  renderStartTime(prop, deployment) {
    const time = deployment.getStartTime();

    return (
      <time dateTime={time.toISOString()} className="deployment-start-time">
        {moment(time).fromNow()}
      </time>
    );
  }

  renderStatus(prop, deployment) {
    const title = `Step ${deployment.getCurrentStep()} of ${deployment.getTotalSteps()}`;
    const services = deployment.getAffectedServices();

    let currentActions = {};
    if (deployment.currentActions && deployment.currentActions.length > 0) {
      currentActions =
        deployment.currentActions.reduce(function (memo, action) {
          memo[action.app] = action.action;

          return memo;
        }, {});
    }

    const items = services.map(function (service, index) {
      let statusText = service.getStatus();
      if (currentActions[service.id] != null) {
        statusText += ` (${currentActions[service.id]})`;
      }

      return (

          {statusText}

      );
    });

    return (

        {title}

{items}

    );
  }

  renderAction(prop, deployment) {
    if (deployment != null) {
      let linkText = 'Rollback';
      if (deployment.isStarting()) {
        linkText = linkText + ' & Remove';
      }
      return (

          {linkText}

      );
    }
  }

  handleRollbackClick(deployment) {
    this.setState({deploymentToRollback: deployment});
  }

  handleRollbackCancel() {
    this.setState({
      awaitingRevertDeploymentResponse: false,
      deploymentToRollback: null,
      deploymentRollbackError: null
    });
  }

  handleRollbackConfirm() {
    let {deploymentToRollback} = this.state;
    if (deploymentToRollback != null) {
      this.setState({awaitingRevertDeploymentResponse: true});
      MarathonActions.revertDeployment(deploymentToRollback.getId());
    }
  }

  getColumns() {
    return [
      {
        className: columnClassNameGetter,
        heading: columnHeading,
        prop: 'id',
        render: this.renderAffectedServices
      },
      {
        className: columnClassNameGetter,
        heading: columnHeading,
        prop: 'location',
        render: this.renderLocation
      },
      {
        className: columnClassNameGetter,
        heading: columnHeading,
        prop: 'startTime',
        render: this.renderStartTime
      },
      {
        className: columnClassNameGetter,
        heading: columnHeading,
        prop: 'status',
        render: this.renderStatus
      },
      {
        className: columnClassNameGetter,
        heading: columnHeading,
        prop: 'action',
        render: this.renderAction
      }
    ];
  }

  getColGroup() {
    return (
      <colgroup>
        <col style={{width: '300px'}} />
        <col className="hidden-mini" />
        <col className="hidden-mini" style={{width: '120px'}} />
        <col style={{width: '240px'}} />
        <col className="hidden-mini" style={{width: '160px'}} />
      </colgroup>
    );
  }

  renderEmpty() {
    return (
      <AlertPanel
        title="No Deployments"
        icon={<Icon id="services" color="white" size="jumbo" />}>

Active deployments will be shown here.

      </AlertPanel>
    );
  }

  renderPopulated(deploymentsItems) {
    let deploymentsCount = deploymentsItems.length;
    let deploymentsLabel = StringUtil.pluralize('Deployment', deploymentsCount);

    return (

          {deploymentsCount} Active {deploymentsLabel}

        {this.renderRollbackModal()}

    );
  }

  renderRollbackModal() {
    let {
      awaitingRevertDeploymentResponse,
      deploymentToRollback,
      deploymentRollbackError
    } = this.state;

    if (deploymentToRollback != null) {
      return (
        <Confirm
          closeByBackdropClick={true}
          disabled={!!awaitingRevertDeploymentResponse}
          footerContainerClass="container container-pod container-pod-short
            container-pod-fluid flush-top flush-bottom"
          onClose={this.handleRollbackCancel}
          leftButtonCallback={this.handleRollbackCancel}
          leftButtonText="Cancel"
          rightButtonClassName="button button-danger"
          rightButtonCallback={this.handleRollbackConfirm}
          rightButtonText="Continue Rollback">

You're About To Rollback The Deployment

{this.getRollbackModalText(deploymentToRollback)}

            {this.renderRollbackError(deploymentRollbackError)}

        </Confirm>
      );
    }
  }

  getRollbackModalText(deploymentToRollback) {
    let serviceNames = deploymentToRollback.getAffectedServices()
      .map(function (service) {
        return StringUtil.capitalize(service.getName());
      });
    let listOfServiceNames = StringUtil.humanizeArray(serviceNames);
    let serviceCount = serviceNames.length;

    let service = StringUtil.pluralize('service', serviceCount);
    let its = (serviceCount === 1) ? 'its' : 'their';
    let version = StringUtil.pluralize('version', serviceCount);

    if (deploymentToRollback.isStarting()) {
      return `This will stop the current deployment of ${listOfServiceNames}
              and start a new deployment to remove the affected ${service}.`;
    }

    return `This will stop the current deployment of ${listOfServiceNames} and
            start a new deployment to revert the affected ${service} to ${its}
            previous ${version}.`;
  }

  renderRollbackError(deploymentRollbackError) {
    if (deploymentRollbackError != null) {
      return (

          {deploymentRollbackError}

      );
    }
  }

  render() {
    let deployments = DCOSStore.deploymentsList.getItems();

    if (deployments.length === 0) {
      return this.renderEmpty();
    } else {
      return this.renderPopulated(deployments);
    }
  }

}

module.exports = DeploymentsTab;

<commit_msg>Avoid errors when no stale service IDs are present<commit_after>import classNames from 'classnames';
import {Confirm, Table} from 'reactjs-components';
import {Link} from 'react-router';
import moment from 'moment';
import mixin from 'reactjs-mixin';
/* eslint-disable no-unused-vars */
import React from 'react';
/* eslint-enable no-unused-vars */
import {StoreMixin} from 'mesosphere-shared-reactjs';

import AlertPanel from '../../components/AlertPanel';
import DCOSStore from '../../stores/DCOSStore';
import defaultServiceImage from '../../../img/services/icon-service-default-small@2x.png';
import Icon from '../../components/Icon';
import MarathonActions from '../../events/MarathonActions';
import NestedServiceLinks from '../../components/NestedServiceLinks';
import ResourceTableUtil from '../../utils/ResourceTableUtil';
import StringUtil from '../../utils/StringUtil';

const columnHeading = ResourceTableUtil.renderHeading({
  id: 'AFFECTED SERVICES',
  startTime: 'STARTED',
  location: 'LOCATION',
  status: 'STATUS',
  action: ''
});
const COLLAPSING_COLUMNS = ['location', 'startTime', 'action'];
const METHODS_TO_BIND = [
  'renderAffectedServices',
  'renderAffectedServicesList',
  'renderLocation',
  'renderStartTime',
  'renderStatus',
  'renderAction',
  'handleRollbackClick',
  'handleRollbackCancel',
  'handleRollbackConfirm',
  'onMarathonStoreDeploymentRollbackSuccess',
  'onMarathonStoreDeploymentRollbackError'
];

// collapsing columns are tightly coupled to the left-align caret property;
// this wrapper allows ordinary columns to collapse.
function columnClassNameGetter(prop, sortBy, row) {
  let classSet = ResourceTableUtil.getClassName(prop, sortBy, row);
  if (COLLAPSING_COLUMNS.includes(prop)) {
    return classNames(classSet, 'hidden-mini');
  }
  return classSet;
}

class DeploymentsTab extends mixin(StoreMixin) {

  constructor() {
    super(...arguments);

    this.state = {};
    this.store_listeners = [
      {name: 'dcos', events: ['change']},
      {
        name: 'marathon',
        events: ['deploymentRollbackSuccess', 'deploymentRollbackError']
      }
    ];

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

  onMarathonStoreDeploymentRollbackSuccess(data) {
    let {deploymentToRollback} = this.state;
    if (deploymentToRollback != null &&
        deploymentToRollback.getId() === data.originalDeploymentID) {
      this.setState({
        awaitingRevertDeploymentResponse: false,
        deploymentToRollback: null,
        deploymentRollbackError: null
      });
    }
  }

  onMarathonStoreDeploymentRollbackError(data) {
    let {deploymentToRollback} = this.state;
    if (deploymentToRollback != null &&
        deploymentToRollback.getId() === data.originalDeploymentID) {
      this.setState({
        awaitingRevertDeploymentResponse: false,
        deploymentRollbackError: data.error
      });
    }
  }

  renderAffectedServices(prop, deployment) {
    return (

{deployment.getId()}

        {this.renderAffectedServicesList(deployment.getAffectedServices())}
        {this.renderStaleServicesList(deployment.getStaleServiceIds())}

    );
  }

  renderStaleServicesList(serviceIds) {
    if (serviceIds == null) {
      return;
    }
    return serviceIds.map(function (serviceId, index) {
      return (
        <dd key={`stale_${index}`}>

          {serviceId}

      );
    });
  }

  renderAffectedServicesList(services) {
    return services.map(function (service, index) {
      const id = encodeURIComponent(service.getId());
      const image = service.getImages()['icon-small'];

      return (
        <dd key={`service_${index}`}>
          <Link to="services-detail" params={{id}} className="deployment-service-name">

            {service.getName()}
          </Link>

      );
    });
  }

  renderLocation(prop, deployment) {
    const services = deployment.getAffectedServices();
    const items = services.map(function (service, index) {
      return (

          <NestedServiceLinks
            serviceID={service.getId()}
            className="deployment-breadcrumb"
            majorLinkClassName="deployment-breadcrumb-service-id"
            minorLinkWrapperClassName="deployment-breadcrumb-crumb" />

      );
    });

    return (

        {items}

    );
  }

  renderStartTime(prop, deployment) {
    const time = deployment.getStartTime();

    return (
      <time dateTime={time.toISOString()} className="deployment-start-time">
        {moment(time).fromNow()}
      </time>
    );
  }

  renderStatus(prop, deployment) {
    const title = `Step ${deployment.getCurrentStep()} of ${deployment.getTotalSteps()}`;
    const services = deployment.getAffectedServices();

    let currentActions = {};
    if (deployment.currentActions && deployment.currentActions.length > 0) {
      currentActions =
        deployment.currentActions.reduce(function (memo, action) {
          memo[action.app] = action.action;

          return memo;
        }, {});
    }

    const items = services.map(function (service, index) {
      let statusText = service.getStatus();
      if (currentActions[service.id] != null) {
        statusText += ` (${currentActions[service.id]})`;
      }

      return (

          {statusText}

      );
    });

    return (

        {title}

{items}

    );
  }

  renderAction(prop, deployment) {
    if (deployment != null) {
      let linkText = 'Rollback';
      if (deployment.isStarting()) {
        linkText = linkText + ' & Remove';
      }
      return (

          {linkText}

      );
    }
  }

  handleRollbackClick(deployment) {
    this.setState({deploymentToRollback: deployment});
  }

  handleRollbackCancel() {
    this.setState({
      awaitingRevertDeploymentResponse: false,
      deploymentToRollback: null,
      deploymentRollbackError: null
    });
  }

  handleRollbackConfirm() {
    let {deploymentToRollback} = this.state;
    if (deploymentToRollback != null) {
      this.setState({awaitingRevertDeploymentResponse: true});
      MarathonActions.revertDeployment(deploymentToRollback.getId());
    }
  }

  getColumns() {
    return [
      {
        className: columnClassNameGetter,
        heading: columnHeading,
        prop: 'id',
        render: this.renderAffectedServices
      },
      {
        className: columnClassNameGetter,
        heading: columnHeading,
        prop: 'location',
        render: this.renderLocation
      },
      {
        className: columnClassNameGetter,
        heading: columnHeading,
        prop: 'startTime',
        render: this.renderStartTime
      },
      {
        className: columnClassNameGetter,
        heading: columnHeading,
        prop: 'status',
        render: this.renderStatus
      },
      {
        className: columnClassNameGetter,
        heading: columnHeading,
        prop: 'action',
        render: this.renderAction
      }
    ];
  }

  getColGroup() {
    return (
      <colgroup>
        <col style={{width: '300px'}} />
        <col className="hidden-mini" />
        <col className="hidden-mini" style={{width: '120px'}} />
        <col style={{width: '240px'}} />
        <col className="hidden-mini" style={{width: '160px'}} />
      </colgroup>
    );
  }

  renderEmpty() {
    return (
      <AlertPanel
        title="No Deployments"
        icon={<Icon id="services" color="white" size="jumbo" />}>

Active deployments will be shown here.

      </AlertPanel>
    );
  }

  renderPopulated(deploymentsItems) {
    let deploymentsCount = deploymentsItems.length;
    let deploymentsLabel = StringUtil.pluralize('Deployment', deploymentsCount);

    return (

          {deploymentsCount} Active {deploymentsLabel}

        {this.renderRollbackModal()}

    );
  }

  renderRollbackModal() {
    let {
      awaitingRevertDeploymentResponse,
      deploymentToRollback,
      deploymentRollbackError
    } = this.state;

    if (deploymentToRollback != null) {
      return (
        <Confirm
          closeByBackdropClick={true}
          disabled={!!awaitingRevertDeploymentResponse}
          footerContainerClass="container container-pod container-pod-short
            container-pod-fluid flush-top flush-bottom"
          onClose={this.handleRollbackCancel}
          leftButtonCallback={this.handleRollbackCancel}
          leftButtonText="Cancel"
          rightButtonClassName="button button-danger"
          rightButtonCallback={this.handleRollbackConfirm}
          rightButtonText="Continue Rollback">

You're About To Rollback The Deployment

{this.getRollbackModalText(deploymentToRollback)}

            {this.renderRollbackError(deploymentRollbackError)}

        </Confirm>
      );
    }
  }

  getRollbackModalText(deploymentToRollback) {
    let serviceNames = deploymentToRollback.getAffectedServices()
      .map(function (service) {
        return StringUtil.capitalize(service.getName());
      });
    let listOfServiceNames = StringUtil.humanizeArray(serviceNames);
    let serviceCount = serviceNames.length;

    let service = StringUtil.pluralize('service', serviceCount);
    let its = (serviceCount === 1) ? 'its' : 'their';
    let version = StringUtil.pluralize('version', serviceCount);

    if (deploymentToRollback.isStarting()) {
      return `This will stop the current deployment of ${listOfServiceNames}
              and start a new deployment to remove the affected ${service}.`;
    }

    return `This will stop the current deployment of ${listOfServiceNames} and
            start a new deployment to revert the affected ${service} to ${its}
            previous ${version}.`;
  }

  renderRollbackError(deploymentRollbackError) {
    if (deploymentRollbackError != null) {
      return (

          {deploymentRollbackError}

      );
    }
  }

  render() {
    let deployments = DCOSStore.deploymentsList.getItems();

    if (deployments.length === 0) {
      return this.renderEmpty();
    } else {
      return this.renderPopulated(deployments);
    }
  }

}

module.exports = DeploymentsTab;

<|endoftext|>


<commit_before>import "../../src/collab"

import {doc, blockquote, pre, h1, h2, p, li, ol, ul, em, strong, code, a, a2, br, hr} from "../build"
import {defTest} from "../tests"
import {P, cmpNode} from "../cmp"
import {tempEditors} from "./def"

class DummyServer {
  constructor() {
    this.version = 0
    this.pms = []
  }

  attach(pm) {
    pm.mod.collab.on("mustSend", () => this.mustSend(pm))
    this.pms.push(pm)
  }

  mustSend(pm) {
    if (pm.mod.collab.frozen) return
    let toSend = pm.mod.collab.sendableSteps()
    this.send(pm, toSend.version, toSend.steps)
    pm.mod.collab.confirmSteps(toSend)
  }

  send(pm, version, steps) {
    this.version += steps.length
    for (let i = 0; i < this.pms.length; i++)
      if (this.pms[i] != pm) this.pms[i].mod.collab.receive(steps)
  }
}

// Kludge to prevent an editor from sending its changes for a moment
function delay(pm, f) {
  pm.mod.collab.frozen = true
  f()
  pm.mod.collab.frozen = false
  if (pm.mod.collab.hasSendableSteps())
    pm.mod.collab.signal("mustSend")
}

function test(name, f, options, n) {
  defTest("collab_" + name, () => {
    let server = new DummyServer
    let optArray = []
    for (let i = 0; i < (n || 2); i++) {
      let copy = {collab: {version: server.version}}
      for (var prop in options) copy[prop] = options[prop]
      optArray.push(copy)
    }
    let pms = tempEditors(optArray)
    pms.forEach(pm => server.attach(pm))
    f.apply(null, pms)
  })
}

function type(pm, text, pos) {
  pm.apply(pm.tr.insertText(pos || pm.selection.head, text))
}

function cut(pm) { pm.history.lastAddedAt = 0 }

function conv(...args) {
  let d = args.pop()
  if (typeof d == "string") d = doc(p(d))
  args.forEach(pm => cmpNode(pm.doc, d))
}

test("converge_easy", (pm1, pm2) => {
  type(pm1, "hi")
  type(pm2, "ok", P(0, 2))
  type(pm1, "!", P(0, 4))
  type(pm2, "...", P(0, 0))
  conv(pm1, pm2, "...hiok!")
})

test("converge_rebased", (pm1, pm2) => {
  type(pm1, "hi")
  delay(pm1, () => {
    type(pm1, "A")
    type(pm2, "X")
    type(pm1, "B")
    type(pm2, "Y")
  })
  conv(pm1, pm2, "hiXYAB")
})

test("converge_three", (pm1, pm2, pm3) => {
  type(pm1, "A")
  type(pm2, "U")
  type(pm3, "X")
  type(pm1, "B")
  type(pm2, "V")
  type(pm3, "C")
  conv(pm1, pm2, pm3, "AUXBVC")
}, null, 3)

test("converge_three_rebased", (pm1, pm2, pm3) => {
  type(pm1, "A")
  delay(pm2, () => {
    type(pm2, "U")
    type(pm3, "X")
    type(pm1, "B")
    type(pm2, "V")
    type(pm3, "C")
  })
  conv(pm1, pm2, pm3, "AXBCUV")
}, null, 3)

test("undo", (pm1, pm2) => {
  type(pm1, "A")
  type(pm2, "B")
  type(pm1, "C")
  pm2.execCommand("undo")
  type(pm2, "D")
  type(pm1, "E")
  conv(pm1, pm2, "ACDE")
})

test("redo", (pm1, pm2) => {
  type(pm1, "A")
  type(pm2, "B")
  type(pm1, "C")
  pm2.execCommand("undo")
  pm2.execCommand("redo")
  type(pm2, "D")
  type(pm1, "E")
  conv(pm1, pm2, "ABCDE")
})

test("undo_deep", (pm1, pm2) => {
  pm1.setSelection(P(0, 5))
  pm2.setSelection(P(1, 3))
  type(pm1, "!")
  type(pm2, "!")
  cut(pm1)
  delay(pm1, () => {
    type(pm1, " ...")
    type(pm2, " ,,,")
  })
  cut(pm1)
  type(pm1, "*")
  type(pm2, "*")
  pm1.execCommand("undo")
  conv(pm1, pm2, doc(p("hello! ..."), p("bye! ,,,*")))
  pm1.execCommand("undo")
  pm1.execCommand("undo")
  conv(pm1, pm2, doc(p("hello"), p("bye! ,,,*")))
  pm1.execCommand("redo")
  pm1.execCommand("redo")
  pm1.execCommand("redo")
  conv(pm1, pm2, doc(p("hello! ...*"), p("bye! ,,,*")))
  pm1.execCommand("undo")
  pm1.execCommand("undo")
  conv(pm1, pm2, doc(p("hello!"), p("bye! ,,,*")))
  pm2.execCommand("undo")
  conv(pm1, pm2, doc(p("hello!"), p("bye")))
}, {doc: doc(p("hello"), p("bye"))})

test("undo_deleted_event", (pm1, pm2) => {
  type(pm1, "A", P(0, 5))
  delay(pm1, () => {
    type(pm1, "B", P(0, 3))
    type(pm1, "C", P(0, 4))
    type(pm1, "D", P(0, 0))
    pm2.apply(pm2.tr.delete(P(0, 1), P(0, 4)))
  })
  conv(pm1, pm2, "DhoA")
  pm1.execCommand("undo")
  conv(pm1, pm2, "ho")
}, {doc: doc(p("hello"))})
<commit_msg>Add commented-out TP_2 test<commit_after>import "../../src/collab"

import {doc, blockquote, pre, h1, h2, p, li, ol, ul, em, strong, code, a, a2, br, hr} from "../build"
import {defTest} from "../tests"
import {P, cmpNode} from "../cmp"
import {tempEditors} from "./def"

class DummyServer {
  constructor() {
    this.version = 0
    this.pms = []
  }

  attach(pm) {
    pm.mod.collab.on("mustSend", () => this.mustSend(pm))
    this.pms.push(pm)
  }

  mustSend(pm) {
    if (pm.mod.collab.frozen) return
    let toSend = pm.mod.collab.sendableSteps()
    this.send(pm, toSend.version, toSend.steps)
    pm.mod.collab.confirmSteps(toSend)
  }

  send(pm, version, steps) {
    this.version += steps.length
    for (let i = 0; i < this.pms.length; i++)
      if (this.pms[i] != pm) this.pms[i].mod.collab.receive(steps)
  }
}

// Kludge to prevent an editor from sending its changes for a moment
function delay(pm, f) {
  pm.mod.collab.frozen = true
  f()
  pm.mod.collab.frozen = false
  if (pm.mod.collab.hasSendableSteps())
    pm.mod.collab.signal("mustSend")
}

function test(name, f, options, n) {
  defTest("collab_" + name, () => {
    let server = new DummyServer
    let optArray = []
    for (let i = 0; i < (n || 2); i++) {
      let copy = {collab: {version: server.version}}
      for (var prop in options) copy[prop] = options[prop]
      optArray.push(copy)
    }
    let pms = tempEditors(optArray)
    pms.forEach(pm => server.attach(pm))
    f.apply(null, pms)
  })
}

function type(pm, text, pos) {
  pm.apply(pm.tr.insertText(pos || pm.selection.head, text))
}

function cut(pm) { pm.history.lastAddedAt = 0 }

function conv(...args) {
  let d = args.pop()
  if (typeof d == "string") d = doc(p(d))
  args.forEach(pm => cmpNode(pm.doc, d))
}

test("converge_easy", (pm1, pm2) => {
  type(pm1, "hi")
  type(pm2, "ok", P(0, 2))
  type(pm1, "!", P(0, 4))
  type(pm2, "...", P(0, 0))
  conv(pm1, pm2, "...hiok!")
})

test("converge_rebased", (pm1, pm2) => {
  type(pm1, "hi")
  delay(pm1, () => {
    type(pm1, "A")
    type(pm2, "X")
    type(pm1, "B")
    type(pm2, "Y")
  })
  conv(pm1, pm2, "hiXYAB")
})

test("converge_three", (pm1, pm2, pm3) => {
  type(pm1, "A")
  type(pm2, "U")
  type(pm3, "X")
  type(pm1, "B")
  type(pm2, "V")
  type(pm3, "C")
  conv(pm1, pm2, pm3, "AUXBVC")
}, null, 3)

test("converge_three_rebased", (pm1, pm2, pm3) => {
  type(pm1, "A")
  delay(pm2, () => {
    type(pm2, "U")
    type(pm3, "X")
    type(pm1, "B")
    type(pm2, "V")
    type(pm3, "C")
  })
  conv(pm1, pm2, pm3, "AXBCUV")
}, null, 3)

test("undo", (pm1, pm2) => {
  type(pm1, "A")
  type(pm2, "B")
  type(pm1, "C")
  pm2.execCommand("undo")
  type(pm2, "D")
  type(pm1, "E")
  conv(pm1, pm2, "ACDE")
})

test("redo", (pm1, pm2) => {
  type(pm1, "A")
  type(pm2, "B")
  type(pm1, "C")
  pm2.execCommand("undo")
  pm2.execCommand("redo")
  type(pm2, "D")
  type(pm1, "E")
  conv(pm1, pm2, "ABCDE")
})

test("undo_deep", (pm1, pm2) => {
  pm1.setSelection(P(0, 5))
  pm2.setSelection(P(1, 3))
  type(pm1, "!")
  type(pm2, "!")
  cut(pm1)
  delay(pm1, () => {
    type(pm1, " ...")
    type(pm2, " ,,,")
  })
  cut(pm1)
  type(pm1, "*")
  type(pm2, "*")
  pm1.execCommand("undo")
  conv(pm1, pm2, doc(p("hello! ..."), p("bye! ,,,*")))
  pm1.execCommand("undo")
  pm1.execCommand("undo")
  conv(pm1, pm2, doc(p("hello"), p("bye! ,,,*")))
  pm1.execCommand("redo")
  pm1.execCommand("redo")
  pm1.execCommand("redo")
  conv(pm1, pm2, doc(p("hello! ...*"), p("bye! ,,,*")))
  pm1.execCommand("undo")
  pm1.execCommand("undo")
  conv(pm1, pm2, doc(p("hello!"), p("bye! ,,,*")))
  pm2.execCommand("undo")
  conv(pm1, pm2, doc(p("hello!"), p("bye")))
}, {doc: doc(p("hello"), p("bye"))})

test("undo_deleted_event", (pm1, pm2) => {
  type(pm1, "A", P(0, 5))
  delay(pm1, () => {
    type(pm1, "B", P(0, 3))
    type(pm1, "C", P(0, 4))
    type(pm1, "D", P(0, 0))
    pm2.apply(pm2.tr.delete(P(0, 1), P(0, 4)))
  })
  conv(pm1, pm2, "DhoA")
  pm1.execCommand("undo")
  conv(pm1, pm2, "ho")
}, {doc: doc(p("hello"))})

/* This is related to the TP_2 condition often referenced in OT
   literature -- if you insert at two points but then pull out the
   content between those points, are the inserts still ordered
   properly. Our algorithm does not guarantee this.

test("tp_2", (pm1, pm2, pm3) => {
  delay(pm1, () => {
    delay(pm3, () => {
      type(pm1, "x", P(0, 1))
      type(pm3, "y", P(0, 2))
      pm2.apply(pm2.tr.delete(P(0, 1), P(0, 2)))
    })
  })
  conv(pm1, pm2, pm3, doc(p("axyc")))
}, {doc: doc(p("abc"))}, 3)
*/
<|endoftext|>


<commit_before>/*jshint esversion: 6 */
myApp.directive('fileReader', function () {
    return {
        scope: {
            fileReader: "=",
            parseResult: "="
        },
        link: function (scope, element) {
            scope.fileReader = {
                header: null,
                contents: []
            };
            $(element).on('change', function (changeEvent) {
                var f = changeEvent.target.files;
                if (f.length) {
                    var r = new FileReader();
                    r.onload = function (e) {
                        var contents = e.target.result;
                        scope.$apply(function () {
                            result = Papa.parse(contents, {
                                "skipEmptyLines": true
                            }).data;
                            scope.parseResult = arrayToJSON(transposeArray(result));
                            scope.fileReader.header = result[0];
                            for (var i = 1; i < result.length; i++) {
                                scope.fileReader.contents.push(result[i]);
                            }
                        });
                    };
                    r.readAsText(f[0]);
                }
            });
        }
    };
});

myApp.directive('peakSelect', function () {
    return {
        restrict: 'E',
        scope: {
            peakTypes: "=peakTypes",
            selectedPeak: "=ngModel"
        },
        require: 'ngModel',
        templateUrl: '/static/partials/peak-select.html',
        link: function (scope, element, attrs) { }
    };
});

myApp.directive('lineGraph', [function () {

    return {
        restrict: 'EA',
        scope: {
            data: "@",
            selectY: "@",
            selectX: "@"
        },
        link: function (scope, element, attrs) {
            //d3Service.d3().then(function(d3) {
            var keys = {
                "x": scope.selectX,
                "y": scope.selectY
            };
            var data = JSON.parse(scope.data);
            var dArray = [];
            for (var x in data) {
                data[x].forEach(function (element, idx) {
                    if (typeof dArray[idx] === "undefined") {
                        dArray[idx] = {};
                    }
                    dArray[idx][x] = element;
                });
            }

            // set the style of the the element to have a width of 100%
            var svg = d3.select(element[0])
                .append('svg')
                .style('width', '100%');

            // Watch for resize event
            scope.$watch(function () {
                return angular.element(window)[0].innerWidth;
            }, function () {

                scope.render(keys);
            });

            // Check for changes in bound data
            scope.$watch('selectX', function (newVal, oldVal) {
                keys.x = newVal;
                for (var x in keys) {
                    if (keys[x] == "") {
                        return;
                    }
                }
                return scope.render(keys);
            }, true);

            scope.$watch('selectY', function (newVal, oldVal) {
                keys.y = newVal;
                for (var x in keys) {
                    if (keys[x] == "") {
                        return;
                    }
                }
                return scope.render(keys);
            }, true);

            // Custom d3 code
            scope.render = function (keys) {
                // remove all previous items before render
                svg.selectAll('*').remove();

                // If we don't pass any data, return out of the element
                if (!keys || !data) return;
                // Set the dimensions of the canvas / graph
                var margin = {
                    top: 30,
                    right: 20,
                    bottom: 30,
                    left: 50
                },
                    width = 600 - margin.left - margin.right,
                    height = 270 - margin.top - margin.bottom;

                svg.attr("width", width + margin.left + margin.right)
                    .attr("height", height + margin.top + margin.bottom)
                    .attr("class", "chart");

                svg.append("g")
                    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

                // Set the ranges
                var x = d3.scaleLinear().range([0, width]).domain(d3.extent(data[keys.x]));
                let min_y = d3.min(data[keys.y])
                let max_y = d3.max(data[keys.y])
                let data_range = max_y - min_y
                // Set y data domain to be 5% larger than the data range.
                var y = d3.scaleLinear().range([height, 0]).domain([min_y - data_range * 0.025, max_y + data_range * 0.025]);

                // Define the axes
                var xAxis = d3.axisBottom().scale(x).ticks(5);

                var yAxis = d3.axisLeft().scale(y).ticks(5);

                // Define the line
                var valueline = d3.line()
                    .x(function (d) {
                        return x(d[keys.x]);
                    })
                    .y(function (d) {
                        return y(d[keys.y]);
                    });
                // Scale the range of the data
                // x.domain(d3.extent(data[keys.x]));
                // y.domain([d3.min(data[keys.y]), d3.max(data[keys.y]) * 1.025]);

                // Add the valueline path.
                svg.append("path")
                    .data([data])
                    .attr("class", "line")
                    .attr("id", "dataPath")
                    .attr("d", valueline(dArray))
                    .attr("stroke", "steelblue")
                    .attr("fill", "none")
                    .attr("stroke-width", "2px")
                    .attr("transform", "translate(" + margin.left + "," + (margin.top) + ")");

                // Add the X Axis
                svg.append("g")
                    .attr("class", "x axis")
                    .attr("transform", "translate(" + margin.left + "," + (height + margin.top) + ")")
                    .call(xAxis)
                    .append("text");

                // Add the Y Axis
                svg.append("g")
                    .attr("class", "y axis")
                    .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
                    .call(yAxis);

                // y-axis label
                svg.append("text")
                    .attr("text-anchor", "middle") // this makes it easy to centre the text as the transform is applied to the anchor
                    .attr("transform", "translate(" + (margin.left / 3) + "," + (height / 2) + ")rotate(-90)") // text is drawn off the screen top left, move down and out and rotate
                    .text(keys.y);

                // x-axis label
                svg.append("text")
                    .attr("text-anchor", "middle") // this makes it easy to centre the text as the transform is applied to the anchor
                    .attr("transform", "translate(" + (margin.left + width / 2) + "," + (height + margin.top + margin.bottom / 1.5) + ")") // text is drawn off the screen top left, move down and out and rotate
                    .text(keys.x);

            };
            //});
        }
    };
}]);

myApp.directive('steadyStateLineGraph', [function () {

    return {
        restrict: 'EA',
        scope: {
            data: "@",
            selectY: "@",
            selectX: "@",
            direction: "@"
        },
        link: function (scope, element, attrs) {
            //d3Service.d3().then(function(d3) {
            var keys = {
                "x": scope.selectX,
                "y": scope.selectY
            };
            var legendRectSize = 18;
            var legendSpacing = 4;
            var data = JSON.parse(scope.data);

            // Set color range
            var color = d3.scaleOrdinal(d3.schemeCategory10);

            var objectConversion = function (d) {
                var dArray = [];
                for (var x in d) {
                    d[x].forEach(function (element, idx) {
                        if (typeof dArray[idx] === "undefined") {
                            dArray[idx] = {};
                        }
                        dArray[idx][x] = element;
                    });
                }
                return dArray;
            };

            var dArray = objectConversion(data);

            // set the style of the the element to have a width of 100%
            var svg = d3.select(element[0])
                .append('svg')
                .style('width', '75%');

            // Browser onresize event
            window.onresize = function () {
                scope.$apply();
            };
            // Watch for resize event
            scope.$watch(function () {
                return angular.element(window)[0].innerWidth;
            }, function () {
                scope.render(keys);
            });
            // Check for changes in bound data
            scope.$watch('selectX', function (newVal, oldVal) {
                keys.x = newVal;
                for (var x in keys) {
                    if (keys[x] == "") {
                        return;
                    }
                }
                return scope.render(keys);
            }, true);

            scope.$watch('selectY', function (newVal, oldVal) {
                keys.y = newVal;
                for (var x in keys) {
                    if (keys[x] == "") {
                        return;
                    }
                }
                return scope.render(keys);
            }, true);

            var input_enc = {
                sao2: 'SaO2sup',
                pa: "P_a",
                paco2: "Pa_CO2"
            };

            // Custom d3 code
            scope.render = function (keys) {
                // remove all previous items before render
                svg.selectAll('*').remove();

                // If we don't pass any data, return out of the element
                if (!keys || !data) return;
                // Set the dimensions of the canvas / graph
                var margin = {
                    top: 20,
                    right: 20,
                    bottom: 50,
                    left: 75
                },
                    width = 600 - margin.left - margin.right,
                    mainHeight = 270 - margin.top - margin.bottom;

                svg.attr("width", width + margin.left + margin.right)
                    .attr("height", mainHeight + margin.top + margin.bottom)
                    .attr("class", "chart");

                svg.append("g")
                    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
                // Add the BSline path.
                $.get("static/data/json/brainsignals-" + scope.direction + "-autoreg.json", function (in_data) {
                    var bsData = in_data[keys.x];
                    //var bsArray = [];
                    var x = d3.scaleLinear().range([0, width]);
                    var y = d3.scaleLinear().range([mainHeight, 0]);

                    x.domain([
                        d3.min([d3.min(data[input_enc[keys.x]]), d3.min(bsData[input_enc[keys.x]])]),
                        d3.max([d3.max(data[input_enc[keys.x]]), d3.max(bsData[input_enc[keys.x]])])
                    ]);

                    y.domain([
                        d3.min([d3.min(data[keys.y]), d3.min(bsData[keys.y])]),
                        d3.max([d3.max(data[keys.y]), d3.max(bsData[keys.y])]) * 1.025
                    ]);
                    // Define the axes
                    var xAxis = d3.axisBottom().scale(x).ticks(5);
                    var yAxis = d3.axisLeft().scale(y).ticks(5);
                    // Add the valueline function.

                    var valueline = d3.line()
                        .x(function (d) {
                            return x(d[input_enc[keys.x]]);
                        })
                        .y(function (d) {
                            return y(d[keys.y]);
                        });

                    var bsArray = objectConversion(bsData);

                    var dataset = [{
                        "label": 'Default BrainSignals',
                        "data": bsData,
                        "array": bsArray
                    },
                    {
                        "label": 'Model Run',
                        "data": data,
                        "array": dArray
                    }
                    ];

                    var path = svg.selectAll('path')
                        .data(dataset, function (d) { return d; })
                        .enter()
                        .append('path')
                        .attr("class", "line")
                        .attr("d", function (d, i) {
                            return valueline(d.array);
                        })
                        .attr("transform", "translate(" + margin.left + "," + (margin.top) + ")")
                        .attr("fill", "none")
                        .attr("stroke-width", function (d) { return d.label === 'Default BrainSignals' ? '5px' : '2px'; })
                        .attr("stroke-dasharray", function (d) { return d.label === 'Default BrainSignals' ? '3' : ''; })
                        .attr("id", function (d) { return d.label === 'Default BrainSignals' ? 'bsPath' : ''; })
                        .attr('stroke', function (d, i) {
                            return color(d.label);
                        });

                    // Add the X Axis
                    svg.append("g")
                        .attr("class", "x axis")
                        .attr("transform", "translate(" + margin.left + "," + (mainHeight + margin.top) + ")")
                        .call(xAxis)
                        .append("text")
                        .attr("x", 6)
                        .attr("dx", ".71em")
                        .style("text-anchor", "end")
                        .text(input_enc[keys.x]);

                    // Add the Y Axis
                    svg.append("g")
                        .attr("class", "y axis")
                        .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
                        .call(yAxis);

                    // y-axis label
                    svg.append("text")
                        .attr("text-anchor", "middle") // this makes it easy to centre the text as the transform is applied to the anchor
                        .attr("transform", "translate(" + (margin.left / 3) + "," + (mainHeight / 2) + ")rotate(-90)") // text is drawn off the screen top left, move down and out and rotate
                        .text("CBF");

                    // x-axis label
                    svg.append("text")
                        .attr("text-anchor", "middle") // this makes it easy to centre the text as the transform is applied to the anchor
                        .attr("transform", "translate(" + (margin.left + width / 2) + "," + (mainHeight + margin.top + margin.bottom / 1.5) + ")") // text is drawn off the screen top left, move down and out and rotate
                        .text(input_enc[keys.x]);

                    var legend = svg.selectAll('.legend') // NEW
                        .data(color.domain()) // NEW
                        .enter() // NEW
                        .append('g') // NEW
                        .attr('class', 'legend') // NEW
                        .attr("font-size", "12px")
                        .attr('transform', function (d, i) { // NEW
                            var height = legendRectSize + legendSpacing; // NEW
                            var offset = height * color.domain().length / 2; // NEW
                            var horz = width + margin.left // NEW
                            var vert = i * height - offset; // NEW
                            return 'translate(' + horz + ',' + (vert + mainHeight / 2) + ')'; // NEW
                        });

                    legend.append('rect') // NEW
                        .attr('width', legendRectSize) // NEW
                        .attr('height', legendRectSize) // NEW
                        .attr('stroke-width', '2')
                        .style('fill', color) // NEW
                        .style('stroke', color); // NEW

                    legend.append('text') // NEW
                        .attr('x', legendRectSize + legendSpacing) // NEW
                        .attr('y', legendRectSize - legendSpacing) // NEW
                        .text(function (d) {
                            return d;
                        });
                });

            };
            //});
            scope.render(scope.keys);
        }
    };
}]);
<commit_msg>Fixes issue #19<commit_after>/*jshint esversion: 6 */
myApp.directive('fileReader', function () {
    return {
        scope: {
            fileReader: "=",
            parseResult: "="
        },
        link: function (scope, element) {
            scope.fileReader = {
                header: null,
                contents: []
            };

            var readFile = function (changeEvent) {
                var f = changeEvent.target.files;
                console.log("Uploaded ", f.type)

                if (f.length > 1) {
                    throw 'Uploaded more than one file'
                } else if (f.length < 1) {
                    throw "Did not upload a file."
                }
                if (f[0].type !== 'text/csv') {
                    throw 'Uploaded file must be a RFC4180-compliant CSV file';
                }
                var r = new FileReader();
                r.onload = function (e) {
                    var contents = e.target.result;
                    scope.$apply(function () {
                        result = Papa.parse(contents, {
                            "skipEmptyLines": true
                        }).data;
                        scope.parseResult = arrayToJSON(transposeArray(result));
                        scope.fileReader.header = result[0];
                        for (var i = 1; i < result.length; i++) {
                            scope.fileReader.contents.push(result[i]);
                        }
                    });
                };
                r.readAsText(f[0]);

            }
            try {
                $(element).on('change', readFile);
            } catch (error) {
                console.log(error);
            }

        }
    };
});

myApp.directive('peakSelect', function () {
    return {
        restrict: 'E',
        scope: {
            peakTypes: "=peakTypes",
            selectedPeak: "=ngModel"
        },
        require: 'ngModel',
        templateUrl: '/static/partials/peak-select.html',
        link: function (scope, element, attrs) { }
    };
});

myApp.directive('lineGraph', [function () {

    return {
        restrict: 'EA',
        scope: {
            data: "@",
            selectY: "@",
            selectX: "@"
        },
        link: function (scope, element, attrs) {
            //d3Service.d3().then(function(d3) {
            var keys = {
                "x": scope.selectX,
                "y": scope.selectY
            };
            var data = JSON.parse(scope.data);
            var dArray = [];
            for (var x in data) {
                data[x].forEach(function (element, idx) {
                    if (typeof dArray[idx] === "undefined") {
                        dArray[idx] = {};
                    }
                    dArray[idx][x] = element;
                });
            }

            // set the style of the the element to have a width of 100%
            var svg = d3.select(element[0])
                .append('svg')
                .style('width', '100%');

            // Watch for resize event
            scope.$watch(function () {
                return angular.element(window)[0].innerWidth;
            }, function () {

                scope.render(keys);
            });

            // Check for changes in bound data
            scope.$watch('selectX', function (newVal, oldVal) {
                keys.x = newVal;
                for (var x in keys) {
                    if (keys[x] == "") {
                        return;
                    }
                }
                return scope.render(keys);
            }, true);

            scope.$watch('selectY', function (newVal, oldVal) {
                keys.y = newVal;
                for (var x in keys) {
                    if (keys[x] == "") {
                        return;
                    }
                }
                return scope.render(keys);
            }, true);

            // Custom d3 code
            scope.render = function (keys) {
                // remove all previous items before render
                svg.selectAll('*').remove();

                // If we don't pass any data, return out of the element
                if (!keys || !data) return;
                // Set the dimensions of the canvas / graph
                var margin = {
                    top: 30,
                    right: 20,
                    bottom: 30,
                    left: 50
                },
                    width = 600 - margin.left - margin.right,
                    height = 270 - margin.top - margin.bottom;

                svg.attr("width", width + margin.left + margin.right)
                    .attr("height", height + margin.top + margin.bottom)
                    .attr("class", "chart");

                svg.append("g")
                    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

                // Set the ranges
                var x = d3.scaleLinear().range([0, width]).domain(d3.extent(data[keys.x]));
                let min_y = d3.min(data[keys.y])
                let max_y = d3.max(data[keys.y])
                let data_range = max_y - min_y
                // Set y data domain to be 5% larger than the data range.
                var y = d3.scaleLinear().range([height, 0]).domain([min_y - data_range * 0.025, max_y + data_range * 0.025]);

                // Define the axes
                var xAxis = d3.axisBottom().scale(x).ticks(5);

                var yAxis = d3.axisLeft().scale(y).ticks(5);

                // Define the line
                var valueline = d3.line()
                    .x(function (d) {
                        return x(d[keys.x]);
                    })
                    .y(function (d) {
                        return y(d[keys.y]);
                    });
                // Scale the range of the data
                // x.domain(d3.extent(data[keys.x]));
                // y.domain([d3.min(data[keys.y]), d3.max(data[keys.y]) * 1.025]);

                // Add the valueline path.
                svg.append("path")
                    .data([data])
                    .attr("class", "line")
                    .attr("id", "dataPath")
                    .attr("d", valueline(dArray))
                    .attr("stroke", "steelblue")
                    .attr("fill", "none")
                    .attr("stroke-width", "2px")
                    .attr("transform", "translate(" + margin.left + "," + (margin.top) + ")");

                // Add the X Axis
                svg.append("g")
                    .attr("class", "x axis")
                    .attr("transform", "translate(" + margin.left + "," + (height + margin.top) + ")")
                    .call(xAxis)
                    .append("text");

                // Add the Y Axis
                svg.append("g")
                    .attr("class", "y axis")
                    .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
                    .call(yAxis);

                // y-axis label
                svg.append("text")
                    .attr("text-anchor", "middle") // this makes it easy to centre the text as the transform is applied to the anchor
                    .attr("transform", "translate(" + (margin.left / 3) + "," + (height / 2) + ")rotate(-90)") // text is drawn off the screen top left, move down and out and rotate
                    .text(keys.y);

                // x-axis label
                svg.append("text")
                    .attr("text-anchor", "middle") // this makes it easy to centre the text as the transform is applied to the anchor
                    .attr("transform", "translate(" + (margin.left + width / 2) + "," + (height + margin.top + margin.bottom / 1.5) + ")") // text is drawn off the screen top left, move down and out and rotate
                    .text(keys.x);

            };
            //});
        }
    };
}]);

myApp.directive('steadyStateLineGraph', [function () {

    return {
        restrict: 'EA',
        scope: {
            data: "@",
            selectY: "@",
            selectX: "@",
            direction: "@"
        },
        link: function (scope, element, attrs) {
            //d3Service.d3().then(function(d3) {
            var keys = {
                "x": scope.selectX,
                "y": scope.selectY
            };
            var legendRectSize = 18;
            var legendSpacing = 4;
            var data = JSON.parse(scope.data);

            // Set color range
            var color = d3.scaleOrdinal(d3.schemeCategory10);

            var objectConversion = function (d) {
                var dArray = [];
                for (var x in d) {
                    d[x].forEach(function (element, idx) {
                        if (typeof dArray[idx] === "undefined") {
                            dArray[idx] = {};
                        }
                        dArray[idx][x] = element;
                    });
                }
                return dArray;
            };

            var dArray = objectConversion(data);

            // set the style of the the element to have a width of 100%
            var svg = d3.select(element[0])
                .append('svg')
                .style('width', '75%');

            // Browser onresize event
            window.onresize = function () {
                scope.$apply();
            };
            // Watch for resize event
            scope.$watch(function () {
                return angular.element(window)[0].innerWidth;
            }, function () {
                scope.render(keys);
            });
            // Check for changes in bound data
            scope.$watch('selectX', function (newVal, oldVal) {
                keys.x = newVal;
                for (var x in keys) {
                    if (keys[x] == "") {
                        return;
                    }
                }
                return scope.render(keys);
            }, true);

            scope.$watch('selectY', function (newVal, oldVal) {
                keys.y = newVal;
                for (var x in keys) {
                    if (keys[x] == "") {
                        return;
                    }
                }
                return scope.render(keys);
            }, true);

            var input_enc = {
                sao2: 'SaO2sup',
                pa: "P_a",
                paco2: "Pa_CO2"
            };

            // Custom d3 code
            scope.render = function (keys) {
                // remove all previous items before render
                svg.selectAll('*').remove();

                // If we don't pass any data, return out of the element
                if (!keys || !data) return;
                // Set the dimensions of the canvas / graph
                var margin = {
                    top: 20,
                    right: 20,
                    bottom: 50,
                    left: 75
                },
                    width = 600 - margin.left - margin.right,
                    mainHeight = 270 - margin.top - margin.bottom;

                svg.attr("width", width + margin.left + margin.right)
                    .attr("height", mainHeight + margin.top + margin.bottom)
                    .attr("class", "chart");

                svg.append("g")
                    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
                // Add the BSline path.
                $.get("static/data/json/brainsignals-" + scope.direction + "-autoreg.json", function (in_data) {
                    var bsData = in_data[keys.x];
                    //var bsArray = [];
                    var x = d3.scaleLinear().range([0, width]);
                    var y = d3.scaleLinear().range([mainHeight, 0]);

                    x.domain([
                        d3.min([d3.min(data[input_enc[keys.x]]), d3.min(bsData[input_enc[keys.x]])]),
                        d3.max([d3.max(data[input_enc[keys.x]]), d3.max(bsData[input_enc[keys.x]])])
                    ]);

                    y.domain([
                        d3.min([d3.min(data[keys.y]), d3.min(bsData[keys.y])]),
                        d3.max([d3.max(data[keys.y]), d3.max(bsData[keys.y])]) * 1.025
                    ]);
                    // Define the axes
                    var xAxis = d3.axisBottom().scale(x).ticks(5);
                    var yAxis = d3.axisLeft().scale(y).ticks(5);
                    // Add the valueline function.

                    var valueline = d3.line()
                        .x(function (d) {
                            return x(d[input_enc[keys.x]]);
                        })
                        .y(function (d) {
                            return y(d[keys.y]);
                        });

                    var bsArray = objectConversion(bsData);

                    var dataset = [{
                        "label": 'Default BrainSignals',
                        "data": bsData,
                        "array": bsArray
                    },
                    {
                        "label": 'Model Run',
                        "data": data,
                        "array": dArray
                    }
                    ];

                    var path = svg.selectAll('path')
                        .data(dataset, function (d) { return d; })
                        .enter()
                        .append('path')
                        .attr("class", "line")
                        .attr("d", function (d, i) {
                            return valueline(d.array);
                        })
                        .attr("transform", "translate(" + margin.left + "," + (margin.top) + ")")
                        .attr("fill", "none")
                        .attr("stroke-width", function (d) { return d.label === 'Default BrainSignals' ? '5px' : '2px'; })
                        .attr("stroke-dasharray", function (d) { return d.label === 'Default BrainSignals' ? '3' : ''; })
                        .attr("id", function (d) { return d.label === 'Default BrainSignals' ? 'bsPath' : ''; })
                        .attr('stroke', function (d, i) {
                            return color(d.label);
                        });

                    // Add the X Axis
                    svg.append("g")
                        .attr("class", "x axis")
                        .attr("transform", "translate(" + margin.left + "," + (mainHeight + margin.top) + ")")
                        .call(xAxis)
                        .append("text")
                        .attr("x", 6)
                        .attr("dx", ".71em")
                        .style("text-anchor", "end")
                        .text(input_enc[keys.x]);

                    // Add the Y Axis
                    svg.append("g")
                        .attr("class", "y axis")
                        .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
                        .call(yAxis);

                    // y-axis label
                    svg.append("text")
                        .attr("text-anchor", "middle") // this makes it easy to centre the text as the transform is applied to the anchor
                        .attr("transform", "translate(" + (margin.left / 3) + "," + (mainHeight / 2) + ")rotate(-90)") // text is drawn off the screen top left, move down and out and rotate
                        .text("CBF");

                    // x-axis label
                    svg.append("text")
                        .attr("text-anchor", "middle") // this makes it easy to centre the text as the transform is applied to the anchor
                        .attr("transform", "translate(" + (margin.left + width / 2) + "," + (mainHeight + margin.top + margin.bottom / 1.5) + ")") // text is drawn off the screen top left, move down and out and rotate
                        .text(input_enc[keys.x]);

                    var legend = svg.selectAll('.legend') // NEW
                        .data(color.domain()) // NEW
                        .enter() // NEW
                        .append('g') // NEW
                        .attr('class', 'legend') // NEW
                        .attr("font-size", "12px")
                        .attr('transform', function (d, i) { // NEW
                            var height = legendRectSize + legendSpacing; // NEW
                            var offset = height * color.domain().length / 2; // NEW
                            var horz = width + margin.left // NEW
                            var vert = i * height - offset; // NEW
                            return 'translate(' + horz + ',' + (vert + mainHeight / 2) + ')'; // NEW
                        });

                    legend.append('rect') // NEW
                        .attr('width', legendRectSize) // NEW
                        .attr('height', legendRectSize) // NEW
                        .attr('stroke-width', '2')
                        .style('fill', color) // NEW
                        .style('stroke', color); // NEW

                    legend.append('text') // NEW
                        .attr('x', legendRectSize + legendSpacing) // NEW
                        .attr('y', legendRectSize - legendSpacing) // NEW
                        .text(function (d) {
                            return d;
                        });
                });

            };
            //});
            scope.render(scope.keys);
        }
    };
}]);
<|endoftext|>


<commit_before>var expect = require('chai').expect;
var monoxide = require('..');
var testSetup = require('./setup');

describe('monoxide.save() / monoxideDocument.save()', function() {
	before(testSetup.init);
	after(testSetup.teardown);

	var users;
	it('should get a list of existing users', function(finish) {
		monoxide.query({
			$collection: 'users',
			$sort: 'name',
		}, function(err, res) {
			expect(err).to.be.not.ok;
			expect(res).to.be.an.array;
			users = res;
			finish();
		});
	});

	var widgets;
	it('should get a list of existing widgets', function(finish) {
		monoxide.query({
			$collection: 'widgets',
			$sort: 'name',
		}, function(err, res) {
			expect(err).to.be.not.ok;
			expect(res).to.be.an.array;
			widgets = res;
			finish();
		});
	});

	it('should save a user', function(finish) {
		monoxide.save({
			$collection: 'users',
			$id: users[0]._id,
			name: 'Edited User',
			mostPurchased: [
				{number: 12, item: widgets[0]._id},
				{number: 15, item: widgets[1]._id},
			],
		}, function(err, user) {
			expect(err).to.not.be.ok;
			expect(user).to.be.an.object;

			expect(user).to.have.property('name', 'Edited User');
			expect(user).to.have.property('role', 'user');
			expect(user).to.have.property('favourite');
			expect(user.favourite).to.be.a.string;
			expect(user).to.have.property('mostPurchased');
			expect(user.mostPurchased).to.be.an.array;
			expect(user.mostPurchased).to.have.length(2);
			expect(user.mostPurchased[0]).to.have.property('number', 12);
			expect(user.mostPurchased[0]).to.have.property('item', widgets[0]._id);
			expect(user.mostPurchased[0].item).to.be.a.string;
			expect(user.mostPurchased[1]).to.have.property('number', 15);
			expect(user.mostPurchased[1]).to.have.property('item', widgets[1]._id);
			expect(user.mostPurchased[1].item).to.be.a.string;

			finish();
		});
	});

	it('should save a user (via model)', function(finish) {
		users[1].name = 'Edited User2';
		users[1].mostPurchased = [
			{number: 18, item: widgets[1]._id},
			{number: 19, item: widgets[0]._id},
		];
		users[1].save(function(err, user) {
			expect(err).to.not.be.ok;
			expect(user).to.be.an.object;

			expect(user).to.have.property('name', 'Edited User2');
			expect(user).to.have.property('role', 'user');
			expect(user).to.have.property('favourite');
			expect(user.favourite).to.be.a.string;
			expect(user).to.have.property('mostPurchased');
			expect(user.mostPurchased).to.be.an.array;
			expect(user.mostPurchased).to.have.length(2);
			expect(user.mostPurchased[0]).to.have.property('number', 18);
			expect(user.mostPurchased[0]).to.have.property('item', widgets[1]._id);
			expect(user.mostPurchased[0].item).to.be.a.string;
			expect(user.mostPurchased[1]).to.have.property('number', 19);
			expect(user.mostPurchased[1]).to.have.property('item', widgets[0]._id);
			expect(user.mostPurchased[1].item).to.be.a.string;

			finish();
		});
	});
});
<commit_msg>Added additional type checks to save operation<commit_after>var expect = require('chai').expect;
var mongoose = require('mongoose');
var monoxide = require('..');
var testSetup = require('./setup');

describe('monoxide.save() / monoxideDocument.save()', function() {
	before(testSetup.init);
	after(testSetup.teardown);

	var users;
	it('should get a list of existing users', function(finish) {
		monoxide.query({
			$collection: 'users',
			$sort: 'name',
		}, function(err, res) {
			expect(err).to.be.not.ok;
			expect(res).to.be.an.array;
			users = res;
			finish();
		});
	});

	var widgets;
	it('should get a list of existing widgets', function(finish) {
		monoxide.query({
			$collection: 'widgets',
			$sort: 'name',
		}, function(err, res) {
			expect(err).to.be.not.ok;
			expect(res).to.be.an.array;
			widgets = res;

			// Quick check that we got _id's which are just strings
			widgets.forEach(function(widget) {
				expect(widget._id).to.be.a.string;
				expect(widget._id).to.not.be.an.instanceOf(mongoose.Types.ObjectId);
			});

			finish();
		});
	});

	it('should save a user', function(finish) {
		monoxide.save({
			$collection: 'users',
			$id: users[0]._id,
			name: 'Edited User',
			favourite: widgets[2]._id,
			mostPurchased: [
				{number: 12, item: widgets[0]._id},
				{number: 15, item: widgets[1]._id},
			],
		}, function(err, user) {
			expect(err).to.not.be.ok;
			expect(user).to.be.an.object;

			expect(user).to.have.property('name', 'Edited User');
			expect(user).to.have.property('role', 'user');
			expect(user).to.have.property('favourite', widgets[2]._id);
			expect(user).to.have.property('mostPurchased');
			expect(user.mostPurchased).to.be.an.array;
			expect(user.mostPurchased).to.have.length(2);
			expect(user.mostPurchased[0]).to.have.property('number', 12);
			expect(user.mostPurchased[0]).to.have.property('item', widgets[0]._id);
			expect(user.mostPurchased[0].item).to.be.a.string;
			expect(user.mostPurchased[1]).to.have.property('number', 15);
			expect(user.mostPurchased[1]).to.have.property('item', widgets[1]._id);
			expect(user.mostPurchased[1].item).to.be.a.string;

			// Now check that Mongoose returns what SHOULD be stored in the database
			// This mainly checks ObjectIDs have been set properly
			monoxide.models.users.$mongooseModel.findOne({name: 'Edited User'}, function(err, doc) {
				expect(err).to.be.not.ok;

				expect(doc).to.have.property('favourite');
				expect(doc.favourite.toString()).to.be.equal(widgets[2]._id);
				expect(doc.favourite).to.be.an.instanceOf(mongoose.Types.ObjectId);

				expect(doc.mostPurchased).to.be.an.array;
				expect(doc.mostPurchased).to.have.length(2);

				expect(doc.mostPurchased[0]).to.have.property('item');
				expect(doc.mostPurchased[0].item.toString()).to.be.equal(widgets[0]._id);
				expect(doc.mostPurchased[0].item).to.be.an.instanceOf(mongoose.Types.ObjectId);

				expect(doc.mostPurchased[1]).to.have.property('item');
				expect(doc.mostPurchased[1].item.toString()).to.be.equal(widgets[1]._id);
				expect(doc.mostPurchased[1].item).to.be.an.instanceOf(mongoose.Types.ObjectId);

				finish();
			});
		});
	});

	it('should save a user (via model)', function(finish) {
		users[1].name = 'Edited User2';
		users[1].mostPurchased = [
			{number: 18, item: widgets[1]._id},
			{number: 19, item: widgets[0]._id},
		];
		users[1].save(function(err, user) {
			expect(err).to.not.be.ok;
			expect(user).to.be.an.object;

			expect(user).to.have.property('name', 'Edited User2');
			expect(user).to.have.property('role', 'user');
			expect(user).to.have.property('favourite');
			expect(user.favourite).to.be.a.string;
			expect(user).to.have.property('mostPurchased');
			expect(user.mostPurchased).to.be.an.array;
			expect(user.mostPurchased).to.have.length(2);
			expect(user.mostPurchased[0]).to.have.property('number', 18);
			expect(user.mostPurchased[0]).to.have.property('item', widgets[1]._id);
			expect(user.mostPurchased[0].item).to.be.a.string;
			expect(user.mostPurchased[1]).to.have.property('number', 19);
			expect(user.mostPurchased[1]).to.have.property('item', widgets[0]._id);
			expect(user.mostPurchased[1].item).to.be.a.string;

			finish();
		});
	});
});
<|endoftext|>


<commit_before>(function(addon) {

    var component;

    if (window.UIkit) {
        component = addon(UIkit);
    }

    if (typeof define == "function" && define.amd) {
        define("uikit-sticky", ["uikit"], function(){
            return component || addon(UIkit);
        });
    }

})(function(UI){

    "use strict";

    var $win         = UI.$win,
        $doc         = UI.$doc,
        sticked      = [],
        direction    = 1;

    UI.component('sticky', {

        defaults: {
            top          : 0,
            bottom       : 0,
            animation    : '',
            clsinit      : 'uk-sticky-init',
            clsactive    : 'uk-active',
            clsinactive  : '',
            getWidthFrom : '',
            showup      : false,
            boundary     : false,
            media        : false,
            target       : false,
            disabled     : false
        },

        boot: function() {

            // should be more efficient than using $win.scroll(checkscrollposition):
            UI.$doc.on('scrolling.uk.document', function(e, data) {
                if (!data || !data.dir) return;
                direction = data.dir.y;
                checkscrollposition();
            });

            UI.$win.on('resize orientationchange', UI.Utils.debounce(function() {

                if (!sticked.length) return;

                for (var i = 0; i < sticked.length; i++) {
                    sticked[i].reset(true);
                    //sticked[i].self.computeWrapper();
                }

                checkscrollposition();
            }, 100));

            // init code
            UI.ready(function(context) {

                setTimeout(function(){

                    UI.$("[data-uk-sticky]", context).each(function(){

                        var $ele = UI.$(this);

                        if(!$ele.data("sticky")) {
                            UI.sticky($ele, UI.Utils.options($ele.attr('data-uk-sticky')));
                        }
                    });

                    checkscrollposition();
                }, 0);
            });
        },

        init: function() {

            var boundary = this.options.boundary, boundtoparent;

            this.wrapper = this.element.wrap('

').parent();
            this.computeWrapper();
            this.element.css('margin', 0);

            if (boundary) {

                if (boundary === true || boundary[0] === '!') {

                    boundary      = boundary === true ? this.wrapper.parent() : this.wrapper.closest(boundary.substr(1));
                    boundtoparent = true;

                } else if (typeof boundary === "string") {
                    boundary = UI.$(boundary);
                }
            }

            this.sticky = {
                self          : this,
                options       : this.options,
                element       : this.element,
                currentTop    : null,
                wrapper       : this.wrapper,
                init          : false,
                getWidthFrom  : UI.$(this.options.getWidthFrom || this.wrapper),
                boundary      : boundary,
                boundtoparent : boundtoparent,
                top           : 0,
                calcTop       : function() {

                    var top = this.options.top;

                    // dynamic top parameter
                    if (this.options.top && typeof(this.options.top) == 'string') {

                        // e.g. 50vh
                        if (this.options.top.match(/^(-|)(\d+)vh$/)) {
                            top = window.innerHeight * parseInt(this.options.top, 10)/100;
                        // e.g. #elementId, or .class-1,class-2,.class-3 (first found is used)
                        } else {

                            var topElement = UI.$(this.options.top).first();

                            if (topElement.length && topElement.is(':visible')) {
                                top = -1 * ((topElement.offset().top + topElement.outerHeight()) - this.wrapper.offset().top);
                            }
                        }

                    }

                    this.top = top;
                },

                reset: function(force) {

                    this.calcTop();

                    var finalize = function() {
                        this.element.css({"position":"", "top":"", "width":"", "left":"", "margin":"0"});
                        this.element.removeClass([this.options.animation, 'uk-animation-reverse', this.options.clsactive].join(' '));
                        this.element.addClass(this.options.clsinactive);
                        this.element.trigger('inactive.uk.sticky');

                        this.currentTop = null;
                        this.animate    = false;

                        this.computeWrapper();

                    }.bind(this);

                    if (!force && this.options.animation && UI.support.animation && !UI.Utils.isInView(this.wrapper)) {

                        this.animate = true;

                        this.element.removeClass(this.options.animation).one(UI.support.animation.end, function(){
                            finalize();
                        }).width(); // force redraw

                        this.element.addClass(this.options.animation+' '+'uk-animation-reverse');
                    } else {
                        finalize();
                    }
                },

                check: function() {

                    if (this.options.disabled) {
                        return false;
                    }

                    if (this.options.media) {

                        switch(typeof(this.options.media)) {
                            case 'number':
                                if (window.innerWidth < this.options.media) {
                                    return false;
                                }
                                break;
                            case 'string':
                                if (window.matchMedia && !window.matchMedia(this.options.media).matches) {
                                    return false;
                                }
                                break;
                        }
                    }

                    var scrollTop      = $win.scrollTop(),
                        documentHeight = $doc.height(),
                        dwh            = documentHeight - window.innerHeight,
                        extra          = (scrollTop > dwh) ? dwh - scrollTop : 0,
                        elementTop     = this.wrapper.offset().top,
                        etse           = elementTop - this.top - extra,
                        active         = (scrollTop  >= etse);

                    if (active && this.options.showup) {

                        // set inactiv if scrolling down
                        if (direction == 1) {
                            active = false;
                        }

                        // set inactive when wrapper is still in view
                        if (direction == -1 && !this.element.hasClass(this.options.clsactive) && UI.Utils.isInView(this.wrapper)) {
                            active = false;
                        }
                    }

                    return active;
                }
            };

            this.sticky.calcTop();

            sticked.push(this.sticky);
        },

        update: function() {
            checkscrollposition(this.sticky);
        },

        enable: function() {
            this.options.disabled = false;
            this.update();
        },

        disable: function(force) {
            this.options.disabled = true;
            this.sticky.reset(force);
        },

        computeWrapper: function() {

            this.wrapper.css({
                'height'        : ['absolute','fixed'].indexOf(this.element.css('position')) == -1 ? this.element.outerHeight() : '',
                'float'         : this.element.css('float') != 'none' ? this.element.css('float') : '',
                'margin-top'    : this.element.css('margin-top'),
                'margin-bottom' : this.element.css('margin-bottom'),
                'margin-left'   : this.element.css('margin-left'),
                'margin-right'  : this.element.css('margin-right')
            });

            if (this.element.css('position') == 'fixed') {
                this.element.css({
                    width: this.sticky.getWidthFrom.length ? this.sticky.getWidthFrom.width() : this.element.width()
                });
            }
        }
    });

    function checkscrollposition(direction) {

        var stickies = arguments.length ? arguments : sticked;

        if (!stickies.length || $win.scrollTop() < 0) return;

        var scrollTop       = $win.scrollTop(),
            documentHeight  = $doc.height(),
            windowHeight    = $win.height(),
            dwh             = documentHeight - windowHeight,
            extra           = (scrollTop > dwh) ? dwh - scrollTop : 0,
            newTop, containerBottom, stickyHeight, sticky;

        for (var i = 0; i < stickies.length; i++) {

            sticky = stickies[i];

            if (!sticky.element.is(":visible") || sticky.animate) {
                continue;
            }

            if (!sticky.check()) {

                if (sticky.currentTop !== null) {
                    sticky.reset();
                }

            } else {

                if (sticky.top < 0) {
                    newTop = 0;
                } else {
                    stickyHeight = sticky.element.outerHeight();
                    newTop = documentHeight - stickyHeight - sticky.top - sticky.options.bottom - scrollTop - extra;
                    newTop = newTop < 0 ? newTop + sticky.top : sticky.top;
                }

                if (sticky.boundary && sticky.boundary.length) {

                    var bTop = sticky.boundary.offset().top;

                    if (sticky.boundtoparent) {
                        containerBottom = documentHeight - (bTop + sticky.boundary.outerHeight()) + parseInt(sticky.boundary.css('padding-bottom'));
                    } else {
                        containerBottom = documentHeight - bTop;
                    }

                    newTop = (scrollTop + stickyHeight) > (documentHeight - containerBottom - (sticky.top < 0 ? 0 : sticky.top)) ? (documentHeight - containerBottom) - (scrollTop + stickyHeight) : newTop;
                }

                if (sticky.currentTop != newTop) {

                    sticky.element.css({
                        position : "fixed",
                        top      : newTop,
                        width    : sticky.getWidthFrom.length ? sticky.getWidthFrom.width() : sticky.element.width()
                    });

                    if (!sticky.init) {

                        sticky.element.addClass(sticky.options.clsinit);

                        if (location.hash && scrollTop > 0 && sticky.options.target) {

                            var $target = UI.$(location.hash);

                            if ($target.length) {

                                setTimeout((function($target, sticky){

                                    return function() {

                                        sticky.element.width(); // force redraw

                                        var offset       = $target.offset(),
                                            maxoffset    = offset.top + $target.outerHeight(),
                                            stickyOffset = sticky.element.offset(),
                                            stickyHeight = sticky.element.outerHeight(),
                                            stickyMaxOffset = stickyOffset.top + stickyHeight;

                                        if (stickyOffset.top < maxoffset && offset.top < stickyMaxOffset) {
                                            scrollTop = offset.top - stickyHeight - sticky.options.target;
                                            window.scrollTo(0, scrollTop);
                                        }
                                    };

                                })($target, sticky), 0);
                            }
                        }
                    }

                    sticky.element.addClass(sticky.options.clsactive).removeClass(sticky.options.clsinactive);
                    sticky.element.trigger('active.uk.sticky');
                    sticky.element.css('margin', '');

                    if (sticky.options.animation && sticky.init && !UI.Utils.isInView(sticky.wrapper)) {
                        sticky.element.addClass(sticky.options.animation);
                    }

                    sticky.currentTop = newTop;
                }
            }

            sticky.init = true;
        }
    }

    return UI.sticky;
});
<commit_msg>sticky updated<commit_after>(function(addon) {

    var component;

    if (window.UIkit) {
        component = addon(UIkit);
    }

    if (typeof define == "function" && define.amd) {
        define("uikit-sticky", ["uikit"], function(){
            return component || addon(UIkit);
        });
    }

})(function(UI){

    "use strict";

    var $win         = UI.$win,
        $doc         = UI.$doc,
        sticked      = [],
        direction    = 1;

    UI.component('sticky', {

        defaults: {
            top          : 0,
            bottom       : 0,
            animation    : '',
            clsinit      : 'uk-sticky-init',
            clsactive    : 'uk-active',
            clsinactive  : '',
            getWidthFrom : '',
            showup      : false,
            boundary     : false,
            media        : false,
            target       : false,
            disabled     : false
        },

        boot: function() {

            // should be more efficient than using $win.scroll(checkscrollposition):
            UI.$doc.on('scrolling.uk.document', function(e, data) {
                if (!data || !data.dir) return;
                direction = data.dir.y;
                checkscrollposition();
            });

            UI.$win.on('resize orientationchange', UI.Utils.debounce(function() {

                if (!sticked.length) return;

                for (var i = 0; i < sticked.length; i++) {
                    sticked[i].reset(true);
                    sticked[i].self.computeWrapper();
                }

                checkscrollposition();
            }, 100));

            // init code
            UI.ready(function(context) {

                setTimeout(function(){

                    UI.$("[data-uk-sticky]", context).each(function(){

                        var $ele = UI.$(this);

                        if (!$ele.data("sticky")) {
                            UI.sticky($ele, UI.Utils.options($ele.attr('data-uk-sticky')));
                        }
                    });

                    checkscrollposition();
                }, 0);
            });
        },

        init: function() {

            var boundary = this.options.boundary, boundtoparent;

            this.wrapper = this.element.wrap('

').parent();
            this.computeWrapper();
            this.wrapper.css({
                'margin-top'    : this.element.css('margin-top'),
                'margin-bottom' : this.element.css('margin-bottom'),
                'margin-left'   : this.element.css('margin-left'),
                'margin-right'  : this.element.css('margin-right')
            })
            this.element.css('margin', 0);

            if (boundary) {

                if (boundary === true || boundary[0] === '!') {

                    boundary      = boundary === true ? this.wrapper.parent() : this.wrapper.closest(boundary.substr(1));
                    boundtoparent = true;

                } else if (typeof boundary === "string") {
                    boundary = UI.$(boundary);
                }
            }

            this.sticky = {
                self          : this,
                options       : this.options,
                element       : this.element,
                currentTop    : null,
                wrapper       : this.wrapper,
                init          : false,
                getWidthFrom  : UI.$(this.options.getWidthFrom || this.wrapper),
                boundary      : boundary,
                boundtoparent : boundtoparent,
                top           : 0,
                calcTop       : function() {

                    var top = this.options.top;

                    // dynamic top parameter
                    if (this.options.top && typeof(this.options.top) == 'string') {

                        // e.g. 50vh
                        if (this.options.top.match(/^(-|)(\d+)vh$/)) {
                            top = window.innerHeight * parseInt(this.options.top, 10)/100;
                        // e.g. #elementId, or .class-1,class-2,.class-3 (first found is used)
                        } else {

                            var topElement = UI.$(this.options.top).first();

                            if (topElement.length && topElement.is(':visible')) {
                                top = -1 * ((topElement.offset().top + topElement.outerHeight()) - this.wrapper.offset().top);
                            }
                        }

                    }

                    this.top = top;
                },

                reset: function(force) {

                    this.calcTop();

                    var finalize = function() {
                        this.element.css({"position":"", "top":"", "width":"", "left":"", "margin":"0"});
                        this.element.removeClass([this.options.animation, 'uk-animation-reverse', this.options.clsactive].join(' '));
                        this.element.addClass(this.options.clsinactive);
                        this.element.trigger('inactive.uk.sticky');

                        this.currentTop = null;
                        this.animate    = false;

                    }.bind(this);

                    if (!force && this.options.animation && UI.support.animation && !UI.Utils.isInView(this.wrapper)) {

                        this.animate = true;

                        this.element.removeClass(this.options.animation).one(UI.support.animation.end, function(){
                            finalize();
                        }).width(); // force redraw

                        this.element.addClass(this.options.animation+' '+'uk-animation-reverse');
                    } else {
                        finalize();
                    }
                },

                check: function() {

                    if (this.options.disabled) {
                        return false;
                    }

                    if (this.options.media) {

                        switch(typeof(this.options.media)) {
                            case 'number':
                                if (window.innerWidth < this.options.media) {
                                    return false;
                                }
                                break;
                            case 'string':
                                if (window.matchMedia && !window.matchMedia(this.options.media).matches) {
                                    return false;
                                }
                                break;
                        }
                    }

                    var scrollTop      = $win.scrollTop(),
                        documentHeight = $doc.height(),
                        dwh            = documentHeight - window.innerHeight,
                        extra          = (scrollTop > dwh) ? dwh - scrollTop : 0,
                        elementTop     = this.wrapper.offset().top,
                        etse           = elementTop - this.top - extra,
                        active         = (scrollTop  >= etse);

                    if (active && this.options.showup) {

                        // set inactiv if scrolling down
                        if (direction == 1) {
                            active = false;
                        }

                        // set inactive when wrapper is still in view
                        if (direction == -1 && !this.element.hasClass(this.options.clsactive) && UI.Utils.isInView(this.wrapper)) {
                            active = false;
                        }
                    }

                    return active;
                }
            };

            this.sticky.calcTop();

            sticked.push(this.sticky);
        },

        update: function() {
            checkscrollposition(this.sticky);
        },

        enable: function() {
            this.options.disabled = false;
            this.update();
        },

        disable: function(force) {
            this.options.disabled = true;
            this.sticky.reset(force);
        },

        computeWrapper: function() {

            this.wrapper.css({
                'height'        : ['absolute','fixed'].indexOf(this.element.css('position')) == -1 ? this.element.outerHeight() : '',
                'float'         : this.element.css('float') != 'none' ? this.element.css('float') : ''
            });

            if (this.element.css('position') == 'fixed') {
                this.element.css({
                    width: this.sticky.getWidthFrom.length ? this.sticky.getWidthFrom.width() : this.element.width()
                });
            }
        }
    });

    function checkscrollposition(direction) {

        var stickies = arguments.length ? arguments : sticked;

        if (!stickies.length || $win.scrollTop() < 0) return;

        var scrollTop       = $win.scrollTop(),
            documentHeight  = $doc.height(),
            windowHeight    = $win.height(),
            dwh             = documentHeight - windowHeight,
            extra           = (scrollTop > dwh) ? dwh - scrollTop : 0,
            newTop, containerBottom, stickyHeight, sticky;

        for (var i = 0; i < stickies.length; i++) {

            sticky = stickies[i];

            if (!sticky.element.is(":visible") || sticky.animate) {
                continue;
            }

            if (!sticky.check()) {

                if (sticky.currentTop !== null) {
                    sticky.reset();
                }

            } else {

                if (sticky.top < 0) {
                    newTop = 0;
                } else {
                    stickyHeight = sticky.element.outerHeight();
                    newTop = documentHeight - stickyHeight - sticky.top - sticky.options.bottom - scrollTop - extra;
                    newTop = newTop < 0 ? newTop + sticky.top : sticky.top;
                }

                if (sticky.boundary && sticky.boundary.length) {

                    var bTop = sticky.boundary.offset().top;

                    if (sticky.boundtoparent) {
                        containerBottom = documentHeight - (bTop + sticky.boundary.outerHeight()) + parseInt(sticky.boundary.css('padding-bottom'));
                    } else {
                        containerBottom = documentHeight - bTop;
                    }

                    newTop = (scrollTop + stickyHeight) > (documentHeight - containerBottom - (sticky.top < 0 ? 0 : sticky.top)) ? (documentHeight - containerBottom) - (scrollTop + stickyHeight) : newTop;
                }

                if (sticky.currentTop != newTop) {

                    sticky.element.css({
                        position : "fixed",
                        top      : newTop,
                        width    : sticky.getWidthFrom.length ? sticky.getWidthFrom.width() : sticky.element.width()
                    });

                    if (!sticky.init) {

                        sticky.element.addClass(sticky.options.clsinit);

                        if (location.hash && scrollTop > 0 && sticky.options.target) {

                            var $target = UI.$(location.hash);

                            if ($target.length) {

                                setTimeout((function($target, sticky){

                                    return function() {

                                        sticky.element.width(); // force redraw

                                        var offset       = $target.offset(),
                                            maxoffset    = offset.top + $target.outerHeight(),
                                            stickyOffset = sticky.element.offset(),
                                            stickyHeight = sticky.element.outerHeight(),
                                            stickyMaxOffset = stickyOffset.top + stickyHeight;

                                        if (stickyOffset.top < maxoffset && offset.top < stickyMaxOffset) {
                                            scrollTop = offset.top - stickyHeight - sticky.options.target;
                                            window.scrollTo(0, scrollTop);
                                        }
                                    };

                                })($target, sticky), 0);
                            }
                        }
                    }

                    sticky.element.addClass(sticky.options.clsactive).removeClass(sticky.options.clsinactive);
                    sticky.element.trigger('active.uk.sticky');
                    sticky.element.css('margin', '');

                    if (sticky.options.animation && sticky.init && !UI.Utils.isInView(sticky.wrapper)) {
                        sticky.element.addClass(sticky.options.animation);
                    }

                    sticky.currentTop = newTop;
                }
            }

            sticky.init = true;
        }
    }

    return UI.sticky;
});
<|endoftext|>


<commit_before>// Karma configuration
// Generated on Wed Nov 09 2016 00:04:58 GMT+0900 (JST)

module.exports = function(config) {
  config.set({

    basePath: '',

    frameworks: ['mocha', 'browserify'],

    files: [
      'test/index.html',
      'test/**/*.js'
    ],

    exclude: [
    ],

    preprocessors: {
      'index.js': ['coverage'],
      'test/**/*.js': ['browserify'],
      'test/**/*.html': 'html2js'
    },

    browserify: {
      debug: true,
      transform: [
        ['babelify', {plugins: ['babel-plugin-espower']}]
      ]
    },

    reporters: ['spec', 'coverage'],

    coverageReporter: {
      reporters: [{type: 'lcov'}]
    },

    port: 9876,

    colors: true,

    logLevel: config.LOG_INFO,

    autoWatch: true,

    browsers: ['PhantomJS'],

    singleRun: false,

    concurrency: Infinity
  })
}
<commit_msg>Fix coverage target js<commit_after>// Karma configuration
// Generated on Wed Nov 09 2016 00:04:58 GMT+0900 (JST)

module.exports = function(config) {
  config.set({

    basePath: '',

    frameworks: ['mocha', 'browserify'],

    files: [
      'index.js',
      'test/index.html',
      'test/**/*.js'
    ],

    exclude: [
    ],

    preprocessors: {
      './**/*.js': ['coverage'],
      'test/**/*.js': ['browserify'],
      'test/**/*.html': 'html2js'
    },

    browserify: {
      debug: true,
      transform: [
        ['babelify', {plugins: ['babel-plugin-espower']}]
      ]
    },

    reporters: ['spec', 'coverage'],

    coverageReporter: {
      reporters: [{type: 'lcov'}]
    },

    port: 9876,

    colors: true,

    logLevel: config.LOG_INFO,

    autoWatch: true,

    browsers: ['PhantomJS'],

    singleRun: false,

    concurrency: Infinity
  })
}
<|endoftext|>


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

Input.propTypes = {
  onChange: PropTypes.func
}
export default function Input({onChange, ...props}) {
  return (
    <input
      {...props}
      onChange={(event) => onChange(renderText(event.target.value))}
    />
  )

  function renderText(text) {
    while (text.indexOf(' ') === 0) {
      text = text.substring(1)
    }
    return text
  }
}
<commit_msg>All Input box trim implemented by default<commit_after>import React from 'react'
import PropTypes from 'prop-types'

Input.propTypes = {
  onChange: PropTypes.func
}
export default function Input({onChange, ...props}) {
  return (
    <input
      {...props}
      onChange={(event) => onChange(renderText(event.target.value))}
    />
  )

  function renderText(text) {
    return text.trim()
  }
}
<|endoftext|>