<commit_before>// @flow

import React from "react";
import injectSheet from "react-jss";
import ReactTooltip from "react-tooltip";
import { createClassFromLiteSpec } from "react-vega-lite";

import entries from "lodash/entries";
import filter from "lodash/filter";
import find from "lodash/find";
import flatMap from "lodash/flatMap";
import groupBy from "lodash/groupBy";
import includes from "lodash/includes";
import map from "lodash/map";
import mean from "lodash/mean";
import reduce from "lodash/reduce";
import some from "lodash/some";

import type { Node, Edge } from "../../graph/dagger/dataAPI/genericGraph";
import type { Workflow, Tag, Job, Dependency } from "../../datamodel";

import Select from "react-select";
import { navigate } from "redux-url";
import { connect } from "react-redux";
import { markdown } from "markdown";

import TagIcon from "react-icons/lib/md/label";
import Dagger from "../../graph/Dagger";
import SlidePanel from "../components/SlidePanel";
import FancyTable from "../components/FancyTable";

import moment from "moment";

type Props = {
  classes: any,
  workflow: Workflow,
  selectedJobs: string[],
  job: string,
  navTo: () => void
};

type State = {
  data: any[]
};

const AverageRunWaitChart = createClassFromLiteSpec("AverageRunWaitChart", {
  width: "550",
  title: "Runtime of jobs across time",
  mark: "area",
  transform: [
    {
      calculate: "datum.kind == 'run' ? 'Running' : 'Waiting'",
      as: "runningSeconds"
    }
  ],
  encoding: {
    x: {
      field: "startTime",
      type: "temporal",
      timeUnit: "utcyearmonthday",
      axis: {
        title: null,
        format: "%d/%m",
        labelAngle: -45
      }
    },
    y: {
      field: "seconds",
      type: "quantitative",
      aggregate: "sum",
      axis: {
        title: "Duration (s)"
      }
    },
    color: {
      type: "nominal",
      field: "runningSeconds",
      scale: {
        range: ["#00BCD4", "#ff9800"]
      },
      legend: { title: "Status" }
    }
  }
});

const MaxRuntimeChart = createClassFromLiteSpec("MaxRuntimeChart", {
  width: "550",
  mark: "line",
  transform: [
    {
      calculate: "datum.durationSeconds - datum.waitingSeconds",
      as: "runningSeconds"
    }
  ],
  encoding: {
    x: {
      field: "startTime",
      timeUnit: "utcyearmonthday",
      type: "temporal",
      axis: {
        title: null,
        format: "%d/%m",
        labelAngle: -45
      }
    },
    y: {
      aggregate: "max",
      type: "quantitative",
      field: "runningSeconds",
      axis: {
        title: "Max running time (s)"
      }
    }
  },
  config : {
    mark : {
      color : "#00BCD4"
    }
  }
});

const SumFailuresChart = createClassFromLiteSpec("SumFailuresChart", {
  width: "550",
  title: "Failures across time.",
  mark: "bar",
  transform: [
    { calculate: "datum.status === 'failed' ? 1 : 0", as: "failures" }
  ],
  encoding: {
    x: {
      field: "startTime",
      timeUnit: "utcyearmonthday",
      type: "temporal",
      axis: {
        format: "%d/%m",
        title: null,
        labelAngle: -45
      }
    },
    y: {
      type: "quantitative",
      aggregate: "sum",
      field: "failures",
      axis: {
        title: "Number of failures"
      }
    }
  },
  config : {
    mark : {
      color : "#e91e63"
    }
  }
});

type ExecutionStat = {
  startTime: string,
  durationSeconds: number,
  waitingSeconds: number,
  status: "successful" | "failure"
};

/**
 * Unpivots execution stats to create
 * separate "run" & "wait" events aggregated
 * by date.
 */
const aggregateDataSet = (data: ExecutionStat[]) =>
  flatMap(
    entries(
      groupBy(data, d =>
        moment(d.startTime)
          .set({
            hour: 0,
            minute: 0,
            second: 0,
            millisecond: 0
          })
          .format()
      )
    ),
    ([k, v]) => [
      {
        startTime: k,
        kind: "run",
        seconds: mean(v.map(x => x.durationSeconds - x.waitingSeconds))
      },
      {
        startTime: k,
        kind: "wait",
        seconds: mean(v.map(x => x.waitingSeconds))
      }
    ]
  );

class WorkflowComponent extends React.Component {
  props: Props;
  state: State;

  constructor(props) {
    super(props);

    this.updateCharts(props);

    this.state = {
      data: []
    };
  }

  componentWillReceiveProps(nextProps: Props) {
    this.updateCharts(nextProps);
  }

  updateCharts(nextProps: Props) {
    const chartJob = nextProps.job || nextProps.workflow.jobs[0].id;
    fetch(`/api/statistics/${chartJob}`).then(data => data.json()).then(json => {
      this.setState({
        data: json
      });
    });
  }

  render() {
    const {
      classes,
      workflow = {},
      job,
      selectedJobs = [],
      navTo
    } = this.props;

    const filteredJobs = filter(workflow.jobs, j =>
      includes(selectedJobs, j.id)
    );
    const jobs = filteredJobs.length > 0 ? filteredJobs : workflow.jobs;
    const nodes: Node[] = map(jobs, (j: Job, i) => ({
      ...j,
      order: i,
      yPosition: i
    }));

    const filteredEdges = filter(
      workflow.dependencies,
      e => some(jobs, { id: e.from }) && some(jobs, { id: e.to })
    );
    const edges: Edge[] = map(filteredEdges, (d: Dependency) => ({
      id: d.from + d.to,
      source: d.from,
      target: d.to,
      value: 1
    }));

    const tagsDictionnary = reduce(
      workflow.tags,
      (acc, current) => ({
        ...acc,
        [current.name]: current
      }),
      {}
    );

    const startNode = find(jobs, { id: job }) || jobs[0];

    ReactTooltip.rebuild();

    const renderTimeSeriesSechduling = () => [
      startNode.scheduling.grid && [

Period:
,

{startNode.scheduling.grid.period}

      ],
      startNode.scheduling.start && [

Start Date:
,

{startNode.scheduling.start}

      ],
      startNode.scheduling.maxPeriods != 1 && [

Max Periods:
,

{startNode.scheduling.maxPeriods}

      ]
    ];

    return (

        <Dagger
          nodes={nodes}
          edges={edges}
          tags={workflow.tags}
          startNodeId={startNode.id}
          onClickNode={id => navTo("/workflow/" + id)}
        />
        <Select
          className={classes.jobSelector}
          name="jobSelector"
          options={map(nodes, n => ({ value: n.id, label: n.name }))}
          onChange={o => navTo("/workflow/" + o.value)}
        />
        <SlidePanel>

            <FancyTable>

Id:

                {startNode.id}

Name:

                {startNode.name}

              {renderTimeSeriesSechduling()}
              {startNode.tags.length > 0 && [

Tags:
,

                  {map(startNode.tags, t => [

                      <TagIcon className="tagIcon" />
                      {tagsDictionnary[t].name}
                    ,
                    <ReactTooltip
                      id={"tag" + tagsDictionnary[t].name}
                      effect="float"
                    />
                  ])}

              ]}
              {startNode.description && [

Description:
,

              ]}
            </FancyTable>

Average run/wait times over last 30 days

            <AverageRunWaitChart
              className="chart"
              data={{ values: aggregateDataSet(this.state.data) }}
            />

Max runtime over last 30 days

            <MaxRuntimeChart
              className="chart"
              data={{ values: this.state.data }}
            />

Number of failures over last 30 days

            <SumFailuresChart
              className="chart"
              data={{ values: this.state.data }}
            />

        </SlidePanel>

    );
  }
}

const styles = {
  main: {
    backgroundColor: "#ECF1F5",
    flex: 1,
    width: "100%",
    height: "calc(100vh - 4em)",
    position: "relative"
  },
  chartSection: {
    "& > .chart": {
      marginLeft: "50px",
      marginBottom: "50px"
    },
    "& h3": {
      color: "#3B4254",
      textAlign: "center",
      fontSize: "1em"
    }
  },
  tags: {
    display: "table-cell"
  },
  tag: {
    cursor: "help",
    verticalAlign: "middle",
    border: "1px solid #999",
    margin: "0 0.2em",
    padding: "0.2em 0.4em",
    borderRadius: "0.2em",
    "& .tagIcon": {
      marginRight: "0.4em",
      fontSize: "1.2em"
    }
  },
  description: {
    lineHeight: "1.25em !important",
    fontSize: "0.95em",
    textAlign: "justify !important",
    overflowY: "scroll"
  },
  jobSelector: {
    position: "absolute",
    top: "2em",
    left: "50%",
    marginLeft: "-300px",
    width: "600px",
    "& .Select-control": {
      height: "1em",
      backgroundColor: "#F5F8FA",
      "& .Select-value": {
        color: "#A9B8C3",
        fontSize: "0.9em"
      },
      "& .Select-menu ! important": {
        margin: "0 1em",
        width: "calc(600px - 2em)"
      },
      "& .Select-menu-outer !important": {
        margin: "0 1em",
        width: "calc(600px - 2em)"
      },
      "& .Select-option !important": {
        fontSize: "0.9em"
      },
      "& .Select-arrow-zone": {
        display: "none"
      }
    }
  },
  jobCard: {
    color: "#3B4254"
  }
};

export default connect(
  () => ({}),
  dispatch => ({
    navTo: link => dispatch(navigate(link))
  })
)(injectSheet(styles)(WorkflowComponent));
<commit_msg>Adds a throbber for chart loading (#151)<commit_after>// @flow

import React from "react";
import injectSheet from "react-jss";
import ReactTooltip from "react-tooltip";
import { createClassFromLiteSpec } from "react-vega-lite";

import entries from "lodash/entries";
import filter from "lodash/filter";
import find from "lodash/find";
import flatMap from "lodash/flatMap";
import groupBy from "lodash/groupBy";
import includes from "lodash/includes";
import map from "lodash/map";
import mean from "lodash/mean";
import reduce from "lodash/reduce";
import some from "lodash/some";

import type { Node, Edge } from "../../graph/dagger/dataAPI/genericGraph";
import type { Workflow, Tag, Job, Dependency } from "../../datamodel";

import Select from "react-select";
import { navigate } from "redux-url";
import { connect } from "react-redux";
import { markdown } from "markdown";

import TagIcon from "react-icons/lib/md/label";
import Dagger from "../../graph/Dagger";
import SlidePanel from "../components/SlidePanel";
import FancyTable from "../components/FancyTable";
import Spinner from "../components/Spinner";

import moment from "moment";

type Props = {
  classes: any,
  workflow: Workflow,
  selectedJobs: string[],
  job: string,
  navTo: () => void
};

type State = {
  data: ?any[]
};

const AverageRunWaitChart = createClassFromLiteSpec("AverageRunWaitChart", {
  width: "550",
  title: "Runtime of jobs across time",
  mark: "area",
  transform: [
    {
      calculate: "datum.kind == 'run' ? 'Running' : 'Waiting'",
      as: "runningSeconds"
    }
  ],
  encoding: {
    x: {
      field: "startTime",
      type: "temporal",
      timeUnit: "utcyearmonthday",
      axis: {
        title: null,
        format: "%d/%m",
        labelAngle: -45
      }
    },
    y: {
      field: "seconds",
      type: "quantitative",
      aggregate: "sum",
      axis: {
        title: "Duration (s)"
      }
    },
    color: {
      type: "nominal",
      field: "runningSeconds",
      scale: {
        range: ["#00BCD4", "#ff9800"]
      },
      legend: { title: "Status" }
    }
  }
});

const MaxRuntimeChart = createClassFromLiteSpec("MaxRuntimeChart", {
  width: "550",
  mark: "line",
  transform: [
    {
      calculate: "datum.durationSeconds - datum.waitingSeconds",
      as: "runningSeconds"
    }
  ],
  encoding: {
    x: {
      field: "startTime",
      timeUnit: "utcyearmonthday",
      type: "temporal",
      axis: {
        title: null,
        format: "%d/%m",
        labelAngle: -45
      }
    },
    y: {
      aggregate: "max",
      type: "quantitative",
      field: "runningSeconds",
      axis: {
        title: "Max running time (s)"
      }
    }
  },
  config : {
    mark : {
      color : "#00BCD4"
    }
  }
});

const SumFailuresChart = createClassFromLiteSpec("SumFailuresChart", {
  width: "550",
  title: "Failures across time.",
  mark: "bar",
  transform: [
    { calculate: "datum.status === 'failed' ? 1 : 0", as: "failures" }
  ],
  encoding: {
    x: {
      field: "startTime",
      timeUnit: "utcyearmonthday",
      type: "temporal",
      axis: {
        format: "%d/%m",
        title: null,
        labelAngle: -45
      }
    },
    y: {
      type: "quantitative",
      aggregate: "sum",
      field: "failures",
      axis: {
        title: "Number of failures"
      }
    }
  },
  config : {
    mark : {
      color : "#e91e63"
    }
  }
});

type ExecutionStat = {
  startTime: string,
  durationSeconds: number,
  waitingSeconds: number,
  status: "successful" | "failure"
};

/**
 * Unpivots execution stats to create
 * separate "run" & "wait" events aggregated
 * by date.
 */
const aggregateDataSet = (data: ExecutionStat[]) =>
  flatMap(
    entries(
      groupBy(data, d =>
        moment(d.startTime)
          .set({
            hour: 0,
            minute: 0,
            second: 0,
            millisecond: 0
          })
          .format()
      )
    ),
    ([k, v]) => [
      {
        startTime: k,
        kind: "run",
        seconds: mean(v.map(x => x.durationSeconds - x.waitingSeconds))
      },
      {
        startTime: k,
        kind: "wait",
        seconds: mean(v.map(x => x.waitingSeconds))
      }
    ]
  );

class WorkflowComponent extends React.Component {
  props: Props;
  state: State;

  constructor(props) {
    super(props);

    this.updateCharts(props);

    this.state = {
      data: undefined
    };
  }

  componentWillReceiveProps(nextProps: Props) {

    this.updateCharts(nextProps);
  }

  updateCharts(nextProps: Props) {
    const chartJob = nextProps.job || nextProps.workflow.jobs[0].id;
    if (nextProps && nextProps.job && nextProps.job !== this.props.job) {
      this.setState({
        data : undefined
      });
    }
    fetch(`/api/statistics/${chartJob}`).then(data => data.json()).then(json => {
      this.setState({
        data: json
      });
    });
  }

  render() {
    const {
      classes,
      workflow = {},
      job,
      selectedJobs = [],
      navTo
    } = this.props;

    const filteredJobs = filter(workflow.jobs, j =>
      includes(selectedJobs, j.id)
    );
    const jobs = filteredJobs.length > 0 ? filteredJobs : workflow.jobs;
    const nodes: Node[] = map(jobs, (j: Job, i) => ({
      ...j,
      order: i,
      yPosition: i
    }));

    const filteredEdges = filter(
      workflow.dependencies,
      e => some(jobs, { id: e.from }) && some(jobs, { id: e.to })
    );
    const edges: Edge[] = map(filteredEdges, (d: Dependency) => ({
      id: d.from + d.to,
      source: d.from,
      target: d.to,
      value: 1
    }));

    const tagsDictionnary = reduce(
      workflow.tags,
      (acc, current) => ({
        ...acc,
        [current.name]: current
      }),
      {}
    );

    const startNode = find(jobs, { id: job }) || jobs[0];

    ReactTooltip.rebuild();

    const renderTimeSeriesSechduling = () => [
      startNode.scheduling.grid && [

Period:
,

{startNode.scheduling.grid.period}

      ],
      startNode.scheduling.start && [

Start Date:
,

{startNode.scheduling.start}

      ],
      startNode.scheduling.maxPeriods != 1 && [

Max Periods:
,

{startNode.scheduling.maxPeriods}

      ]
    ];

    const charts = (data : any) => {
      if (data) {
        return

Average run/wait times over last 30 days

            <AverageRunWaitChart
              className="chart"
              data={{ values: aggregateDataSet(data) }}
            />

Max runtime over last 30 days

            <MaxRuntimeChart
              className="chart"
              data={{ values: data }}
            />

Number of failures over last 30 days

            <SumFailuresChart
              className="chart"
              data={{ values: data }}
            />

;
      }
      return

          <Spinner />

;
    };

    return (

        <Dagger
          nodes={nodes}
          edges={edges}
          tags={workflow.tags}
          startNodeId={startNode.id}
          onClickNode={id => navTo("/workflow/" + id)}
        />
        <Select
          className={classes.jobSelector}
          name="jobSelector"
          options={map(nodes, n => ({ value: n.id, label: n.name }))}
          onChange={o => navTo("/workflow/" + o.value)}
        />
        <SlidePanel>

            <FancyTable>

Id:

                {startNode.id}

Name:

                {startNode.name}

              {renderTimeSeriesSechduling()}
              {startNode.tags.length > 0 && [

Tags:
,

                  {map(startNode.tags, t => [

                      <TagIcon className="tagIcon" />
                      {tagsDictionnary[t].name}
                    ,
                    <ReactTooltip
                      id={"tag" + tagsDictionnary[t].name}
                      effect="float"
                    />
                  ])}

              ]}
              {startNode.description && [

Description:
,

              ]}
            </FancyTable>

          {charts(this.state.data)}
        </SlidePanel>

    );
  }
}

const styles = {
  main: {
    backgroundColor: "#ECF1F5",
    flex: 1,
    width: "100%",
    height: "calc(100vh - 4em)",
    position: "relative"
  },
  chartSection: {
    "& > .chart": {
      marginLeft: "50px",
      marginBottom: "50px"
    },
    "& h3": {
      color: "#3B4254",
      textAlign: "center",
      fontSize: "1em"
    }
  },
  tags: {
    display: "table-cell"
  },
  tag: {
    cursor: "help",
    verticalAlign: "middle",
    border: "1px solid #999",
    margin: "0 0.2em",
    padding: "0.2em 0.4em",
    borderRadius: "0.2em",
    "& .tagIcon": {
      marginRight: "0.4em",
      fontSize: "1.2em"
    }
  },
  description: {
    lineHeight: "1.25em !important",
    fontSize: "0.95em",
    textAlign: "justify !important",
    overflowY: "scroll"
  },
  jobSelector: {
    position: "absolute",
    top: "2em",
    left: "50%",
    marginLeft: "-300px",
    width: "600px",
    "& .Select-control": {
      height: "1em",
      backgroundColor: "#F5F8FA",
      "& .Select-value": {
        color: "#A9B8C3",
        fontSize: "0.9em"
      },
      "& .Select-menu ! important": {
        margin: "0 1em",
        width: "calc(600px - 2em)"
      },
      "& .Select-menu-outer !important": {
        margin: "0 1em",
        width: "calc(600px - 2em)"
      },
      "& .Select-option !important": {
        fontSize: "0.9em"
      },
      "& .Select-arrow-zone": {
        display: "none"
      }
    }
  },
  jobCard: {
    color: "#3B4254"
  }
};

export default connect(
  () => ({}),
  dispatch => ({
    navTo: link => dispatch(navigate(link))
  })
)(injectSheet(styles)(WorkflowComponent));
<|endoftext|>


<commit_before>docLayer.modules.define("spellcheck", {
	html: "",
	autocorrectWords: [],
	customDictionary: [],
	shouldCheckSpelling: true,
	replace: function (from, to) { //http://stackoverflow.com/questions/29067342/inline-autocorrect-with-contenteditable/29067657#29067657
		var sel = document.getSelection(),
			nd = sel.anchorNode,
			text = nd.textContent.slice(0, sel.focusOffset),
			newText = text.replace(from, to),
			wholeNew = nd.textContent.replace(text, newText),
			range = document.createRange();

		nd.textContent = wholeNew;
		range.setStart(nd, newText.length);
		range.collapse(true);
		sel.removeAllRanges();
		sel.addRange(range);
		nd.parentNode.focus();

	},
	focusElement: function (p) { //http://stackoverflow.com/a/16863913/4603285
		var s = window.getSelection(),
			r = document.createRange();
		p.innerHTML = '\u00a0';
		r.selectNodeContents(p);
		s.removeAllRanges();
		s.addRange(r);
		document.execCommand('delete', false, null);
	},
	correct: function () {
		var _ = this;
		var words = document.getSelection().anchorNode.textContent.split(" "); //only check the currently selected node for performance
		var spellingIsFixed;

		for (var i = 0; i < words.length; i++) {
			//split() works differently in webkit, so there might be an extra space after the word that needs to be removed.
			var value = words[i].replace(/\s/g, "");
			if (!spellcheck.checkSpelling(value) && _.customDictionary.indexOf(value) < 0) { //the word is misspelled, and it isn't overriden by the custom dictionary
				for (var z = 0; z < _.autocorrectWords.length; z++) { //check if we should autocorrect, or show a misspelling
					var autocorrect = _.autocorrectWords[z];
					if (value == autocorrect.from) {
						_.replace(value, autocorrect.to);
						spellingIsFixed = true;
					}
				}

				if (!spellingIsFixed) {
					document.execCommand("insertHTML", false, "a");

					var oldHTML = docLayer.editregion.html();
					var regex = new RegExp(value, "g");
					var newHTML = oldHTML.replace(regex, "" + value + "");
					docLayer.editregion.html(newHTML);
					_.focusElement(document.getElementById("cursor-target"));
					$("#cursor-target").contents().unwrap();
					$(".misspelling > .misspelling").contents().unwrap();
				}
			}
		}
	},
	showMenu: function (el) { //creates a context menu for a misspelling element

		//we don't need performance to be very good, so we can adjust the skip_interval downwards from the default
		spellcheck.skip_interval = 3;

		var _ = this;
		$(".spellcheck-menu").remove(); //remove any previous menus
		var _ = this;
		var offset = el.offset();
		var word = el.text();
		var suggestion = spellcheck.getBestReplacement(word);
		var menu = $('

');
		menu.css({
			position: "absolute",
			top: (offset.top + el.height() + 5) + "px",
			left: offset.left + "px",
		});

		var suggestionItem = $('
' + suggestion + '
');
		suggestionItem.children().on("click", function () {
			_.replaceSpelling(el, suggestion);
			$(".spellcheck-menu").hide();
		});
		suggestionItem.appendTo(menu);

		menu.append("

");

		var alwaysCorrectItem = $('
' + 'Always correct to "' + suggestion + '"
');
		alwaysCorrectItem.children().on("click", function () {
			_.replaceSpelling(el, suggestion);
			_.autocorrectWords.push({
				from: word,
				to: suggestion
			});
			setPref("spellcheck.autocorrect", _.autocorrectWords);
			$(".spellcheck-menu").hide();
		});
		alwaysCorrectItem.appendTo(menu);

		var addToDictionaryItem = $('
Add to dictionary
');
		addToDictionaryItem.children().on("click", function () {
			_.customDictionary.push(word);
			el.contents().unwrap(); //remove the misspelling underline
			setPref("spellcheck.dictionary", _.customDictionary);
			$(".spellcheck-menu").hide();
		});
		addToDictionaryItem.appendTo(menu);

		menu.appendTo(document.body);
	},
	replaceSpelling: function (el, replacement) { //replace the misspelling with the correct form
		$(el).html(replacement).contents().unwrap();
	},
	regrade: function (el) { //recheck all the misspellings to see if they have been changed and are now spelled correctly
		var _ = this;
		var span = $(el);
		var content = span.text();
		if (spellcheck.checkSpelling(content) || content.indexOf(" ") > -1 || _.customDictionary.indexOf(content) > -1) { //the word is now spelled correctly, or it now contains multiple words and will not be parsed correctly, or it has been added to the custom dictionary
			span.remove();
			document.execCommand("insertHTML", false, span.html());
		}
	},
	init: function () {
		var _ = this;

		if (window.definePref) {
			definePref({
				category: "Editing",
				description: "Check spelling as you type",
				pref: "editor.spellcheck.enabled",
				values: [true, false],
				defaultValue: true,
			});
			return; //we aren't in the editor, don't try to run editor setup
		}

		getPref("editor.spellcheck.enabled", function (value) {
			if (value == false) {
				_.shouldCheckSpelling = false;
			}
		});

		this.correct = this.correct.bind(this);
		this.showMenu = this.showMenu.bind(this);
		this.regrade = this.regrade.bind(this);

		docLayer.editregion.attr("spellcheck", "false");
		docLayer.editregion.on("keyup", function (e) {
			if (e.keyCode == 32 && _.shouldCheckSpelling) {
				_.correct();
			}
		});

		docLayer.editregion.on("keyup", function () {
			if ($(document.getSelection().anchorNode.parentNode).hasClass("misspelling")) {
				_.regrade(document.getSelection().anchorNode.parentNode);
			}
		});

		docLayer.editregion.on("contextmenu touchend", ".misspelling", function (e) { //show a misspelling menu
			e.preventDefault();
			_.showMenu($(e.target));
		});
		$(document.body).on("click", function () {
			$(".spellcheck-menu").remove();
		});

		getPref("spellcheck.autocorrect", function (value) {
			if (value) {
				_.autocorrectWords = value;
			}
		});
		getPref("spellcheck.dictionary", function (value) {
			if (value) {
				_.customDictionary = value;
			}
		});
	}

});
<commit_msg>better webkit compatibility<commit_after>docLayer.modules.define("spellcheck", {
	html: "",
	autocorrectWords: [],
	customDictionary: [],
	shouldCheckSpelling: true,
	replace: function (from, to) { //http://stackoverflow.com/questions/29067342/inline-autocorrect-with-contenteditable/29067657#29067657
		var sel = document.getSelection(),
			nd = sel.anchorNode,
			text = nd.textContent.slice(0, sel.focusOffset),
			newText = text.replace(from, to),
			wholeNew = nd.textContent.replace(text, newText),
			range = document.createRange();

		nd.textContent = wholeNew;
		range.setStart(nd, newText.length);
		range.collapse(true);
		sel.removeAllRanges();
		sel.addRange(range);
		nd.parentNode.focus();

	},
	correct: function () {
		var _ = this;
		var selNode = document.getSelection().anchorNode;
		var words = selNode.textContent.split(" "); //only check the currently selected node for performance
		var spellingIsFixed;

		for (var i = 0; i < words.length; i++) {
			//split() works differently in webkit, so there might be an extra space after the word that needs to be removed.
			var value = words[i].replace(/\s/g, "");
			if (!spellcheck.checkSpelling(value) && _.customDictionary.indexOf(value) < 0) { //the word is misspelled, and it isn't overriden by the custom dictionary
				for (var z = 0; z < _.autocorrectWords.length; z++) { //check if we should autocorrect, or show a misspelling
					var autocorrect = _.autocorrectWords[z];
					if (value == autocorrect.from) {
						_.replace(value, autocorrect.to);
						spellingIsFixed = true;
					}
				}

				if (!spellingIsFixed) {
					//based on http://stackoverflow.com/a/6328851/4603285
					var range = document.createRange();
					var index = selNode.textContent.indexOf(value)
					range.setStart(selNode, index);
					range.setEnd(selNode, index + value.length);
					var selectedText = range.extractContents();
					var span = document.createElement("span");
					span.classList.add("misspelling");
					span.appendChild(selectedText);
					range.insertNode(span);
				}
			}
		}
	},
	showMenu: function (el) { //creates a context menu for a misspelling element

		//we don't need performance to be very good, so we can adjust the skip_interval downwards from the default
		spellcheck.skip_interval = 3;

		var _ = this;
		$(".spellcheck-menu").remove(); //remove any previous menus
		var _ = this;
		var offset = el.offset();
		var word = el.text();
		var suggestion = spellcheck.getBestReplacement(word);
		var menu = $('

');
		menu.css({
			position: "absolute",
			top: (offset.top + el.height() + 5) + "px",
			left: offset.left + "px",
		});

		var suggestionItem = $('
' + suggestion + '
');
		suggestionItem.children().on("click", function () {
			_.replaceSpelling(el, suggestion);
			$(".spellcheck-menu").hide();
		});
		suggestionItem.appendTo(menu);

		menu.append("

");

		var alwaysCorrectItem = $('
' + 'Always correct to "' + suggestion + '"
');
		alwaysCorrectItem.children().on("click", function () {
			_.replaceSpelling(el, suggestion);
			_.autocorrectWords.push({
				from: word,
				to: suggestion
			});
			setPref("spellcheck.autocorrect", _.autocorrectWords);
			$(".spellcheck-menu").hide();
		});
		alwaysCorrectItem.appendTo(menu);

		var addToDictionaryItem = $('
Add to dictionary
');
		addToDictionaryItem.children().on("click", function () {
			_.customDictionary.push(word);
			el.contents().unwrap(); //remove the misspelling underline
			setPref("spellcheck.dictionary", _.customDictionary);
			$(".spellcheck-menu").hide();
		});
		addToDictionaryItem.appendTo(menu);

		menu.appendTo(document.body);
	},
	replaceSpelling: function (el, replacement) { //replace the misspelling with the correct form
		$(el).html(replacement).contents().unwrap();
	},
	regrade: function (el) { //recheck all the misspellings to see if they have been changed and are now spelled correctly
		var _ = this;
		var span = $(el);
		var content = span.text();
		if (spellcheck.checkSpelling(content) || content.indexOf(" ") > -1 || _.customDictionary.indexOf(content) > -1) { //the word is now spelled correctly, or it now contains multiple words and will not be parsed correctly, or it has been added to the custom dictionary
			span.remove();
			document.execCommand("insertHTML", false, span.html());
		}
	},
	init: function () {
		var _ = this;

		if (window.definePref) {
			definePref({
				category: "Editing",
				description: "Check spelling as you type",
				pref: "editor.spellcheck.enabled",
				values: [true, false],
				defaultValue: true,
			});
			return; //we aren't in the editor, don't try to run editor setup
		}

		getPref("editor.spellcheck.enabled", function (value) {
			if (value == false) {
				_.shouldCheckSpelling = false;
			}
		});

		this.correct = this.correct.bind(this);
		this.showMenu = this.showMenu.bind(this);
		this.regrade = this.regrade.bind(this);

		docLayer.editregion.attr("spellcheck", "false");
		docLayer.editregion.on("keyup", function (e) {
			if (e.keyCode == 32 && _.shouldCheckSpelling) {
				_.correct();
			}
		});

		docLayer.editregion.on("keyup", function () {
			if ($(document.getSelection().anchorNode.parentNode).hasClass("misspelling")) {
				_.regrade(document.getSelection().anchorNode.parentNode);
			}
		});

		docLayer.editregion.on("contextmenu touchend", ".misspelling", function (e) { //show a misspelling menu
			e.preventDefault();
			_.showMenu($(e.target));
		});
		$(document.body).on("click", function () {
			$(".spellcheck-menu").remove();
		});

		getPref("spellcheck.autocorrect", function (value) {
			if (value) {
				_.autocorrectWords = value;
			}
		});
		getPref("spellcheck.dictionary", function (value) {
			if (value) {
				_.customDictionary = value;
			}
		});
	}

});
<|endoftext|>


<commit_before>/**
 * Copyright JS Foundation and other contributors, http://js.foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 **/
RED.validators = {
    number: function(blankAllowed){return function(v) { return (blankAllowed&&v==='') || (v!=='' && !isNaN(v));}},
    regex: function(re){return function(v) { return re.test(v);}},
    typedInput: function(ptypeName,isConfig) { return function(v) {
        var ptype = $("#node-"+(isConfig?"config-":"")+"input-"+ptypeName).val() || this[ptypeName];
        if (ptype === 'json') {
            try {
                JSON.parse(v);
                return true;
            } catch(err) {
                return false;
            }
        } else if (ptype === 'msg' || ptype === 'flow' || ptype === 'global' ) {
            return RED.utils.validatePropertyExpression(v);
        } else if (ptype === 'num') {
            return /^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$/.test(v);
        }
        return true;
    }}
};
<commit_msg>Numeric validator that accepts blank should accept undefined<commit_after>/**
 * Copyright JS Foundation and other contributors, http://js.foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 **/
RED.validators = {
    number: function(blankAllowed){return function(v) { return (blankAllowed&&(v===''||v===undefined)) || (v!=='' && !isNaN(v));}},
    regex: function(re){return function(v) { return re.test(v);}},
    typedInput: function(ptypeName,isConfig) { return function(v) {
        var ptype = $("#node-"+(isConfig?"config-":"")+"input-"+ptypeName).val() || this[ptypeName];
        if (ptype === 'json') {
            try {
                JSON.parse(v);
                return true;
            } catch(err) {
                return false;
            }
        } else if (ptype === 'msg' || ptype === 'flow' || ptype === 'global' ) {
            return RED.utils.validatePropertyExpression(v);
        } else if (ptype === 'num') {
            return /^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$/.test(v);
        }
        return true;
    }}
};
<|endoftext|>


<commit_before>var put_locale = function(svgCanvas, given_param){
	var lang_param;
	if(given_param) {
		lang_param = given_param;
	} else {
		lang_param = $.pref('lang');
		if(!lang_param) {
			if (navigator.userLanguage) // Explorer
				lang_param = navigator.userLanguage;
			else if (navigator.language) // FF, Opera, ...
				lang_param = navigator.language;
			if (lang_param == "")
				return;
		}
		if(typeof(lang_param) != 'string') {
			alert(typeof(lang_param));
			alert(lang_param)
		}
		// don't bother on first run if language is English
		if(lang_param.indexOf("en") == 0) return;
	}

	var url = "locale/lang." + lang_param + ".js";
	$.get(url, function(data){
		var LangData = eval(data), js_strings;
		$.each(LangData, function(i, data) {
			if(data.id) {
				var elem = $('#'+data.id)[0];
				if(elem) {
					if(data.title)
						elem.title = data.title;
					if(data.textContent) {
						// Only replace text nodes, not elements
						$.each(elem.childNodes, function(j, node) {
							if(node.nodeType == 3) {
								node.textContent = data.textContent;
							}
						});
					}
				}
			} else if(data.js_strings) {
				js_strings = data.js_strings;
			}
		});
		svgCanvas.setLang(lang_param, js_strings);
	},"json");
};
<commit_msg>Possibly fixed lang detection script<commit_after>var put_locale = function(svgCanvas, given_param){
	var lang_param;
	if(given_param) {
		lang_param = given_param;
	} else {
		lang_param = $.pref('lang');
		if(!lang_param) {
			if (navigator.userLanguage) // Explorer
				lang_param = navigator.userLanguage;
			else if (navigator.language) // FF, Opera, ...
				lang_param = navigator.language;
			if (lang_param == "")
				return;
		}
		// don't bother on first run if language is English
		if(String(lang_param).indexOf("en") == 0) return;
	}

	var url = "locale/lang." + lang_param + ".js";
	$.get(url, function(data){
		var LangData = eval(data), js_strings;
		$.each(LangData, function(i, data) {
			if(data.id) {
				var elem = $('#'+data.id)[0];
				if(elem) {
					if(data.title)
						elem.title = data.title;
					if(data.textContent) {
						// Only replace text nodes, not elements
						$.each(elem.childNodes, function(j, node) {
							if(node.nodeType == 3) {
								node.textContent = data.textContent;
							}
						});
					}
				}
			} else if(data.js_strings) {
				js_strings = data.js_strings;
			}
		});
		svgCanvas.setLang(lang_param, js_strings);
	},"json");
};
<|endoftext|>


<commit_before>/**
 * @author Eberhard Graether / http://egraether.com/
 */

THREE.LandscapeControls = function ( cover, camera ) {

    THREE.EventTarget.call( this );

    var _this = this;
    STATE = { NONE : -1, ROTATE : 0, PAN : 1, ZOOM : 2 };
    var statefunc = {
        0: this.rotate.bind(this),
        1: this.pan.bind(this),
        2: this.zoom.bind(this),
    }

    this.keystate = null;
    this.camera = camera;
    this.domElement = cover;

    // API
    this.enabled = true;

    // Constants
    this.rotateSpeed = .4;
    this.zoomSpeed = .002;
    this.maxRadius = 400; // makes sure axes & flatmat don't merge in depth buffer
    this.minRadius = function(mix){return 101*mix}; // limits zoom for flatmap, which disappears at r=100
    this.panSpeed = 0.3;
    this.clickTimeout = 200; // milliseconds
    this.friction = 0.5;

    // internals
    this.target = new THREE.Vector3();
    this.azimuth = 45;
    this.altitude = 75;
    this.radius = 250;
    this._scheduled = false;

    var _state = STATE.NONE,
        _start = new THREE.Vector3(),
        _end = new THREE.Vector3();
        _touch = false;

    var _mousedowntime = 0;
    var _clicktime = 0; // Time of last click (mouseup event)
    var _indblpick = false; // In double-click and hold?
    var _picktimer = false; // timer that runs pick event

    // events

    var changeEvent = { type: 'change' };

    this.update = function (flatmix) {
        var mousechange;

        if (_touch) {
            _state = 0;
            mouseChange = _end;
        } else
            mouseChange = _end.clone().subSelf(_start);

        if (mouseChange.length() > 0 && statefunc[_state]) {
            if (statefunc[_state])
                statefunc[_state](mouseChange);
        }

        this.flatmix = flatmix;
        this.setCamera();

        _start = _end;
    };

    // listeners
    function keydown( event ) {
        if ( ! this.enabled ) return;

        if (event.keyCode == 17) {
            this.keystate = STATE.ZOOM;
        } else if (event.keyCode == 16) {
            this.keystate = STATE.PAN;
        } else {
            this.keystate = null;
        }

    };

    function keyup( event ) {
        if ( ! this.enabled ) return;
        this.keystate = null;
    };

    function mousedown( event ) {
        this.unschedule();

        event.preventDefault();
        event.stopPropagation();

        if ( _state === STATE.NONE) {
            _state = this.keystate ? this.keystate : event.button;
            _start = _end = this.getMouse(event);
            if (event.button == 0) {
                _mousedowntime = new Date().getTime();
            }
            // Run double-click event if time since last click is short enough
            if ( _mousedowntime - _clicktime < this.clickTimeout && event.button == 0 ) {
                if (_picktimer) clearTimeout(_picktimer);
                var mouse2D = this.getMouse(event).clone();
                this.dispatchEvent({ type:"dblpick", x:mouse2D.x, y:mouse2D.y, keep:this.keystate == STATE.ZOOM });
                _indblpick = true;
            } else {
                this.dispatchEvent({ type:"mousedown" });
            }
        }
    };

    function mouseup( event ) {
        if ( ! this.enabled ) return;

        this.schedule( true );

        event.preventDefault();
        event.stopPropagation();

        _state = STATE.NONE;
        if (event.button == 0) {
            _clicktime = new Date().getTime();
        }

        // Run picker if time since mousedown is short enough
        if ( _clicktime - _mousedowntime < this.clickTimeout && event.button == 0) {
            var mouse2D = this.getMouse(event).clone();
            this.dispatchEvent({ type: "mouseup" });
            this.dispatchEvent({ type:"pick", x:mouse2D.x, y:mouse2D.y, keep:this.keystate == STATE.ZOOM});
        } else if ( event.button == 0 && _indblpick == true ) {
            this.dispatchEvent({ type:"undblpick" });
            _indblpick = false;
        } else {
            this.dispatchEvent({ type: "mouseup" });
        }
    };

    function mousemove( event ) {
        if ( ! this.enabled ) return;

        if ( _state === STATE.NONE ) {
            return;
        } else {
            _end = this.getMouse(event);
        }
        this.dispatchEvent( changeEvent );
    };

    function touchmove( event ) {
        $(this.domElement).append("touch");
        _touch = true;
        _end = new THREE.Vector2(event.touches[0].clientX, event.touches[1].clientY);
        this.dispatchEvent( changeEvent );
    };

    function mousewheel( event ) {
        if ( ! this.enabled ) return;

        event.preventDefault();
        event.stopPropagation();

        this.wheelzoom( event );
        this.setCamera();
        this.dispatchEvent( changeEvent );

    };

    //code from http://vetruvet.blogspot.com/2010/12/converting-single-touch-events-to-mouse.html
    var touchToMouse=function(b){if(!(b.touches.length>1)){var a=b.changedTouches[0],c="";switch(b.type){case "touchstart":c="mousedown";break;case "touchmove":c="mousemove";break;case "touchend":c="mouseup";break;default:return}var d=document.createEvent("MouseEvent");d.initMouseEvent(c,true,true,window,1,a.screenX,a.screenY,a.clientX,a.clientY,false,false,false,false,0,null);a.target.dispatchEvent(d);b.preventDefault()}};
    this.domElement.addEventListener( 'touchstart', touchToMouse );
    this.domElement.addEventListener( 'touchmove', touchToMouse );
    this.domElement.addEventListener( 'touchend', touchToMouse );

    this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false );

    this.domElement.addEventListener( 'mousemove', mousemove.bind(this), false );
    this.domElement.addEventListener( 'mousedown', mousedown.bind(this), false );
    this.domElement.addEventListener( 'mouseup', mouseup.bind(this), false );
    this.domElement.addEventListener( 'mousewheel', mousewheel.bind(this), false);
    this.domElement.addEventListener( 'mouseout', mouseup.bind(this), false );

    window.addEventListener( 'keydown', keydown.bind(this), false );
    window.addEventListener( 'keyup', keyup.bind(this), false );

    this.resize($(cover).width(), $(cover).height() );
    this.flatmix = 0;
    this.setCamera();
};

THREE.LandscapeControls.prototype = {
    _limitview: function( ) {
        var flatmix = this.flatmix;
        var azlim = flatmix * 180;
        var altlim = flatmix * 90;

        this.altitude = this.altitude > 179.9999-altlim ? 179.9999-altlim : this.altitude;
        this.altitude = this.altitude < 0.0001+altlim ? 0.0001+altlim : this.altitude;

        if (azlim > this.azimuth || this.azimuth > (360 - azlim)) {
            var d1 = azlim - this.azimuth;
            var d2 = 360 - azlim - this.azimuth;
            this.azimuth = Math.abs(d1) > Math.abs(d2) ? 360-azlim : azlim;
        }

        var rad = this.radius, target = this.target.clone();
        var container = $(this.domElement.parentNode.parentNode)
        if (container.find("#zlockwhole").length > 0) {
            if (container.find("#zlockwhole")[0].checked) {
                rad  = this.flatsize / 2 / this.camera.cameraP.aspect;
                rad /= Math.tan(this.camera.fov / 2 * Math.PI / 180);
                rad -= this.flatoff;
                rad = flatmix * rad + (1 - flatmix) * this.radius;
            } else if (!container.find("#zlocknone")[0].checked) {
                rad  = this.flatsize / 4 / this.camera.cameraP.aspect;
                rad /= Math.tan(this.camera.fov / 2 * Math.PI / 180);
                rad -= this.flatoff;
                rad = flatmix * rad + (1 - flatmix) * this.radius;
                if (container.find("#zlockleft")[0].checked) {
                    target.x = flatmix * (-this.flatsize / 4) + (1 - flatmix) * target.x;
                } else if (container.find("#zlockright")[0].checked) {
                    target.x = flatmix * ( this.flatsize / 4) + (1 - flatmix) * target.x;
                }
            }
        }

        this._radius = rad;
        this._target = target;
    },

    resize: function(w, h) {
        this.domElement.style.width = w+"px";
        this.domElement.style.height = h+"px";
    },

    getMouse: function ( event ) {
        var off = $(event.target).offset();
        return new THREE.Vector2( event.clientX - off.left, event.clientY - off.top);
    },

    setCamera: function(az, alt, rad) {
        var changed = false;
        if (az !== undefined) {
            this.azimuth = ((az % 360)+360)%360;
            changed = true;
        }
        if (alt !== undefined) {
            this.altitude = alt;
            changed = true;
        }
        if (rad !== undefined) {
            this.radius = rad;
            changed = true;
        }

        this._limitview();

        var altrad = this.altitude*Math.PI / 180;
        var azirad = (this.azimuth+90)*Math.PI / 180;

        var eye = new THREE.Vector3(
            this._radius*Math.sin(altrad)*Math.cos(azirad),
            this._radius*Math.sin(altrad)*Math.sin(azirad),
            this._radius*Math.cos(altrad)
        );

        this.camera.position.add( this._target, eye );
        this.camera.lookAt( this._target );
        if (changed)
            this.dispatchEvent( {type:'change'} );
    },

    rotate: function ( mouseChange ) {
        this.azvel = -1 * this.rotateSpeed * mouseChange.x;
        var az = this.azimuth + this.azvel;
        if ( this.azlim > 0 ) {
            if ( this.azlim > az || az > (360-this.azlim)) {
                this.azimuth = azdiff < 0 ? this.azlim : 360-this.azlim;
            } else {
                this.azimuth = az - 360*Math.floor(az / 360);
            }
        } else {
            this.azimuth = az - 360*Math.floor(az / 360);
        }

        //this.altitude -= this.rotateSpeed*mouseChange.y;
        this.altvel = -1 * this.rotateSpeed * mouseChange.y;
        this.altitude += this.altvel;
        this.altitude = Math.max(Math.min(this.altitude, 180), 0.01);

        // Add panning depending on flatmix
        var panMouseChange = new Object;
        panMouseChange.x = mouseChange.x * Math.pow(this.flatmix, 2);
        panMouseChange.y = mouseChange.y * Math.pow(this.flatmix, 2);
        this.pan(panMouseChange);
    },

    pan: function( mouseChange ) {
        this.panxvel = this.panSpeed * mouseChange.x;
        this.panyvel = this.panSpeed * mouseChange.y;
        this.setpan( this.panSpeed * mouseChange.x, this.panSpeed * mouseChange.y );
    },

    setpan: function( x, y ) {
        var eye = this.camera.position.clone().subSelf(this.target);
        var right = eye.clone().crossSelf( this.camera.up );
        var up = right.clone().crossSelf(eye);
        var pan = (new THREE.Vector3()).add(
            right.setLength( x ),
            up.setLength( y ));
        this.camera.position.addSelf( pan );
        this.target.addSelf( pan );
    },

    zoom: function( mouseChange ) {
        var factor = 1.0 + mouseChange.y*this.zoomSpeed;
        this.radius *= factor;
        if (this.radius > this.maxRadius) {
            this.radius = this.maxRadius;
        }
        if (this.radius < this.minRadius(this.flatmix)) {
            this.radius = this.minRadius(this.flatmix);
        }
    },

    wheelzoom: function( wheelEvent ) {
        var factor = 1.0 + this.zoomSpeed * -1 * wheelEvent.wheelDelta/10.0;
        this.radius *= factor;
    },

    schedule: function( force ) {
        // console.log("Scheduling");
        if (this._in_anim || force) {
            this._in_anim = true;
            requestAnimationFrame( this.anim.bind(this) );
        }
    },

    unschedule: function() {
        this.resetvel();
        this._in_anim = false;
    },

    resetvel: function() {
        this.altvel = 0;
        this.azvel = 0;
        this.panxvel = 0;
        this.panyvel = 0;
    },

    anim: function() {
        if ( Math.abs(this.azvel) > 0 || Math.abs(this.altvel) > 0 ) {
            // console.log("Animating", this.azvel, this.altvel);
            this.azvel *= this.friction;
            this.azimuth += this.azvel;
            this.azimuth = ((this.azimuth % 360) + 360) % 360;

            this.altvel *= this.friction;
            this.altitude += this.altvel;
            this.altitude = Math.max(Math.min(this.altitude, 180), 0.01);
        }

        if ( Math.abs(this.panxvel) > 0 || Math.abs(this.panyvel) > 0 ) {
            // console.log("Animating", this.panxvel, this.panyvel);
            this.panxvel *= this.friction;
            this.panyvel *= this.friction;

            this.setpan(this.panxvel, this.panyvel);
        }

        this.dispatchEvent( {type:'change'} );

        if (Math.abs(this.altvel)<0.01 && Math.abs(this.azvel)<0.01 && Math.abs(this.panxvel)<0.01 && Math.abs(this.panyvel)<0.01) {
            this.resetvel();
        } else {
            this.schedule();
        }

    }

}
<commit_msg>um switch the convention on friction to make it more semantically sensical; now lower friction means movements glide for longer, and higher friction means movements stop quickly<commit_after>/**
 * @author Eberhard Graether / http://egraether.com/
 */

THREE.LandscapeControls = function ( cover, camera ) {

    THREE.EventTarget.call( this );

    var _this = this;
    STATE = { NONE : -1, ROTATE : 0, PAN : 1, ZOOM : 2 };
    var statefunc = {
        0: this.rotate.bind(this),
        1: this.pan.bind(this),
        2: this.zoom.bind(this),
    }

    this.keystate = null;
    this.camera = camera;
    this.domElement = cover;

    // API
    this.enabled = true;

    // Constants
    this.rotateSpeed = .4;
    this.zoomSpeed = .002;
    this.maxRadius = 400; // makes sure axes & flatmat don't merge in depth buffer
    this.minRadius = function(mix){return 101*mix}; // limits zoom for flatmap, which disappears at r=100
    this.panSpeed = 0.3;
    this.clickTimeout = 200; // milliseconds
    this.friction = 0.5;

    // internals
    this.target = new THREE.Vector3();
    this.azimuth = 45;
    this.altitude = 75;
    this.radius = 250;
    this._scheduled = false;

    var _state = STATE.NONE,
        _start = new THREE.Vector3(),
        _end = new THREE.Vector3();
        _touch = false;

    var _mousedowntime = 0;
    var _clicktime = 0; // Time of last click (mouseup event)
    var _indblpick = false; // In double-click and hold?
    var _picktimer = false; // timer that runs pick event

    // events

    var changeEvent = { type: 'change' };

    this.update = function (flatmix) {
        var mousechange;

        if (_touch) {
            _state = 0;
            mouseChange = _end;
        } else
            mouseChange = _end.clone().subSelf(_start);

        if (mouseChange.length() > 0 && statefunc[_state]) {
            if (statefunc[_state])
                statefunc[_state](mouseChange);
        }

        this.flatmix = flatmix;
        this.setCamera();

        _start = _end;
    };

    // listeners
    function keydown( event ) {
        if ( ! this.enabled ) return;

        if (event.keyCode == 17) {
            this.keystate = STATE.ZOOM;
        } else if (event.keyCode == 16) {
            this.keystate = STATE.PAN;
        } else {
            this.keystate = null;
        }

    };

    function keyup( event ) {
        if ( ! this.enabled ) return;
        this.keystate = null;
    };

    function mousedown( event ) {
        this.unschedule();

        event.preventDefault();
        event.stopPropagation();

        if ( _state === STATE.NONE) {
            _state = this.keystate ? this.keystate : event.button;
            _start = _end = this.getMouse(event);
            if (event.button == 0) {
                _mousedowntime = new Date().getTime();
            }
            // Run double-click event if time since last click is short enough
            if ( _mousedowntime - _clicktime < this.clickTimeout && event.button == 0 ) {
                if (_picktimer) clearTimeout(_picktimer);
                var mouse2D = this.getMouse(event).clone();
                this.dispatchEvent({ type:"dblpick", x:mouse2D.x, y:mouse2D.y, keep:this.keystate == STATE.ZOOM });
                _indblpick = true;
            } else {
                this.dispatchEvent({ type:"mousedown" });
            }
        }
    };

    function mouseup( event ) {
        if ( ! this.enabled ) return;

        this.schedule( true );

        event.preventDefault();
        event.stopPropagation();

        _state = STATE.NONE;
        if (event.button == 0) {
            _clicktime = new Date().getTime();
        }

        // Run picker if time since mousedown is short enough
        if ( _clicktime - _mousedowntime < this.clickTimeout && event.button == 0) {
            var mouse2D = this.getMouse(event).clone();
            this.dispatchEvent({ type: "mouseup" });
            this.dispatchEvent({ type:"pick", x:mouse2D.x, y:mouse2D.y, keep:this.keystate == STATE.ZOOM});
        } else if ( event.button == 0 && _indblpick == true ) {
            this.dispatchEvent({ type:"undblpick" });
            _indblpick = false;
        } else {
            this.dispatchEvent({ type: "mouseup" });
        }
    };

    function mousemove( event ) {
        if ( ! this.enabled ) return;

        if ( _state === STATE.NONE ) {
            return;
        } else {
            _end = this.getMouse(event);
        }
        this.dispatchEvent( changeEvent );
    };

    function touchmove( event ) {
        $(this.domElement).append("touch");
        _touch = true;
        _end = new THREE.Vector2(event.touches[0].clientX, event.touches[1].clientY);
        this.dispatchEvent( changeEvent );
    };

    function mousewheel( event ) {
        if ( ! this.enabled ) return;

        event.preventDefault();
        event.stopPropagation();

        this.wheelzoom( event );
        this.setCamera();
        this.dispatchEvent( changeEvent );

    };

    //code from http://vetruvet.blogspot.com/2010/12/converting-single-touch-events-to-mouse.html
    var touchToMouse=function(b){if(!(b.touches.length>1)){var a=b.changedTouches[0],c="";switch(b.type){case "touchstart":c="mousedown";break;case "touchmove":c="mousemove";break;case "touchend":c="mouseup";break;default:return}var d=document.createEvent("MouseEvent");d.initMouseEvent(c,true,true,window,1,a.screenX,a.screenY,a.clientX,a.clientY,false,false,false,false,0,null);a.target.dispatchEvent(d);b.preventDefault()}};
    this.domElement.addEventListener( 'touchstart', touchToMouse );
    this.domElement.addEventListener( 'touchmove', touchToMouse );
    this.domElement.addEventListener( 'touchend', touchToMouse );

    this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false );

    this.domElement.addEventListener( 'mousemove', mousemove.bind(this), false );
    this.domElement.addEventListener( 'mousedown', mousedown.bind(this), false );
    this.domElement.addEventListener( 'mouseup', mouseup.bind(this), false );
    this.domElement.addEventListener( 'mousewheel', mousewheel.bind(this), false);
    this.domElement.addEventListener( 'mouseout', mouseup.bind(this), false );

    window.addEventListener( 'keydown', keydown.bind(this), false );
    window.addEventListener( 'keyup', keyup.bind(this), false );

    this.resize($(cover).width(), $(cover).height() );
    this.flatmix = 0;
    this.setCamera();
};

THREE.LandscapeControls.prototype = {
    _limitview: function( ) {
        var flatmix = this.flatmix;
        var azlim = flatmix * 180;
        var altlim = flatmix * 90;

        this.altitude = this.altitude > 179.9999-altlim ? 179.9999-altlim : this.altitude;
        this.altitude = this.altitude < 0.0001+altlim ? 0.0001+altlim : this.altitude;

        if (azlim > this.azimuth || this.azimuth > (360 - azlim)) {
            var d1 = azlim - this.azimuth;
            var d2 = 360 - azlim - this.azimuth;
            this.azimuth = Math.abs(d1) > Math.abs(d2) ? 360-azlim : azlim;
        }

        var rad = this.radius, target = this.target.clone();
        var container = $(this.domElement.parentNode.parentNode)
        if (container.find("#zlockwhole").length > 0) {
            if (container.find("#zlockwhole")[0].checked) {
                rad  = this.flatsize / 2 / this.camera.cameraP.aspect;
                rad /= Math.tan(this.camera.fov / 2 * Math.PI / 180);
                rad -= this.flatoff;
                rad = flatmix * rad + (1 - flatmix) * this.radius;
            } else if (!container.find("#zlocknone")[0].checked) {
                rad  = this.flatsize / 4 / this.camera.cameraP.aspect;
                rad /= Math.tan(this.camera.fov / 2 * Math.PI / 180);
                rad -= this.flatoff;
                rad = flatmix * rad + (1 - flatmix) * this.radius;
                if (container.find("#zlockleft")[0].checked) {
                    target.x = flatmix * (-this.flatsize / 4) + (1 - flatmix) * target.x;
                } else if (container.find("#zlockright")[0].checked) {
                    target.x = flatmix * ( this.flatsize / 4) + (1 - flatmix) * target.x;
                }
            }
        }

        this._radius = rad;
        this._target = target;
    },

    resize: function(w, h) {
        this.domElement.style.width = w+"px";
        this.domElement.style.height = h+"px";
    },

    getMouse: function ( event ) {
        var off = $(event.target).offset();
        return new THREE.Vector2( event.clientX - off.left, event.clientY - off.top);
    },

    setCamera: function(az, alt, rad) {
        var changed = false;
        if (az !== undefined) {
            this.azimuth = ((az % 360)+360)%360;
            changed = true;
        }
        if (alt !== undefined) {
            this.altitude = alt;
            changed = true;
        }
        if (rad !== undefined) {
            this.radius = rad;
            changed = true;
        }

        this._limitview();

        var altrad = this.altitude*Math.PI / 180;
        var azirad = (this.azimuth+90)*Math.PI / 180;

        var eye = new THREE.Vector3(
            this._radius*Math.sin(altrad)*Math.cos(azirad),
            this._radius*Math.sin(altrad)*Math.sin(azirad),
            this._radius*Math.cos(altrad)
        );

        this.camera.position.add( this._target, eye );
        this.camera.lookAt( this._target );
        if (changed)
            this.dispatchEvent( {type:'change'} );
    },

    rotate: function ( mouseChange ) {
        this.azvel = -1 * this.rotateSpeed * mouseChange.x;
        var az = this.azimuth + this.azvel;
        if ( this.azlim > 0 ) {
            if ( this.azlim > az || az > (360-this.azlim)) {
                this.azimuth = azdiff < 0 ? this.azlim : 360-this.azlim;
            } else {
                this.azimuth = az - 360*Math.floor(az / 360);
            }
        } else {
            this.azimuth = az - 360*Math.floor(az / 360);
        }

        //this.altitude -= this.rotateSpeed*mouseChange.y;
        this.altvel = -1 * this.rotateSpeed * mouseChange.y;
        this.altitude += this.altvel;
        this.altitude = Math.max(Math.min(this.altitude, 180), 0.01);

        // Add panning depending on flatmix
        var panMouseChange = new Object;
        panMouseChange.x = mouseChange.x * Math.pow(this.flatmix, 2);
        panMouseChange.y = mouseChange.y * Math.pow(this.flatmix, 2);
        this.pan(panMouseChange);
    },

    pan: function( mouseChange ) {
        this.panxvel = this.panSpeed * mouseChange.x;
        this.panyvel = this.panSpeed * mouseChange.y;
        this.setpan( this.panSpeed * mouseChange.x, this.panSpeed * mouseChange.y );
    },

    setpan: function( x, y ) {
        var eye = this.camera.position.clone().subSelf(this.target);
        var right = eye.clone().crossSelf( this.camera.up );
        var up = right.clone().crossSelf(eye);
        var pan = (new THREE.Vector3()).add(
            right.setLength( x ),
            up.setLength( y ));
        this.camera.position.addSelf( pan );
        this.target.addSelf( pan );
    },

    zoom: function( mouseChange ) {
        var factor = 1.0 + mouseChange.y*this.zoomSpeed;
        this.radius *= factor;
        if (this.radius > this.maxRadius) {
            this.radius = this.maxRadius;
        }
        if (this.radius < this.minRadius(this.flatmix)) {
            this.radius = this.minRadius(this.flatmix);
        }
    },

    wheelzoom: function( wheelEvent ) {
        var factor = 1.0 + this.zoomSpeed * -1 * wheelEvent.wheelDelta/10.0;
        this.radius *= factor;
    },

    schedule: function( force ) {
        // console.log("Scheduling");
        if (this._in_anim || force) {
            this._in_anim = true;
            requestAnimationFrame( this.anim.bind(this) );
        }
    },

    unschedule: function() {
        this.resetvel();
        this._in_anim = false;
    },

    resetvel: function() {
        this.altvel = 0;
        this.azvel = 0;
        this.panxvel = 0;
        this.panyvel = 0;
    },

    anim: function() {
        if ( Math.abs(this.azvel) > 0 || Math.abs(this.altvel) > 0 ) {
            // console.log("Animating", this.azvel, this.altvel);
            this.azvel *= 1 - this.friction;
            this.azimuth += this.azvel;
            this.azimuth = ((this.azimuth % 360) + 360) % 360;

            this.altvel *= 1 - this.friction;
            this.altitude += this.altvel;
            this.altitude = Math.max(Math.min(this.altitude, 180), 0.01);
        }

        if ( Math.abs(this.panxvel) > 0 || Math.abs(this.panyvel) > 0 ) {
            // console.log("Animating", this.panxvel, this.panyvel);
            this.panxvel *= 1 - this.friction;
            this.panyvel *= 1 - this.friction;

            this.setpan(this.panxvel, this.panyvel);
        }

        this.dispatchEvent( {type:'change'} );

        if (Math.abs(this.altvel)<0.01 && Math.abs(this.azvel)<0.01 && Math.abs(this.panxvel)<0.01 && Math.abs(this.panyvel)<0.01) {
            this.resetvel();
        } else {
            this.schedule();
        }

    }

}
<|endoftext|>


<commit_before>import { test } from 'substance-test'
import { openMetadataEditor, createTestVfs, getSelection } from './shared/integrationTestHelpers'
import setupTestApp from './shared/setupTestApp'

const TRANSLATED_TITLE = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE article PUBLIC "-//NLM//DTD JATS (Z39.96) Journal Archiving DTD v1.0 20120330//EN" "JATS-journalarchiving.dtd">
<article xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ali="http://www.niso.org/schemas/ali/1.0">
  <front>
    <article-meta>
      <title-group>
        <article-title>Object vision to hand action in macaque parietal, premotor, and motor cortices</article-title>
        <trans-title-group xml:lang="es">
          <trans-title id="trans-title-1">Objeto de visión a acción manual en <italic id="italic-1">cortezas parietales</italic>, premotoras y motoras de macaco</trans-title>
        </trans-title-group>
      </title-group>
      <abstract>
      </abstract>
    </article-meta>
  </front>
  <body>
  </body>
  <back>
  </back>
</article>`

test(`MetadataEditor: clicking on "Add Translation" should not select card (#838)`, t => {
  let { editor } = _setup(t, TRANSLATED_TITLE)
  let translatableEditor = editor.find('.sc-translatable-editor')
  // TODO: I don't like 'se-control' as a name for the 'Add Translation' button
  let addTranslation = translatableEditor.find('.se-control')
  addTranslation.click()
  let sel = getSelection(editor)
  let isCardSelected = sel && sel.isCustomSelection() && sel.getCustomType() === 'model'
  t.notOk(isCardSelected, 'The translation card should not be selected')
  t.end()
})

test(`MetadataEditor: putting a cursor inside a newly created footnote (#947)`, t => {
  t.end()
})

test(`MetadataEditor: after adding a new footnote it should be selected (#948)`, t => {
  t.end()
})

function _setup (t, seedXML) {
  let testVfs = createTestVfs(seedXML)
  let {app} = setupTestApp(t, {
    vfs: testVfs,
    archiveId: 'test'
  })
  let editor = openMetadataEditor(app)
  return { editor }
}
<commit_msg>Reflect current problems in test.<commit_after>import { test } from 'substance-test'
import { openMetadataEditor, createTestVfs, getSelection } from './shared/integrationTestHelpers'
import setupTestApp from './shared/setupTestApp'

const TRANSLATED_TITLE = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE article PUBLIC "-//NLM//DTD JATS (Z39.96) Journal Archiving DTD v1.0 20120330//EN" "JATS-journalarchiving.dtd">
<article xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ali="http://www.niso.org/schemas/ali/1.0">
  <front>
    <article-meta>
      <title-group>
        <article-title>Object vision to hand action in macaque parietal, premotor, and motor cortices</article-title>
        <trans-title-group xml:lang="es">
          <trans-title id="trans-title-1">Objeto de visión a acción manual en <italic id="italic-1">cortezas parietales</italic>, premotoras y motoras de macaco</trans-title>
        </trans-title-group>
      </title-group>
      <abstract>
      </abstract>
    </article-meta>
  </front>
  <body>
  </body>
  <back>
  </back>
</article>`

const EMPTY_ARTICLE = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE article PUBLIC "-//NLM//DTD JATS (Z39.96) Journal Archiving DTD v1.0 20120330//EN" "JATS-journalarchiving.dtd">
<article xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ali="http://www.niso.org/schemas/ali/1.0">
  <front>
    <article-meta>
      <title-group>
        <article-title></article-title>
      </title-group>
    </article-meta>
  </front>
  <body>
  </body>
  <back>
  </back>
</article>`

test(`MetadataEditor: clicking on "Add Translation" should not select card (#838)`, t => {
  let { editor } = _setup(t, TRANSLATED_TITLE)
  let translatableEditor = editor.find('.sc-translatable-editor')
  // TODO: I don't like 'se-control' as a name for the 'Add Translation' button
  let addTranslation = translatableEditor.find('.se-control')
  addTranslation.click()
  let sel = getSelection(editor)
  let isCardSelected = sel && sel.isCustomSelection() && sel.getCustomType() === 'model'
  t.notOk(isCardSelected, 'The translation card should not be selected')
  t.end()
})

test(`MetadataEditor: a newly created footnote should contain at least one parapgraph (#947)`, t => {
  let { editor } = _setup(t, EMPTY_ARTICLE)
  _addItem(editor, 'footnote')
  let fnEditor = editor.find('.sc-footnote .sc-fn')
  let paragraphs = fnEditor.findAll('.sc-p')
  t.ok(paragraphs.length > 0, 'There should be some paragraphs')
  t.end()
})

test(`MetadataEditor: after adding a new footnote cursor should be inside (#948)`, t => {
  let { editor } = _setup(t, EMPTY_ARTICLE)
  _addItem(editor, 'footnote')
  let sel = getSelection(editor)
  let isContentPropertySelection = sel && sel.isPropertySelection() && sel.getPath()[1] === 'content'
  t.ok(isContentPropertySelection, 'The footnote content should be selected')
  t.end()
})

function _setup (t, seedXML) {
  let testVfs = createTestVfs(seedXML)
  let {app} = setupTestApp(t, {
    vfs: testVfs,
    archiveId: 'test'
  })
  let editor = openMetadataEditor(app)
  return { editor }
}

function _addItem (metadataEditor, modelName) {
  // open the add drop down
  let addDropDown = metadataEditor.find('.sc-tool-dropdown.sm-add')
  addDropDown.find('button').click()
  addDropDown.find('.sc-menu-item.sm-add-' + modelName).click()
}
<|endoftext|>


<commit_before>'use strict'

const pmpProcessConfig      = require('./configs').pmpProcessConfig;
const pmpEngineStatusEvts   = require('./configs').pmpEngineStatusEvts;
const pmpEvts               = require('./configs').pmpGulpEvts;
const PmpEvt                = require('./configs').PmpGulpEvt;
const isJsonString = function (str) {
    try {
        JSON.parse(str);
    } catch (e) {
        return false;
    }
    return true;
}

let starter = function(spawnWatch, pmpConfig, statusStream, additionalArguments) {
    /*************************************************************************
     *  four steps process:
     *  1 - first start child process itself
     *  2 - wait for gulp pimpCommands config prompt
     *  3 - pass gulp pimpCommands config to child process
     *  4 - receive pmpEngine started notification
     * ********************************************************************* */

    //STEP 1
    let pEvtSub = spawnWatch.processEventStream.subscribe((evt)=>{
        console.log(evt)
        //react to process evts
        if(evt === 'pending stop' || evt === 'pending start') statusStream.onNext(pmpEngineStatusEvts.pending);
        if(evt === 'stopped') {
            statusStream.onNext(pmpEngineStatusEvts.stopped);
            pEvtSub.dispose();
        }
        //do not react to started evt because this is handled below (at the hand of step 4)
    });
    let ipsEvtSub = spawnWatch.ipcStream.subscribe(IPCmsg => {
        console.log(IPCmsg)
        switch(IPCmsg.type){
            //STEP 2
            case pmpEvts.init:
                //STEP 3 - send config to process
                spawnWatch.ipcInput(new PmpEvt(pmpEvts.configSend, pmpConfig));
            break;

            //STEP 4
            case pmpEvts.started:
                statusStream.onNext(pmpEngineStatusEvts.started);
                ipsEvtSub.dispose();
            break;
        };
    });

    //setup process config with optional arguments
    let processConfig = Object.assign({}, pmpProcessConfig);
    if(additionalArguments) processConfig.args = processConfig.args.concat(additionalArguments);

    return spawnWatch.start(processConfig);
}

module.exports = {
    starter: starter
}<commit_msg>fix multiple calls to start when restarting instance<commit_after>'use strict'

const pmpProcessConfig      = require('./configs').pmpProcessConfig;
const pmpEngineStatusEvts   = require('./configs').pmpEngineStatusEvts;
const pmpEvts               = require('./configs').pmpGulpEvts;
const PmpEvt                = require('./configs').PmpGulpEvt;
const isJsonString = function (str) {
    try {
        JSON.parse(str);
    } catch (e) {
        return false;
    }
    return true;
}

let starter = function(spawnWatch, pmpConfig, statusStream, additionalArguments) {
    /*************************************************************************
     *  four steps process:
     *  1 - first start child process itself
     *  2 - wait for gulp pimpCommands config prompt
     *  3 - pass gulp pimpCommands config to child process
     *  4 - receive pmpEngine started notification
     * ********************************************************************* */
    //STEP 1

    //status update - pending start / pending stop / stopped (started update is done at step 4)
    //all updates can happen once
    spawnWatch.processEventStream
        .filter(status => (status === 'pending start'))
        .first()
        .subscribe((evt)=>{ statusStream.onNext(pmpEngineStatusEvts.pending); });

    spawnWatch.processEventStream
        .filter(status => (status === 'pending stop'))
        .first()
        .subscribe((evt)=>{ statusStream.onNext(pmpEngineStatusEvts.pending); });

    spawnWatch.processEventStream
        .filter(status => (status === 'stopped'))
        .first()
        .subscribe((evt)=>{ statusStream.onNext(pmpEngineStatusEvts.stopped); });

    let ipsEvtSub = spawnWatch.ipcStream.subscribe(IPCmsg => {
        switch(IPCmsg.type){
            //STEP 2
            case pmpEvts.init:
                //STEP 3 - send config to process
                spawnWatch.ipcInput(new PmpEvt(pmpEvts.configSend, pmpConfig));
            break;

            //STEP 4 (! ipc started event means pmp-gulp, not the child process itself)
            case pmpEvts.started:
                statusStream.onNext(pmpEngineStatusEvts.started);
                ipsEvtSub.dispose();
            break;
        };
    });

    //setup process config with optional arguments
    let processConfig = Object.assign({}, pmpProcessConfig);
    if(additionalArguments) processConfig.args = processConfig.args.concat(additionalArguments);

    return spawnWatch.start(processConfig);
}

module.exports = {
    starter: starter
}<|endoftext|>


<commit_before>'use strict'
var SourceMappingDecoder = require('./sourceMappingDecoder')

function offsetToColumnConverter (compilerEvent) {
  this.lineBreakPositionsByContent = {}
  this.sourceMappingDecoder = new SourceMappingDecoder()
  var self = this
  if (compilerEvent) {
    compilerEvent.register('compilationFinished', function (success, data, source) {
      self.clear()
    })
  }
}

offsetToColumnConverter.prototype.offsetToLineColumn = function (rawLocation, file, sources, asts) {
  if (!this.lineBreakPositionsByContent[file]) {
    for (var filename in asts) {
      var source = asts[filename]
      if (source.id === file) {
        this.lineBreakPositionsByContent[file] = this.sourceMappingDecoder.getLinebreakPositions(sources[filename].content)
        break
      }
    }
  }
  return this.sourceMappingDecoder.convertOffsetToLineColumn(rawLocation, this.lineBreakPositionsByContent[file])
}

offsetToColumnConverter.prototype.clear = function () {
  this.lineBreakPositionsByContent = {}
}

module.exports = offsetToColumnConverter
<commit_msg>checking of the type of id<commit_after>'use strict'
var SourceMappingDecoder = require('./sourceMappingDecoder')

function offsetToColumnConverter (compilerEvent) {
  this.lineBreakPositionsByContent = {}
  this.sourceMappingDecoder = new SourceMappingDecoder()
  var self = this
  if (compilerEvent) {
    compilerEvent.register('compilationFinished', function (success, data, source) {
      self.clear()
    })
  }
}

offsetToColumnConverter.prototype.offsetToLineColumn = function (rawLocation, file, sources, asts) {
  if (!this.lineBreakPositionsByContent[file]) {
    for (var filename in asts) {
      var source = asts[filename]
      // source id was string before. in newer versions it has been changed to an integer so we need to check the type here
      if (typeof source.id === 'string') source.id = parseInt(source.id, 10)
      if (source.id === file) {
        this.lineBreakPositionsByContent[file] = this.sourceMappingDecoder.getLinebreakPositions(sources[filename].content)
        break
      }
    }
  }
  return this.sourceMappingDecoder.convertOffsetToLineColumn(rawLocation, this.lineBreakPositionsByContent[file])
}

offsetToColumnConverter.prototype.clear = function () {
  this.lineBreakPositionsByContent = {}
}

module.exports = offsetToColumnConverter
<|endoftext|>


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

// Test installation on Cordova 2 project

var fs = require('fs')
  , path = require('path')
  , plist = require('plist')
  , xcode = require('xcode')
  , osenv = require('osenv')
  , shell = require('shelljs')
  , et = require('elementtree')
  , android = require(path.join(__dirname, '..', 'platforms', 'android'))

  , test_dir = path.join(osenv.tmpdir(), 'test_plugman')
  , test_project_dir = path.join(test_dir, 'projects', 'android_two')
  , test_plugin_dir = path.join(test_dir, 'plugins', 'ChildBrowser')
  , xml_path     = path.join(test_dir, 'plugins', 'ChildBrowser', 'plugin.xml')
  , xml_text, plugin_et;

exports.setUp = function(callback) {
    shell.mkdir('-p', test_dir);

    // copy the ios test project to a temp directory
    shell.cp('-r', path.join(__dirname, 'projects'), test_dir);

    // copy the ios test plugin to a temp directory
    shell.cp('-r', path.join(__dirname, 'plugins'), test_dir);

    // parse the plugin.xml into an elementtree object
    xml_text   = fs.readFileSync(xml_path, 'utf-8')
    plugin_et  = new et.ElementTree(et.XML(xml_text));

    callback();
}

exports.tearDown = function(callback) {
    // remove the temp files (projects and plugins)
    shell.rm('-rf', test_dir);
    callback();
}

exports['should install webless plugin'] = function (test) {

    // setting up a DummyPlugin
    var dummy_plugin_dir = path.join(test_dir, 'plugins', 'WeblessPlugin')
    var dummy_xml_path = path.join(test_dir, 'plugins', 'WeblessPlugin', 'plugin.xml')
    dummy_plugin_et  = new et.ElementTree(et.XML(fs.readFileSync(dummy_xml_path, 'utf-8')));

    android.handlePlugin('install', test_project_dir, dummy_plugin_dir, dummy_plugin_et, { APP_ID: 12345 });

    test.done();
}

exports['should move the js file'] = function (test) {
    var jsPath = path.join(test_dir, 'projects', 'android_two', 'assets', 'www', 'childbrowser.js');

    android.handlePlugin('install', test_project_dir, test_plugin_dir, plugin_et, { APP_ID: 12345 });
    fs.stat(jsPath, function(err, stats) {
        test.ok(!err);
        test.ok(stats.isFile());
        test.done();
    });
}

exports['should move the directory'] = function (test) {
    android.handlePlugin('install', test_project_dir, test_plugin_dir, plugin_et);

    var assetPath = path.join(test_dir, 'projects', 'android_two', 'assets', 'www', 'childbrowser');

    var assets = fs.statSync(assetPath);

    test.ok(assets.isDirectory());
    test.ok(fs.statSync(assetPath + '/image.jpg'))
    test.done();
}

exports['should move the src file'] = function (test) {
    android.handlePlugin('install', test_project_dir, test_plugin_dir, plugin_et, { APP_ID: 12345 });

    var javaPath = path.join(test_dir, 'projects', 'android_two', 'src', 'com', 'phonegap', 'plugins', 'childBrowser', 'ChildBrowser.java');
    test.ok(fs.statSync(javaPath));
    test.done();
}

exports['should add ChildBrowser to config.xml'] = function (test) {
    android.handlePlugin('install', test_project_dir, test_plugin_dir, plugin_et, { APP_ID: 12345 });

    var pluginsXmlPath = path.join(test_dir, 'projects', 'android_two', 'res', 'xml', 'config.xml');
    var pluginsTxt = fs.readFileSync(pluginsXmlPath, 'utf-8'),
        pluginsDoc = new et.ElementTree(et.XML(pluginsTxt)),
        expected = 'plugins/plugin[@name="ChildBrowser"]' +
                    '[@value="com.phonegap.plugins.childBrowser.ChildBrowser"]';

    test.ok(pluginsDoc.find(expected));
    test.done();
}

exports['should add ChildBrowser to AndroidManifest.xml'] = function (test) {
    android.handlePlugin('install', test_project_dir, test_plugin_dir, plugin_et, { APP_ID: 12345 });

    var manifestPath = path.join(test_dir, 'projects', 'android_two', 'AndroidManifest.xml');
    var manifestTxt = fs.readFileSync(manifestPath, 'utf-8'),
        manifestDoc = new et.ElementTree(et.XML(manifestTxt)),
        activities = manifestDoc.findall('application/activity'), i;

    var found = false;
    for (i=0; i<activities.length; i++) {
        if ( activities[i].attrib['android:name'] === 'com.phonegap.plugins.childBrowser.ChildBrowser' ) {
            found = true;
            break;
        }
    }
    test.ok(found);
    test.done();
}

exports['should not install a plugin that is already installed'] = function (test) {
    var jsPath = path.join(test_dir, 'projects', 'android_two', 'assets', 'www', 'childbrowser.js');

    android.handlePlugin('install', test_project_dir, test_plugin_dir, plugin_et, { APP_ID: 12345 });

    test.throws(function() {android.handlePlugin('install', test_project_dir, test_plugin_dir, plugin_et);}, /already installed/);

    test.done();
}

exports['should add whitelist hosts'] = function (test) {
	android.handlePlugin('install', test_project_dir, test_plugin_dir, plugin_et, { APP_ID: 12345 });

    var pluginsXmlPath = path.join(test_dir, 'projects', 'android_two', 'res', 'xml', 'config.xml');
    var pluginsTxt = fs.readFileSync(pluginsXmlPath, 'utf-8'),
        pluginsDoc = new et.ElementTree(et.XML(pluginsTxt));

    test.equal(pluginsDoc.findall("access").length, 3, "/access");
	test.equal(pluginsDoc.findall("access")[1].attrib["origin"], "build.phonegap.com")
    test.equal(pluginsDoc.findall("access")[2].attrib["origin"], "12345.s3.amazonaws.com")
    test.done();
}
<commit_msg>fixed android two install tests<commit_after>/*
 *
 * Copyright 2013 Anis Kadri
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
*/

// Test installation on Cordova 2 project

var fs = require('fs')
  , path = require('path')
  , plist = require('plist')
  , xcode = require('xcode')
  , osenv = require('osenv')
  , shell = require('shelljs')
  , et = require('elementtree')
  , android = require(path.join(__dirname, '..', 'platforms', 'android'))
  , plugin_loader = require('../util/plugin_loader')
  , test_dir = path.join(osenv.tmpdir(), 'test_plugman')
  , test_project_dir = path.join(test_dir, 'projects', 'android_two')
  , test_plugin_dir = path.join(test_dir, 'plugins', 'ChildBrowser')
  , xml_path     = path.join(test_dir, 'plugins', 'ChildBrowser', 'plugin.xml')
  , xml_text, plugin_et;

exports.setUp = function(callback) {
    shell.mkdir('-p', test_dir);

    // copy the ios test project to a temp directory
    shell.cp('-r', path.join(__dirname, 'projects'), test_dir);

    // copy the ios test plugin to a temp directory
    shell.cp('-r', path.join(__dirname, 'plugins'), test_dir);

    // parse the plugin.xml into an elementtree object
    xml_text   = fs.readFileSync(xml_path, 'utf-8')
    plugin_et  = new et.ElementTree(et.XML(xml_text));

    callback();
}

exports.tearDown = function(callback) {
    // remove the temp files (projects and plugins)
    shell.rm('-rf', test_dir);
    callback();
}

exports['should install webless plugin'] = function (test) {

    // setting up a DummyPlugin
    var dummy_plugin_dir = path.join(test_dir, 'plugins', 'WeblessPlugin')
    var dummy_xml_path = path.join(test_dir, 'plugins', 'WeblessPlugin', 'plugin.xml')
    dummy_plugin_et  = new et.ElementTree(et.XML(fs.readFileSync(dummy_xml_path, 'utf-8')));

    android.handlePlugin('install', test_project_dir, dummy_plugin_dir, dummy_plugin_et, { APP_ID: 12345 });

    test.done();
}

exports['should move the js file'] = function (test) {
    var pluginsPath = path.join(test_dir, 'plugins');
    var wwwPath = path.join(test_dir, 'projects', 'android_two', 'assets', 'www');
    var jsPath = path.join(test_dir, 'projects', 'android_two', 'assets', 'www', 'childbrowser.js');

    android.handlePlugin('install', test_project_dir, test_plugin_dir, plugin_et, { APP_ID: 12345 });
    plugin_loader.handlePrepare(test_project_dir, pluginsPath, wwwPath, 'android');
    console.log(wwwPath + ' ' + fs.readdirSync(wwwPath));
    fs.stat(jsPath, function(err, stats) {
        test.ok(!err);
        test.ok(stats.isFile());
        test.done();
    });
}

exports['should move the directory'] = function (test) {
    var pluginsPath = path.join(test_dir, 'plugins');
    var wwwPath = path.join(test_dir, 'projects', 'android_two', 'assets', 'www');

    android.handlePlugin('install', test_project_dir, test_plugin_dir, plugin_et);
    plugin_loader.handlePrepare(test_project_dir, pluginsPath, wwwPath, 'android');

    var assetPath = path.join(test_dir, 'projects', 'android_two', 'assets', 'www', 'childbrowser');
    var assets = fs.statSync(assetPath);

    test.ok(assets.isDirectory());
    test.ok(fs.statSync(assetPath + '/image.jpg'))
    test.done();
}

exports['should move the src file'] = function (test) {
    var pluginsPath = path.join(test_dir, 'plugins');
    var wwwPath = path.join(test_dir, 'projects', 'android_two', 'assets', 'www');

    android.handlePlugin('install', test_project_dir, test_plugin_dir, plugin_et, { APP_ID: 12345 });
    plugin_loader.handlePrepare(test_project_dir, pluginsPath, wwwPath, 'android');

    var javaPath = path.join(test_dir, 'projects', 'android_two', 'src', 'com', 'phonegap', 'plugins', 'childBrowser', 'ChildBrowser.java');
    test.ok(fs.statSync(javaPath));
    test.done();
}

exports['should add ChildBrowser to config.xml'] = function (test) {
    android.handlePlugin('install', test_project_dir, test_plugin_dir, plugin_et, { APP_ID: 12345 });

    var pluginsXmlPath = path.join(test_dir, 'projects', 'android_two', 'res', 'xml', 'config.xml');
    var pluginsTxt = fs.readFileSync(pluginsXmlPath, 'utf-8'),
        pluginsDoc = new et.ElementTree(et.XML(pluginsTxt)),
        expected = 'plugins/plugin[@name="ChildBrowser"]' +
                    '[@value="com.phonegap.plugins.childBrowser.ChildBrowser"]';

    test.ok(pluginsDoc.find(expected));
    test.done();
}

exports['should add ChildBrowser to AndroidManifest.xml'] = function (test) {
    android.handlePlugin('install', test_project_dir, test_plugin_dir, plugin_et, { APP_ID: 12345 });

    var manifestPath = path.join(test_dir, 'projects', 'android_two', 'AndroidManifest.xml');
    var manifestTxt = fs.readFileSync(manifestPath, 'utf-8'),
        manifestDoc = new et.ElementTree(et.XML(manifestTxt)),
        activities = manifestDoc.findall('application/activity'), i;

    var found = false;
    for (i=0; i<activities.length; i++) {
        if ( activities[i].attrib['android:name'] === 'com.phonegap.plugins.childBrowser.ChildBrowser' ) {
            found = true;
            break;
        }
    }
    test.ok(found);
    test.done();
}

exports['should not install a plugin that is already installed'] = function (test) {
    var pluginsPath = path.join(test_dir, 'plugins');
    var wwwPath = path.join(test_dir, 'projects', 'android_two', 'assets', 'www');
    var jsPath = path.join(test_dir, 'projects', 'android_two', 'assets', 'www', 'childbrowser.js');

    android.handlePlugin('install', test_project_dir, test_plugin_dir, plugin_et, { APP_ID: 12345 });
    plugin_loader.handlePrepare(test_project_dir, pluginsPath, wwwPath, 'android');

    test.throws(function() {android.handlePlugin('install', test_project_dir, test_plugin_dir, plugin_et);}, /already installed/);

    test.done();
}

exports['should add whitelist hosts'] = function (test) {
	android.handlePlugin('install', test_project_dir, test_plugin_dir, plugin_et, { APP_ID: 12345 });

    var pluginsXmlPath = path.join(test_dir, 'projects', 'android_two', 'res', 'xml', 'config.xml');
    var pluginsTxt = fs.readFileSync(pluginsXmlPath, 'utf-8'),
        pluginsDoc = new et.ElementTree(et.XML(pluginsTxt));

    test.equal(pluginsDoc.findall("access").length, 3, "/access");
	test.equal(pluginsDoc.findall("access")[1].attrib["origin"], "build.phonegap.com")
    test.equal(pluginsDoc.findall("access")[2].attrib["origin"], "12345.s3.amazonaws.com")
    test.done();
}
<|endoftext|>


<commit_before>import React from 'react';
import ReactDOM from 'react-dom';
import SplitPane from '../../lib/SplitPane';
import Resizer from '../../lib/Resizer';
import Pane from '../../lib/Pane';
import chai from 'chai';
const expect = chai.expect;
import VendorPrefix from 'react-vendor-prefix';
import ReactTestUtils from 'react-addons-test-utils';

export default (jsx) => {

    const splitPane = ReactTestUtils.renderIntoDocument(jsx);
    const component = ReactTestUtils.findRenderedComponentWithType(splitPane, SplitPane);

    const findPanes = () => {
        return ReactTestUtils.scryRenderedComponentsWithType(component, Pane);
    };

    const findTopPane = () => {
        return findPanes()[0];
    };

    const findResizer = () => {
        return ReactTestUtils.scryRenderedComponentsWithType(splitPane, Resizer);
    };

    const assertStyles = (componentName, actualStyles, expectedStyles) => {
        const prefixed = VendorPrefix.prefix({styles: expectedStyles}).styles;
        for (let prop in prefixed) {
            if (prefixed.hasOwnProperty(prop)) {
                //console.log(prop + ': \'' + actualStyles[prop] + '\',');
                if (prefixed[prop] && prefixed[prop] !== '') {
                    //console.log(prop + ': \'' + actualStyles[prop] + '\',');
                    expect(actualStyles[prop]).to.equal(prefixed[prop], `${componentName} has incorrect css property for '${prop}'`);
                }
                //expect(actualStyles[prop]).to.equal(prefixed[prop], `${componentName} has incorrect css property for '${prop}'`);
            }
        }
        return this;
    };

    const assertFirstPaneStyles= (expectedStyles) => {
        //return assertStyles('First Pane', findTopPane().getDOMNode().style, expectedStyles);
        return assertStyles('First Pane', ReactDOM.findDOMNode(findTopPane()).style, expectedStyles);
    };

    return {

        assertOrientation(expectedOrientation) {
            expect(ReactDOM.findDOMNode(component).className).to.contain(expectedOrientation, `Incorrect orientation`);
            return this;
        },

        assertPaneContents(expectedContents) {
            const panes = findPanes();
            let values = panes.map((pane) => {
                //return pane.getDOMNode().textContent;
                return ReactDOM.findDOMNode(pane).textContent;
            });
            expect(values).to.eql(expectedContents, `Incorrect contents for Pane`);
            return this;
        },

        assertContainsResizer(){
            expect(findResizer().length).to.equal(1, `Expected the SplitPane to have a single Resizer`);
            expect(findPanes().length).to.equal(2, `Expected the SplitPane to have 2 panes`);
            return this;
        },

        assertFirstPaneWidth(expectedWidth) {
            return assertFirstPaneStyles({width: expectedWidth});
        },

        assertFirstPaneHeight(expectedHeight) {
            return assertFirstPaneStyles({height: expectedHeight});
        }
    }
}

<commit_msg>remove commented out lines<commit_after>import React from 'react';
import ReactDOM from 'react-dom';
import SplitPane from '../../lib/SplitPane';
import Resizer from '../../lib/Resizer';
import Pane from '../../lib/Pane';
import chai from 'chai';
const expect = chai.expect;
import VendorPrefix from 'react-vendor-prefix';
import ReactTestUtils from 'react-addons-test-utils';

export default (jsx) => {

    const splitPane = ReactTestUtils.renderIntoDocument(jsx);
    const component = ReactTestUtils.findRenderedComponentWithType(splitPane, SplitPane);

    const findPanes = () => {
        return ReactTestUtils.scryRenderedComponentsWithType(component, Pane);
    };

    const findTopPane = () => {
        return findPanes()[0];
    };

    const findResizer = () => {
        return ReactTestUtils.scryRenderedComponentsWithType(splitPane, Resizer);
    };

    const assertStyles = (componentName, actualStyles, expectedStyles) => {
        const prefixed = VendorPrefix.prefix({styles: expectedStyles}).styles;
        for (let prop in prefixed) {
            if (prefixed.hasOwnProperty(prop)) {
                //console.log(prop + ': \'' + actualStyles[prop] + '\',');
                if (prefixed[prop] && prefixed[prop] !== '') {
                    //console.log(prop + ': \'' + actualStyles[prop] + '\',');
                    expect(actualStyles[prop]).to.equal(prefixed[prop], `${componentName} has incorrect css property for '${prop}'`);
                }
                //expect(actualStyles[prop]).to.equal(prefixed[prop], `${componentName} has incorrect css property for '${prop}'`);
            }
        }
        return this;
    };

    const assertFirstPaneStyles= (expectedStyles) => {
        return assertStyles('First Pane', ReactDOM.findDOMNode(findTopPane()).style, expectedStyles);
    };

    return {

        assertOrientation(expectedOrientation) {
            expect(ReactDOM.findDOMNode(component).className).to.contain(expectedOrientation, `Incorrect orientation`);
            return this;
        },

        assertPaneContents(expectedContents) {
            const panes = findPanes();
            let values = panes.map((pane) => {
                return ReactDOM.findDOMNode(pane).textContent;
            });
            expect(values).to.eql(expectedContents, `Incorrect contents for Pane`);
            return this;
        },

        assertContainsResizer(){
            expect(findResizer().length).to.equal(1, `Expected the SplitPane to have a single Resizer`);
            expect(findPanes().length).to.equal(2, `Expected the SplitPane to have 2 panes`);
            return this;
        },

        assertFirstPaneWidth(expectedWidth) {
            return assertFirstPaneStyles({width: expectedWidth});
        },

        assertFirstPaneHeight(expectedHeight) {
            return assertFirstPaneStyles({height: expectedHeight});
        }
    }
}

<|endoftext|>