<commit_before>/**
 * Created by davewcampbell on 15-01-27.
 */

(function(){
    'use strict';

    angular
        .module('dorin')
        .factory('purgeService', purgeService);

    purgeService.$inject = ['$http'];

    function purgeService($http){

        var service = {
            addJob: addJob,
            getAllJobs: getAllJobs,
            getJobById: getJobById,
            saveJob: saveJob
        };

        return service;

        ///////

        function addJob(job){
            var url = '/api/jobs/';

            $http.post(url, job)
                .then(addJobComplete)
                .catch(handleError);
        }

        function addJobComplete(response){
            return response.data;
        }

        function getAllJobs(){

            return $http.get('/api/jobs')
                .then(getAllJobsComplete)
                .catch(handleError);
        }

        function getAllJobsComplete(response){
            return response.data;
        }

        function getJobById(id){

            return $http.get('/api/jobs/' + id )
                .then(getJobByIdComplete)
                .catch(handleError);

        }

        function getJobByIdComplete(response){
            return response.data;
        }

        function handleError(err){
            //TODO: handle error
            console.log(err);
        }

        function saveJob(job){

            var url = '/api/jobs/' + job._id;

            return $http.put(url, job)
                .then(saveJobComplete)
                .catch(handleError);
        }

        function saveJobComplete(response){
            return response.data;
        }
    }

})();
<commit_msg>created a function to handle the deleteJob communication<commit_after>/**
 * Created by davewcampbell on 15-01-27.
 */

(function(){
    'use strict';

    angular
        .module('dorin')
        .factory('purgeService', purgeService);

    purgeService.$inject = ['$http'];

    function purgeService($http){

        var service = {
            addJob: addJob,
            deleteJob: deleteJob,
            getAllJobs: getAllJobs,
            getJobById: getJobById,
            saveJob: saveJob
        };

        return service;

        ///////

        function addJob(job){
            var url = '/api/jobs/';

            return $http.post(url, job)
                .then(addJobComplete)
                .catch(handleError);
        }

        function deleteJob(id){

            var url = '/api/jobs/' + id;

            return $http.delete(url)
                .then(deleteJobComplete)
                .catch(handleError);
        }

        function deleteJobComplete(response){
            return true;
        }

        function addJobComplete(response){
            return response.data;
        }

        function getAllJobs(){

            return $http.get('/api/jobs')
                .then(getAllJobsComplete)
                .catch(handleError);
        }

        function getAllJobsComplete(response){
            return response.data;
        }

        function getJobById(id){

            return $http.get('/api/jobs/' + id )
                .then(getJobByIdComplete)
                .catch(handleError);

        }

        function getJobByIdComplete(response){
            return response.data;
        }

        function handleError(err){
            //TODO: handle error
            console.log(err);
        }

        function saveJob(job){

            var url = '/api/jobs/' + job._id;

            return $http.put(url, job)
                .then(saveJobComplete)
                .catch(handleError);
        }

        function saveJobComplete(response){
            return response.data;
        }
    }

})();
<|endoftext|>


<commit_before>import get from 'lodash/get';
import set from 'lodash/fp/set';
import isEqual from 'lodash/isEqual';

// NOTE: This can be improved by creating a class
const runningRefetches = [];
function isRefetching(props) {
  return runningRefetches.find(refetchProps => isEqual(props, refetchProps));
}

function registerRefetch(props) {
  runningRefetches.push(props);
}

function markRefetchAsComplete(props) {
  const refetchIndex = runningRefetches.findIndex(refetchProps => isEqual(props, refetchProps));
  runningRefetches.splice(refetchIndex, 1);
}

// TODO: Test this
export default function fetchMoreResolver({ collectionName, mapTo, data } = {}) {
  return () => {
    const skip = (get(data, collectionName) || []).length;
    const refetchProps = {
      collectionName,
      mapTo,
      data,
      skip,
    };
    // Short-circuit the refetch if it's already running
    if (isRefetching(refetchProps)) return;
    registerRefetch(refetchProps);

    data.fetchMore({
      variables: { ...data.variables, skip },
      updateQuery: (previousResult, { fetchMoreResult }) => {
        markRefetchAsComplete(refetchProps);
        if (!fetchMoreResult) return previousResult;
        return set(mapTo || collectionName,
          [
            ...(get(previousResult, collectionName) || []),
            ...(get(fetchMoreResult, collectionName) || []),
          ],
          { ...previousResult },
        );
      },
    });
  };
}
<commit_msg>(SYS-4582) Fix Scrolling Skips and Duplicates (#749)<commit_after>import get from 'lodash/get';
import set from 'lodash/fp/set';

// TODO: this should be passed in and managed via state
let fetchLock;

export default function fetchMoreResolver({
  collectionName,
  // NOTE: not sure if mapTo is ever used
  // mapTo,
  data,
} = {}) {
  return () => {
    // Short-circuit the fetchMore if it's already running
    if (fetchLock) {
      return;
    }

    // lock to make sure this function doesn't run again until this thread is done
    fetchLock = true;

    // set the new offset to the number of items in the list
    const skip = (get(data, collectionName) || []).length;

    data.fetchMore({
      variables: { ...data.variables, skip },
      updateQuery: (previousResult, { fetchMoreResult }) => {
        // NOTE: not sure what this is doing
        // if (!fetchMoreResult) return previousResult;

        // combine previous data and fetchMore data
        const newResult = set(
          // NOTE: not sure if mapTo is ever used
          // mapTo || collectionName,
          collectionName,
          [
            ...(get(previousResult, collectionName) || []),
            ...(get(fetchMoreResult, collectionName) || []),
          ],
          { ...previousResult },
        );
        // TODO: the only problem with this may be if another call gets
        // made in between the unlock and the return. Not sure how to fix.
        fetchLock = false;
        return newResult;
      },
    });
  };
}
<|endoftext|>


<commit_before>'use strict'

const pWhilst = require('p-whilst')
const pMap = require('p-map')
const Entry = require('./entry')

class EntryIO {
  // Fetch log graphs in parallel
  static fetchParallel (ipfs, hashes, length, exclude = [], concurrency, timeout, onProgressCallback) {
    const fetchOne = (hash) => EntryIO.fetchAll(ipfs, hash, length, exclude, timeout, onProgressCallback)
    const concatArrays = (arr1, arr2) => arr1.concat(arr2)
    const flatten = (arr) => arr.reduce(concatArrays, [])
    return pMap(hashes, fetchOne, { concurrency: Math.max(concurrency || hashes.length, 1) })
      .then(flatten) // Flatten the results
  }

  /**
   * Fetch log entries sequentially
   *
   * @param {IPFS} [ipfs] An IPFS instance
   * @param {string} [hash] Multihash of the entry to fetch
   * @param {string} [parent] Parent of the node to be fetched
   * @param {Object} [all] Entries to skip
   * @param {Number} [amount=-1] How many entries to fetch
   * @param {Number} [depth=0] Current depth of the recursion
   * @param {function(hash, entry, parent, depth)} onProgressCallback
   * @returns {Promise<Array<Entry>>}
   */
  static fetchAll (ipfs, hashes, amount, exclude = [], timeout = null, onProgressCallback) {
    let result = []
    let cache = {}
    let loadingQueue = Array.isArray(hashes)
      ? hashes.slice()
      : [hashes]

    // Add a multihash to the loading queue
    const addToLoadingQueue = e => loadingQueue.push(e)

    // Add entries that we don't need to fetch to the "cache"
    var addToExcludeCache = e => (cache[e.hash] = e)
    exclude && exclude.length && exclude.forEach(addToExcludeCache)

    const shouldFetchMore = () => {
      return loadingQueue.length > 0 &&
          (result.length < amount || amount < 0)
    }

    const fetchEntry = () => {
      const hash = loadingQueue.shift()

      if (cache[hash]) {
        return Promise.resolve()
      }

      return new Promise((resolve, reject) => {
        // Resolve the promise after a timeout (if given) in order to
        // not get stuck loading a block that is unreachable
        const timer = timeout
          ? setTimeout(() => {
            console.warn(`Warning: Couldn't fetch entry '${hash}', request timed out (${timeout}ms)`)
            resolve()
          }, timeout)
          : null

        const addToResults = (entry) => {
          clearTimeout(timer)
          if (Entry.isEntry(entry)) {
            entry.next.forEach(addToLoadingQueue)
            result.push(entry)
            cache[hash] = entry
            if (onProgressCallback) {
              onProgressCallback(hash, entry, result.length)
            }
          }
        }

        // Load the entry
        Entry.fromMultihash(ipfs, hash)
          .then(addToResults)
          .then(resolve)
          .catch(reject)
      })
    }

    return pWhilst(shouldFetchMore, fetchEntry)
      .then(() => result)
  }
}

module.exports = EntryIO
<commit_msg>Make exclude array check in entry-io more explicit<commit_after>'use strict'

const pWhilst = require('p-whilst')
const pMap = require('p-map')
const Entry = require('./entry')

class EntryIO {
  // Fetch log graphs in parallel
  static fetchParallel (ipfs, hashes, length, exclude = [], concurrency, timeout, onProgressCallback) {
    const fetchOne = (hash) => EntryIO.fetchAll(ipfs, hash, length, exclude, timeout, onProgressCallback)
    const concatArrays = (arr1, arr2) => arr1.concat(arr2)
    const flatten = (arr) => arr.reduce(concatArrays, [])
    return pMap(hashes, fetchOne, { concurrency: Math.max(concurrency || hashes.length, 1) })
      .then(flatten) // Flatten the results
  }

  /**
   * Fetch log entries sequentially
   *
   * @param {IPFS} [ipfs] An IPFS instance
   * @param {string} [hash] Multihash of the entry to fetch
   * @param {string} [parent] Parent of the node to be fetched
   * @param {Object} [all] Entries to skip
   * @param {Number} [amount=-1] How many entries to fetch
   * @param {Number} [depth=0] Current depth of the recursion
   * @param {function(hash, entry, parent, depth)} onProgressCallback
   * @returns {Promise<Array<Entry>>}
   */
  static fetchAll (ipfs, hashes, amount, exclude = [], timeout = null, onProgressCallback) {
    let result = []
    let cache = {}
    let loadingQueue = Array.isArray(hashes)
      ? hashes.slice()
      : [hashes]

    // Add a multihash to the loading queue
    const addToLoadingQueue = e => loadingQueue.push(e)

    // Add entries that we don't need to fetch to the "cache"
    exclude = exclude && Array.isArray(exclude) ? exclude : []
    var addToExcludeCache = e => cache[e.hash] = e
    exclude.forEach(addToExcludeCache)

    const shouldFetchMore = () => {
      return loadingQueue.length > 0 &&
          (result.length < amount || amount < 0)
    }

    const fetchEntry = () => {
      const hash = loadingQueue.shift()

      if (cache[hash]) {
        return Promise.resolve()
      }

      return new Promise((resolve, reject) => {
        // Resolve the promise after a timeout (if given) in order to
        // not get stuck loading a block that is unreachable
        const timer = timeout
          ? setTimeout(() => {
            console.warn(`Warning: Couldn't fetch entry '${hash}', request timed out (${timeout}ms)`)
            resolve()
          }, timeout)
          : null

        const addToResults = (entry) => {
          clearTimeout(timer)
          if (Entry.isEntry(entry)) {
            entry.next.forEach(addToLoadingQueue)
            result.push(entry)
            cache[hash] = entry
            if (onProgressCallback) {
              onProgressCallback(hash, entry, result.length)
            }
          }
        }

        // Load the entry
        Entry.fromMultihash(ipfs, hash)
          .then(addToResults)
          .then(resolve)
          .catch(reject)
      })
    }

    return pWhilst(shouldFetchMore, fetchEntry)
      .then(() => result)
  }
}

module.exports = EntryIO
<|endoftext|>


<commit_before>import styled, { css } from 'styled-components';
import DayPicker from 'react-day-picker';
import baseTheme from '../../style/themes/base';

const StyledDayPicker = styled(DayPicker)`
${({ className }) => {
    const selector = className ? `&.${className.split(' ').join('.')}` : '&';

    return css`
      ${selector} {
      background-color: #fff;
      box-shadow: 0 5px 5px 0 rgba(0,20,29,0.2), 0 10px 10px 0 rgba(0,20,29,0.1);
      color: ${({ theme }) => theme.text.color};
      display: block;
      font-size: 14px;
      font-weight: 800;
      overflow: hidden;
      padding: 32px;
      position: absolute;
      text-align: center;
      user-select: none;

      * {
        box-sizing: border-box;
      }

      &:focus {
        outline: none;
      }

      abbr[title] {
        border: none;
        cursor: initial;
      }

      .DayPicker-wrapper {
        padding: 0;
      }

      .DayPicker-Month {
        margin: 0 0 2px;
      }

      .DayPicker-Body, .DayPicker-Week {
        width: 100%;
      }

      .DayPicker-Caption {
        height: 40px;
        line-height: 40px;
        font-size: 16px;
        font-weight: 800;

        >div {
          margin: 0 auto;
          width: 80%;
        }
      }

      .DayPicker-Day {
        background-color: #fff;
        cursor: pointer;
        padding: 10px;
        border: none;
        font-weight: 800;

        &:hover {
          background-color: #F2F5F6;
          color: ${({ theme }) => theme.text.color};
        }

        +* {
          border-left: 1px;
        }

        abbr {
          text-decoration: none;
        }
      }

      .DayPicker-Day {
        padding: 10px 12px;
        width: 40px;
      }

      .DayPicker-Day--today, .DayPicker-Day--today.DayPicker-Day--outside {
        font-weight: 700;
      }

      .DayPicker-Day--today {
        color: ${({ theme }) => theme.text.color};
        background-color: #ccd6db;
      }

      .DayPicker-Day--outside {
        color: ${({ theme }) => theme.disabled.disabled};

      }

      .DayPicker-Day--outside {
        background-color: #fff;
      }

      .DayPicker-Day--disabled, .DayPicker-Day--disabled:hover {
        background-color: #fff;
        cursor: default;
      }

      .DayPicker-Day--selected:not(.DayPicker-Day--disabled):not(.DayPicker-Day--outside) {
        background-color: ${({ theme }) => theme.colors.secondary};
        color: #fff;
        font-weight: 700;
      }
    }`;
  }}`;

StyledDayPicker.defaultProps = {
  theme: baseTheme
};

export default StyledDayPicker;
<commit_msg>FE-1223 - classic theme<commit_after>import styled, { css } from 'styled-components';
import DayPicker from 'react-day-picker';

const classicStyle = css`
background-color: #fff;
box-shadow: 0 5px 5px 0 rgba(0,20,29,.2), 0 10px 10px 0 rgba(0,20,29,.1);
color: rgba(0,0,0,.85);
display: block;
font-size: 14px;
font-weight: 700;
overflow: hidden;
padding: 10px;
position: absolute;
text-align: center;
user-select: none;

* {
  box-sizing: border-box;
}
&:focus {
  outline: none;
}
abbr[title] {
  border: none;
  cursor: initial;
}

.DayPicker-wrapper {
  padding: 0;
}

.DayPicker-Month {
  margin: 0 0 2px;
}

.DayPicker-Body, .DayPicker-Week {
  width: 100%;
}

.DayPicker-Day, .DayPicker-Weekday {
  padding: 10px 12px;
  border-color: #fff;
  border-style: solid;
  border-width: 0 3px 3px 3px;

  abbr {
    text-decoration: none;
  }
}

.DayPicker-Day {
  width: 14.28571%;
}

.DayPicker-Weekday {
  width: 14.28571%;
}

.DayPicker-Caption {
  background-color: #fff;
  color: $black-85;
  height: 40px;
  line-height: 40px;

  >div {
    margin: 0 auto;
    width: 80%;
  }
}

.DayPicker-Weekday {
  background-color: #fff;
  color: rgba(0,0,0,.6);
  font-size: 14px;
  font-weight: 700;
  text-align: center;
  text-transform: capitalize;
}

.DayPicker-Day {
  background-color: #fff;
  color: #255bc7;
  cursor: pointer;

  &:hover {
    background-color: mix(black, #255bc7, 20);
    color: #fff;
  }

  +* {
    border-left: 1px;
  }
}

.DayPicker-Day, .DayPicker-Weekday {
  padding: 10px 12px;
  border-color: #fff;
  border-style: solid;
  border-width: 0 3px 3px 3px;
}

.DayPicker-Day {
  width: 14.28571%;
}

.DayPicker-Weekday {
  width: 14.28571%;
}

.DayPicker-Day--selected, .DayPicker-Day--selected.DayPicker-Day--outside {
  background-color: #255bc7 !important;
  color: #fff;
  font-weight: 700;
}

.DayPicker-Day--today, .DayPicker-Day--today.DayPicker-Day--outside {
  background-color: #255bc7 !important;
  color: #fff;
  font-weight: 700;
}

.DayPicker-Day--outside {
  background-color: #fff;
  color: #8099a4;

  &:hover {
    background-color: #004b87;
    color: #fff;
  }
}

.DayPicker-Day--disabled, .DayPicker-Day--disabled:hover {
  background-color: #fff;
  color: #b3c2c8;
  cursor: default;
}
`;

const themeStyle = css`
background-color: #fff;
box-shadow: 0 5px 5px 0 rgba(0,20,29,0.2), 0 10px 10px 0 rgba(0,20,29,0.1);
color: ${({ theme }) => theme.text.color};
display: block;
font-size: 14px;
font-weight: 800;
overflow: hidden;
padding: 32px;
position: absolute;
text-align: center;
user-select: none;

* {
  box-sizing: border-box;
}

&:focus {
  outline: none;
}

abbr[title] {
  border: none;
  cursor: initial;
}

.DayPicker-wrapper {
  padding: 0;
}

.DayPicker-Month {
  margin: 0 0 2px;
}

.DayPicker-Body, .DayPicker-Week {
  width: 100%;
}

.DayPicker-Caption {
  height: 40px;
  line-height: 40px;
  font-size: 16px;
  font-weight: 800;

  >div {
    margin: 0 auto;
    width: 80%;
  }
}

.DayPicker-Day {
  background-color: #fff;
  cursor: pointer;
  padding: 10px;
  border: none;
  font-weight: 800;

  &:hover {
    background-color: #F2F5F6;
    color: ${({ theme }) => theme.text.color};
  }

  +* {
    border-left: 1px;
  }

  abbr {
    text-decoration: none;
  }
}

.DayPicker-Day {
  padding: 10px 12px;
  width: 40px;
}

.DayPicker-Day--today, .DayPicker-Day--today.DayPicker-Day--outside {
  font-weight: 700;
}

.DayPicker-Day--today {
  color: ${({ theme }) => theme.text.color};
  background-color: #ccd6db;
}

.DayPicker-Day--outside {
  color: ${({ theme }) => theme.disabled.disabled};

}

.DayPicker-Day--outside {
  background-color: #fff;
}

.DayPicker-Day--disabled, .DayPicker-Day--disabled:hover {
  background-color: #fff;
  cursor: default;
}

.DayPicker-Day--selected:not(.DayPicker-Day--disabled):not(.DayPicker-Day--outside) {
  background-color: ${({ theme }) => theme.colors.secondary};
  color: #fff;
  font-weight: 700;
}`;

const StyledDayPicker = styled(DayPicker)`
${({ className }) => {
    const selector = className ? `&.${className.split(' ').join('.')}` : '&';

    return css`
      ${selector} {
        ${({ theme }) => (theme.name === 'classic' ? classicStyle : themeStyle)}
      }`;
  }}`;

export default StyledDayPicker;
<|endoftext|>


<commit_before>import { select, mouse } from "d3-selection";
import 'd3-transition';
import { scaleLinear } from "d3-scale";
import { axisBottom, axisLeft } from "d3-axis";
import { rgb } from "d3-color";
import { area } from "d3-shape";
import { dataFont } from "../../globalStyles";
import { prettyString } from "../../util/stringHelpers";
import { unassigned_label } from "../../util/processFrequencies";
import { isColorByGenotype, decodeColorByGenotype } from "../../util/getGenotype";

/* C O N S T A N T S */
const opacity = 0.85;

export const areListsEqual = (a, b) => {
  if (a.length !== b.length) return false;
  return !a.filter((el, idx) => el !== b[idx]).length;
};

export const parseColorBy = (colorBy, colorOptions) => {
  if (colorOptions[colorBy]) {
    return colorOptions[colorBy].legendTitle;
  } else if (isColorByGenotype(colorBy)) {
    const genotype = decodeColorByGenotype(colorBy);
    return genotype.aa
      ? `Genotype at ${genotype.gene} pos ${genotype.positions.join(", ")}`
      : `Genotype at Nuc. ${genotype.positions.join(", ")}`;
  }
  return prettyString(colorBy);
};

const getOrderedCategories = (matrixCategories, colorScale) => {
  /* get the colorBy's in the same order as in the tree legend */
  const orderedCategories = colorScale.legendValues
    .filter((d) => d !== undefined)
    .reverse()
    .map((v) => v.toString());
  /* remove categories that (for whatever reason) are in the legend but aren't in the matrix */
  for (let i = orderedCategories.length - 1; i >= 0; --i) {
    if (matrixCategories.indexOf(orderedCategories[i]) === -1) {
      orderedCategories.splice(i, 1);
    }
  }
  /* add in categories that (for whatever reason) aren't in the legend */
  if (matrixCategories.length > orderedCategories.length) {
    matrixCategories.forEach((v) => {
      if (orderedCategories.indexOf(v) === -1) {
        orderedCategories.push(v);
      }
    });
  }
  return orderedCategories;
};

export const calcXScale = (chartGeom, pivots, ticks) => {
  const x = scaleLinear()
    .domain([pivots[0], pivots[pivots.length - 1]])
    .range([chartGeom.spaceLeft, chartGeom.width - chartGeom.spaceRight]);
  return {x, numTicksX: ticks.length};
};

export const calcYScale = (chartGeom, maxY) => {
  const y = scaleLinear()
    .domain([0, maxY])
    .range([chartGeom.height - chartGeom.spaceBottom, chartGeom.spaceTop]);
  return {y, numTicksY: 5};
};

const removeXAxis = (svg) => {
  svg.selectAll(".x.axis").remove();
};

const removeYAxis = (svg) => {
  svg.selectAll(".y.axis").remove();
};

const removeProjectionInfo = (svg) => {
  svg.selectAll(".projection-pivot").remove();
  svg.selectAll(".projection-text").remove();
};

export const drawXAxis = (svg, chartGeom, scales) => {
  removeXAxis(svg);
  svg.append("g")
    .attr("class", "x axis")
    .attr("transform", `translate(0,${chartGeom.height - chartGeom.spaceBottom})`)
    .style("font-family", dataFont)
    .style("font-size", "12px")
    .call(axisBottom(scales.x).ticks(scales.numTicksX, ".1f"));
};

export const drawYAxis = (svg, chartGeom, scales) => {
  removeYAxis(svg);
  svg.append("g")
    .attr("class", "y axis")
    .attr("transform", `translate(${chartGeom.spaceLeft},0)`)
    .style("font-family", dataFont)
    .style("font-size", "12px")
    .call(axisLeft(scales.y).ticks(scales.numTicksY));
};

export const drawProjectionInfo = (svg, scales, projection_pivot) => {
  if (projection_pivot) {

    removeProjectionInfo(svg);

    svg.append("g")
      .attr("class", "projection-pivot")
      .append("line")
      .attr("x1", scales.x(parseFloat(projection_pivot)))
      .attr("x2", scales.x(parseFloat(projection_pivot)))
      .attr("y1", scales.y(1))
      .attr("y2", scales.y(0))
      .style("visibility", "visible")
      .style("stroke", "rgba(55,55,55,0.9)")
      .style("stroke-width", "2")
      .style("stroke-dasharray", "4 4");

    const midPoint = 0.5 * (scales.x(parseFloat(projection_pivot)) + scales.x.range()[1]);
    svg.append("g")
      .attr("class", "projection-text")
      .append("text")
      .attr("x", midPoint)
      .attr("y", scales.y(1) - 3)
      .style("pointer-events", "none")
      .style("fill", "#555")
      .style("font-family", dataFont)
      .style("font-size", 12)
      .style("alignment-baseline", "bottom")
      .style("text-anchor", "middle")
      .text("Projection");

  }
};

const turnMatrixIntoSeries = (categories, nPivots, matrix) => {
  /*
  WHAT IS A SERIES?
  this is the data structure demanded by d3 for a stream graph.
  it is often produced by the d3.stack function - see https://github.com/d3/d3-shape/blob/master/README.md#_stack
  but it's faster to create this ourselves.

  THIS IS THE STRUCTURE:
    [x1, x2, ... xn] where n is the number of categories
      xi = [y1, y2, ..., ym] where m is the number of pivots
        yi = [z1, z2]: the (y0, y1) values of the categorie at that pivot point.

  TO DO:
  this should / could be in the reducer. But what if we want to re-order things?!?!
  */
  const series = [];
  for (let i = 0; i < categories.length; i++) {
    const x = [];
    for (let j = 0; j < nPivots; j++) {
      if (i === 0) {
        x.push([0, matrix[categories[i]][j]]);
      } else {
        const prevY1 = series[i - 1][j][1];
        x.push([prevY1, matrix[categories[i]][j] + prevY1]);
      }
    }
    series.push(x);
  }
  return series;
};

const getMeaningfulLabels = (categories, colorScale) => {
  if (colorScale.continuous) {
    return categories.map((name) => name === unassigned_label ?
      unassigned_label :
      `${colorScale.legendBounds[name][0].toFixed(2)} - ${colorScale.legendBounds[name][1].toFixed(2)}`
    );
  }
  return categories.slice();
};

export const removeStream = (svg) => {
  svg.selectAll("path").remove();
  svg.selectAll("line").remove();
  svg.selectAll("text").remove();
};

const generateColorScaleD3 = (categories, colorScale) => (d, i) =>
  categories[i] === unassigned_label ? "rgb(190, 190, 190)" : rgb(colorScale.scale(categories[i])).toString();

function handleMouseOver() {
  select(this).attr("opacity", 1);
}

function handleMouseOut() {
  select(this).attr("opacity", opacity);
  select("#freqinfo").style("visibility", "hidden");
  select("#vline").style("visibility", "hidden");
}

/* returns [[xval, yval], [xval, yval], ...] order: that of {series} */
const calcBestXYPositionsForLabels = (series, pivots, scales, lookahead) => series.map((d) => {
  const maxY = scales.y.domain()[1];
  const displayThresh = 0.15 * maxY;
  for (let pivotIdx = 0; pivotIdx < d.length - lookahead; pivotIdx++) {
    const nextIdx = pivotIdx + lookahead;
    if (d[pivotIdx][1] - d[pivotIdx][0] > displayThresh && d[nextIdx][1] - d[nextIdx][0] > displayThresh) {
      return [
        scales.x(pivots[pivotIdx + 1]),
        (scales.y((d[pivotIdx][1] + d[pivotIdx][0]) / 2) + scales.y((d[nextIdx][1] + d[nextIdx][0]) / 2)) / 2
      ];
    }
  }
  return [undefined, undefined]; /* don't display text! */
});

const drawLabelsOverStream = (svgStreamGroup, series, pivots, labels, scales) => {
  const xyPos = calcBestXYPositionsForLabels(series, pivots, scales, 3);
  svgStreamGroup.selectAll(".streamLabels")
    .data(labels)
    .enter()
    .append("text")
    .attr("x", (d, i) => xyPos[i][0])
    .attr("y", (d, i) => xyPos[i][1])
    .style("pointer-events", "none")
    .style("fill", "white")
    .style("font-family", dataFont)
    .style("font-size", 14)
    .style("alignment-baseline", "middle")
    .text((d, i) => xyPos[i][0] ? prettyString(d) : "");
};

const calcMaxYValue = (series) => {
  return series[series.length - 1].reduce((curMax, el) => Math.max(curMax, el[1]), 0);
};

export const processMatrix = ({matrix, pivots, colorScale}) => {
  const categories = getOrderedCategories(Object.keys(matrix), colorScale);
  const series = turnMatrixIntoSeries(categories, pivots.length, matrix);
  const maxY = calcMaxYValue(series);
  return {categories, series, maxY};
};

export const drawStream = (
  svgStreamGroup, scales, {categories, series}, {colorBy, colorScale, colorOptions, pivots, projection_pivot}
) => {
  removeStream(svgStreamGroup);
  const colourer = generateColorScaleD3(categories, colorScale);
  const labels = getMeaningfulLabels(categories, colorScale);

  /* https://github.com/d3/d3-shape/blob/master/README.md#areas */
  const areaObj = area()
    .x((d, i) => scales.x(pivots[i]))
    .y0((d) => scales.y(d[0]))
    .y1((d) => scales.y(d[1]));

  /* define handleMouseMove inside drawStream so it can access the provided arguments */
  function handleMouseMove(d, i) {
    const [mousex] = mouse(this); // [x, y] x starts from left, y starts from top
    /* what's the closest pivot? */
    const date = scales.x.invert(mousex);
    const pivotIdx = pivots.reduce((closestIdx, val, idx, arr) => Math.abs(val - date) < Math.abs(arr[closestIdx] - date) ? idx : closestIdx, 0);
    const freqVal = Math.round((d[pivotIdx][1] - d[pivotIdx][0]) * 100) + "%";
    const xValueOfPivot = scales.x(pivots[pivotIdx]);
    const y1ValueOfPivot = scales.y(d[pivotIdx][1]);
    const y2ValueOfPivot = scales.y(d[pivotIdx][0]);

    select("#vline")
      .style("visibility", "visible")
      .attr("x1", xValueOfPivot)
      .attr("x2", xValueOfPivot)
      .attr("y1", y1ValueOfPivot)
      .attr("y2", y2ValueOfPivot);

    const left = xValueOfPivot > 0.5 * scales.x.range()[1] ? "" : `${xValueOfPivot + 25}px`;
    const right = xValueOfPivot > 0.5 * scales.x.range()[1] ? `${scales.x.range()[1] - xValueOfPivot + 25}px` : "";
    const top = y1ValueOfPivot > 0.5 * scales.y(0) ? `${scales.y(0) - 50}px` : `${y1ValueOfPivot + 25}px`;

    let frequencyText = "Frequency";
    if (projection_pivot) {
      if (pivots[pivotIdx] > projection_pivot) {
        frequencyText = "Projected frequency";
      }
    }

    select("#freqinfo")
      .style("left", left)
      .style("right", right)
      .style("top", top)
      .style("padding-left", "10px")
      .style("padding-right", "10px")
      .style("padding-top", "0px")
      .style("padding-bottom", "0px")
      .style("visibility", "visible")
      .style("background-color", "rgba(55,55,55,0.9)")
      .style("color", "white")
      .style("font-family", dataFont)
      .style("font-size", 18)
      .style("line-height", 1)
      .style("font-weight", 300)
      .html(`
${parseColorBy(colorBy, colorOptions)}: ${prettyString(labels[i])}

Time point: ${pivots[pivotIdx]}

${frequencyText}: ${freqVal}
`);
  }

  /* the streams */
  svgStreamGroup.selectAll(".stream")
    .data(series)
    .enter()
    .append("path")
    .attr("d", areaObj)
    .attr("fill", colourer)
    .attr("opacity", opacity)
    .on("mouseover", handleMouseOver)
    .on("mouseout", handleMouseOut)
    .on("mousemove", handleMouseMove);

  /* the vertical line to indicate the highlighted frequency interval */
  svgStreamGroup.append("line")
    .attr("id", "vline")
    .style("visibility", "hidden")
    .style("pointer-events", "none")
    .style("stroke", "rgba(55,55,55,0.9)")
    .style("stroke-width", 4);

  drawLabelsOverStream(svgStreamGroup, series, pivots, labels, scales);
};
<commit_msg>Format frequency y axis as percentage<commit_after>import { select, mouse } from "d3-selection";
import 'd3-transition';
import { scaleLinear } from "d3-scale";
import { axisBottom, axisLeft } from "d3-axis";
import { rgb } from "d3-color";
import { area } from "d3-shape";
import { format } from "d3-format";
import { dataFont } from "../../globalStyles";
import { prettyString } from "../../util/stringHelpers";
import { unassigned_label } from "../../util/processFrequencies";
import { isColorByGenotype, decodeColorByGenotype } from "../../util/getGenotype";

/* C O N S T A N T S */
const opacity = 0.85;

export const areListsEqual = (a, b) => {
  if (a.length !== b.length) return false;
  return !a.filter((el, idx) => el !== b[idx]).length;
};

export const parseColorBy = (colorBy, colorOptions) => {
  if (colorOptions[colorBy]) {
    return colorOptions[colorBy].legendTitle;
  } else if (isColorByGenotype(colorBy)) {
    const genotype = decodeColorByGenotype(colorBy);
    return genotype.aa
      ? `Genotype at ${genotype.gene} pos ${genotype.positions.join(", ")}`
      : `Genotype at Nuc. ${genotype.positions.join(", ")}`;
  }
  return prettyString(colorBy);
};

const getOrderedCategories = (matrixCategories, colorScale) => {
  /* get the colorBy's in the same order as in the tree legend */
  const orderedCategories = colorScale.legendValues
    .filter((d) => d !== undefined)
    .reverse()
    .map((v) => v.toString());
  /* remove categories that (for whatever reason) are in the legend but aren't in the matrix */
  for (let i = orderedCategories.length - 1; i >= 0; --i) {
    if (matrixCategories.indexOf(orderedCategories[i]) === -1) {
      orderedCategories.splice(i, 1);
    }
  }
  /* add in categories that (for whatever reason) aren't in the legend */
  if (matrixCategories.length > orderedCategories.length) {
    matrixCategories.forEach((v) => {
      if (orderedCategories.indexOf(v) === -1) {
        orderedCategories.push(v);
      }
    });
  }
  return orderedCategories;
};

export const calcXScale = (chartGeom, pivots, ticks) => {
  const x = scaleLinear()
    .domain([pivots[0], pivots[pivots.length - 1]])
    .range([chartGeom.spaceLeft, chartGeom.width - chartGeom.spaceRight]);
  return {x, numTicksX: ticks.length};
};

export const calcYScale = (chartGeom, maxY) => {
  const y = scaleLinear()
    .domain([0, maxY])
    .range([chartGeom.height - chartGeom.spaceBottom, chartGeom.spaceTop]);
  return {y, numTicksY: 5};
};

const removeXAxis = (svg) => {
  svg.selectAll(".x.axis").remove();
};

const removeYAxis = (svg) => {
  svg.selectAll(".y.axis").remove();
};

const removeProjectionInfo = (svg) => {
  svg.selectAll(".projection-pivot").remove();
  svg.selectAll(".projection-text").remove();
};

export const drawXAxis = (svg, chartGeom, scales) => {
  removeXAxis(svg);
  svg.append("g")
    .attr("class", "x axis")
    .attr("transform", `translate(0,${chartGeom.height - chartGeom.spaceBottom})`)
    .style("font-family", dataFont)
    .style("font-size", "12px")
    .call(axisBottom(scales.x).ticks(scales.numTicksX, ".1f"));
};

export const drawYAxis = (svg, chartGeom, scales) => {
  removeYAxis(svg);
  const formatPercent = format(".0%");
  svg.append("g")
    .attr("class", "y axis")
    .attr("transform", `translate(${chartGeom.spaceLeft},0)`)
    .style("font-family", dataFont)
    .style("font-size", "12px")
    .call(axisLeft(scales.y).ticks(scales.numTicksY).tickFormat(formatPercent));
};

export const drawProjectionInfo = (svg, scales, projection_pivot) => {
  if (projection_pivot) {

    removeProjectionInfo(svg);

    svg.append("g")
      .attr("class", "projection-pivot")
      .append("line")
      .attr("x1", scales.x(parseFloat(projection_pivot)))
      .attr("x2", scales.x(parseFloat(projection_pivot)))
      .attr("y1", scales.y(1))
      .attr("y2", scales.y(0))
      .style("visibility", "visible")
      .style("stroke", "rgba(55,55,55,0.9)")
      .style("stroke-width", "2")
      .style("stroke-dasharray", "4 4");

    const midPoint = 0.5 * (scales.x(parseFloat(projection_pivot)) + scales.x.range()[1]);
    svg.append("g")
      .attr("class", "projection-text")
      .append("text")
      .attr("x", midPoint)
      .attr("y", scales.y(1) - 3)
      .style("pointer-events", "none")
      .style("fill", "#555")
      .style("font-family", dataFont)
      .style("font-size", 12)
      .style("alignment-baseline", "bottom")
      .style("text-anchor", "middle")
      .text("Projection");

  }
};

const turnMatrixIntoSeries = (categories, nPivots, matrix) => {
  /*
  WHAT IS A SERIES?
  this is the data structure demanded by d3 for a stream graph.
  it is often produced by the d3.stack function - see https://github.com/d3/d3-shape/blob/master/README.md#_stack
  but it's faster to create this ourselves.

  THIS IS THE STRUCTURE:
    [x1, x2, ... xn] where n is the number of categories
      xi = [y1, y2, ..., ym] where m is the number of pivots
        yi = [z1, z2]: the (y0, y1) values of the categorie at that pivot point.

  TO DO:
  this should / could be in the reducer. But what if we want to re-order things?!?!
  */
  const series = [];
  for (let i = 0; i < categories.length; i++) {
    const x = [];
    for (let j = 0; j < nPivots; j++) {
      if (i === 0) {
        x.push([0, matrix[categories[i]][j]]);
      } else {
        const prevY1 = series[i - 1][j][1];
        x.push([prevY1, matrix[categories[i]][j] + prevY1]);
      }
    }
    series.push(x);
  }
  return series;
};

const getMeaningfulLabels = (categories, colorScale) => {
  if (colorScale.continuous) {
    return categories.map((name) => name === unassigned_label ?
      unassigned_label :
      `${colorScale.legendBounds[name][0].toFixed(2)} - ${colorScale.legendBounds[name][1].toFixed(2)}`
    );
  }
  return categories.slice();
};

export const removeStream = (svg) => {
  svg.selectAll("path").remove();
  svg.selectAll("line").remove();
  svg.selectAll("text").remove();
};

const generateColorScaleD3 = (categories, colorScale) => (d, i) =>
  categories[i] === unassigned_label ? "rgb(190, 190, 190)" : rgb(colorScale.scale(categories[i])).toString();

function handleMouseOver() {
  select(this).attr("opacity", 1);
}

function handleMouseOut() {
  select(this).attr("opacity", opacity);
  select("#freqinfo").style("visibility", "hidden");
  select("#vline").style("visibility", "hidden");
}

/* returns [[xval, yval], [xval, yval], ...] order: that of {series} */
const calcBestXYPositionsForLabels = (series, pivots, scales, lookahead) => series.map((d) => {
  const maxY = scales.y.domain()[1];
  const displayThresh = 0.15 * maxY;
  for (let pivotIdx = 0; pivotIdx < d.length - lookahead; pivotIdx++) {
    const nextIdx = pivotIdx + lookahead;
    if (d[pivotIdx][1] - d[pivotIdx][0] > displayThresh && d[nextIdx][1] - d[nextIdx][0] > displayThresh) {
      return [
        scales.x(pivots[pivotIdx + 1]),
        (scales.y((d[pivotIdx][1] + d[pivotIdx][0]) / 2) + scales.y((d[nextIdx][1] + d[nextIdx][0]) / 2)) / 2
      ];
    }
  }
  return [undefined, undefined]; /* don't display text! */
});

const drawLabelsOverStream = (svgStreamGroup, series, pivots, labels, scales) => {
  const xyPos = calcBestXYPositionsForLabels(series, pivots, scales, 3);
  svgStreamGroup.selectAll(".streamLabels")
    .data(labels)
    .enter()
    .append("text")
    .attr("x", (d, i) => xyPos[i][0])
    .attr("y", (d, i) => xyPos[i][1])
    .style("pointer-events", "none")
    .style("fill", "white")
    .style("font-family", dataFont)
    .style("font-size", 14)
    .style("alignment-baseline", "middle")
    .text((d, i) => xyPos[i][0] ? prettyString(d) : "");
};

const calcMaxYValue = (series) => {
  return series[series.length - 1].reduce((curMax, el) => Math.max(curMax, el[1]), 0);
};

export const processMatrix = ({matrix, pivots, colorScale}) => {
  const categories = getOrderedCategories(Object.keys(matrix), colorScale);
  const series = turnMatrixIntoSeries(categories, pivots.length, matrix);
  const maxY = calcMaxYValue(series);
  return {categories, series, maxY};
};

export const drawStream = (
  svgStreamGroup, scales, {categories, series}, {colorBy, colorScale, colorOptions, pivots, projection_pivot}
) => {
  removeStream(svgStreamGroup);
  const colourer = generateColorScaleD3(categories, colorScale);
  const labels = getMeaningfulLabels(categories, colorScale);

  /* https://github.com/d3/d3-shape/blob/master/README.md#areas */
  const areaObj = area()
    .x((d, i) => scales.x(pivots[i]))
    .y0((d) => scales.y(d[0]))
    .y1((d) => scales.y(d[1]));

  /* define handleMouseMove inside drawStream so it can access the provided arguments */
  function handleMouseMove(d, i) {
    const [mousex] = mouse(this); // [x, y] x starts from left, y starts from top
    /* what's the closest pivot? */
    const date = scales.x.invert(mousex);
    const pivotIdx = pivots.reduce((closestIdx, val, idx, arr) => Math.abs(val - date) < Math.abs(arr[closestIdx] - date) ? idx : closestIdx, 0);
    const freqVal = Math.round((d[pivotIdx][1] - d[pivotIdx][0]) * 100) + "%";
    const xValueOfPivot = scales.x(pivots[pivotIdx]);
    const y1ValueOfPivot = scales.y(d[pivotIdx][1]);
    const y2ValueOfPivot = scales.y(d[pivotIdx][0]);

    select("#vline")
      .style("visibility", "visible")
      .attr("x1", xValueOfPivot)
      .attr("x2", xValueOfPivot)
      .attr("y1", y1ValueOfPivot)
      .attr("y2", y2ValueOfPivot);

    const left = xValueOfPivot > 0.5 * scales.x.range()[1] ? "" : `${xValueOfPivot + 25}px`;
    const right = xValueOfPivot > 0.5 * scales.x.range()[1] ? `${scales.x.range()[1] - xValueOfPivot + 25}px` : "";
    const top = y1ValueOfPivot > 0.5 * scales.y(0) ? `${scales.y(0) - 50}px` : `${y1ValueOfPivot + 25}px`;

    let frequencyText = "Frequency";
    if (projection_pivot) {
      if (pivots[pivotIdx] > projection_pivot) {
        frequencyText = "Projected frequency";
      }
    }

    select("#freqinfo")
      .style("left", left)
      .style("right", right)
      .style("top", top)
      .style("padding-left", "10px")
      .style("padding-right", "10px")
      .style("padding-top", "0px")
      .style("padding-bottom", "0px")
      .style("visibility", "visible")
      .style("background-color", "rgba(55,55,55,0.9)")
      .style("color", "white")
      .style("font-family", dataFont)
      .style("font-size", 18)
      .style("line-height", 1)
      .style("font-weight", 300)
      .html(`
${parseColorBy(colorBy, colorOptions)}: ${prettyString(labels[i])}

Time point: ${pivots[pivotIdx]}

${frequencyText}: ${freqVal}
`);
  }

  /* the streams */
  svgStreamGroup.selectAll(".stream")
    .data(series)
    .enter()
    .append("path")
    .attr("d", areaObj)
    .attr("fill", colourer)
    .attr("opacity", opacity)
    .on("mouseover", handleMouseOver)
    .on("mouseout", handleMouseOut)
    .on("mousemove", handleMouseMove);

  /* the vertical line to indicate the highlighted frequency interval */
  svgStreamGroup.append("line")
    .attr("id", "vline")
    .style("visibility", "hidden")
    .style("pointer-events", "none")
    .style("stroke", "rgba(55,55,55,0.9)")
    .style("stroke-width", 4);

  drawLabelsOverStream(svgStreamGroup, series, pivots, labels, scales);
};
<|endoftext|>


<commit_before>// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
'use strict';

foam.CLASS({
  package: 'org.chromium.webidl',
  name: 'ParserRunner',
  extends: 'org.chromium.webidl.PipelineRunner',

  requires: ['org.chromium.webidl.BaseParser'],

  documentation: 'Runnable Box that parsers IDLFileContents into ASTs.',

  properties: [
    {
      class: 'Class',
      documentation: 'Class of the parser used to parse the IDL files.',
      name: 'parserType',
      value: 'org.chromium.webidl.Parser',
    },
    {
      class: 'String',
      name: 'ioRelationshipType',
      documentation: 'The n:m relationship type of input-to-output.',
      value: '1:*',
    },
    {
      class: 'Class',
      documentation: 'Type of input parameter of run().',
      name: 'inputType',
      value: 'org.chromium.webidl.BaseIDLFile',
    },
    {
      class: 'Class',
      documentation: 'Type of output values produced by run().',
      name: 'outputType',
      value: 'org.chromium.webidl.ast.Definition',
    },
    {
      documentation: 'An instance of the parser class.',
      name: 'parser_',
      factory: function() {
        return this.parserType.create();
      },
    },
  ],

  methods: [
    function init() {
      foam.assert(this.BaseParser.isInstance(this.parser_),
          'ParserRunner: Expected to receive a valid parser class.');
      this.SUPER();
    },
    function run(file) {
      if (this.validateMessage(file)) return;

      // Prepare metadata to be injected by parser.
      var sourceId = file.cls_.create({id: file.id});
      var results = this.parser_.parseString(file.contents, sourceId);

      // Determine if parse of entire file was successful.
      if (!Array.isArray(results.value) ||
          file.contents.length !== results.pos) {
        var errMsg = this.fmtErrorMsg(
            `Incomplete parse on file. Source ID: ${sourceId}`, 'run');
        this.error(new Error(errMsg));
      } else {
        var asts = results.value;
        asts.forEach(function(ast) {
          this.output(ast);
        }.bind(this));
      }
    }
  ],
});
<commit_msg>Updating parser error message to print sources correctly.<commit_after>// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
'use strict';

foam.CLASS({
  package: 'org.chromium.webidl',
  name: 'ParserRunner',
  extends: 'org.chromium.webidl.PipelineRunner',

  requires: ['org.chromium.webidl.BaseParser'],

  documentation: 'Runnable Box that parsers IDLFileContents into ASTs.',

  properties: [
    {
      class: 'Class',
      documentation: 'Class of the parser used to parse the IDL files.',
      name: 'parserType',
      value: 'org.chromium.webidl.Parser',
    },
    {
      class: 'String',
      name: 'ioRelationshipType',
      documentation: 'The n:m relationship type of input-to-output.',
      value: '1:*',
    },
    {
      class: 'Class',
      documentation: 'Type of input parameter of run().',
      name: 'inputType',
      value: 'org.chromium.webidl.BaseIDLFile',
    },
    {
      class: 'Class',
      documentation: 'Type of output values produced by run().',
      name: 'outputType',
      value: 'org.chromium.webidl.ast.Definition',
    },
    {
      documentation: 'An instance of the parser class.',
      name: 'parser_',
      factory: function() {
        return this.parserType.create();
      },
    },
  ],

  methods: [
    function init() {
      foam.assert(this.BaseParser.isInstance(this.parser_),
          'ParserRunner: Expected to receive a valid parser class.');
      this.SUPER();
    },
    function run(file) {
      if (this.validateMessage(file)) return;

      // Prepare metadata to be injected by parser.
      var sourceId = file.cls_.create({id: file.id});
      var results = this.parser_.parseString(file.contents, sourceId);

      // Determine if parse of entire file was successful.
      if (!Array.isArray(results.value) ||
          file.contents.length !== results.pos) {
        var errMsg = this.fmtErrorMsg(
            `Incomplete parse on file.
            Source: ${foam.json.Pretty.stringify(sourceId)}`,
            'run');
        this.error(new Error(errMsg));
      } else {
        var asts = results.value;
        asts.forEach(function(ast) {
          this.output(ast);
        }.bind(this));
      }
    }
  ],
});
<|endoftext|>


<commit_before>"use strict"

const EventEmitter = require( 'events' ).EventEmitter
const util = require( 'util' )
const rethinkdb = require( 'rethinkdb' )
const Connection = require( './connection' )
const TableManager = require( './table-manager' )
const dataTransform = require( './transform-data' )
const pckg = require( '../package.json' )
const PRIMARY_KEY = require( './primary-key')

class Connector extends EventEmitter {

  /**
   * Connects deepstream to a rethinkdb. RethinksDB is a great fit for deepstream due to its realtime capabilities.
   *
   * Similar to other storage connectors (e.g. MongoDB), this connector supports saving records to multiple tables.
   * In order to do this, specify a splitChar, e.g. '/' and use it in your record names. Naming your record
   *
   * user/i4vcg5j1-16n1qrnziuog
   *
   * for instance will create a user table and store it in it. This will allow for more sophisticated queries as
   * well as speed up read operations since there are less entries to look through
   *
   * @param {Object} options rethinkdb driver options. See rethinkdb.com/api/javascript/#connect
   *
   * e.g.
   *
   * {
   *     host: 'localhost',
   *     port: 28015,
   *     authKey: 'someString'
   *     database: 'deepstream',
   *     defaultTable: 'deepstream_records',
   *     splitChar: '/'
   * }
   *
   * Please note the three additional, optional keys:
   *
   * database   specifies which database to use. Defaults to 'deepstream'
   * defaultTable specifies which table records will be stored in that don't specify a table. Defaults to deepstream_records
   * splitChar   specifies a character that separates the record's id from the table it should be stored in. defaults to null
   *
   * @constructor
   */
  constructor( options ) {
    super()
    this.isReady = false
    this.name = pckg.name
    this.version = pckg.version
    this._checkOptions( options )
    this._options = options
    this._connection = new Connection( options, this._onConnection.bind( this ) )
    this._tableManager = new TableManager( this._connection )
    this._defaultTable = options.defaultTable || 'deepstream_records'
    this._splitChar = options.splitChar || null
    this._primaryKey = options.primaryKey || PRIMARY_KEY
  }

  /**
   * Writes a value to the database. If the specified table doesn't exist yet, it will be created
   * before the write is excecuted. If a table creation is already in progress, create table will
   * only add the method to its array of callbacks
   *
   * @param {String}   key
   * @param {Object}   value
   * @param {Function} callback Will be called with null for successful set operations or with an error message string
   *
   * @public
   * @returns {void}
   */
  set( key, value, callback ) {
    const params = this._getParams( key )
    const entry  = dataTransform.transformValueForStorage( value )
    const insert = this._insert.bind( this, params, entry, callback )

    if( this._tableManager.hasTable( params.table ) ) {
      insert()
    } else {
      this._tableManager.createTable( params.table, this._primaryKey, insert )
    }
  }

  /**
   * Retrieves a value from the cache
   *
   * @param {String}   key
   * @param {Function} callback Will be called with null and the stored object
   *                            for successful operations or with an error message string
   *
   * @public
   * @returns {void}
   */
  get( key, callback ) {
    const params = this._getParams( key )

    if( this._tableManager.hasTable( params.table ) ) {
      rethinkdb.table( params.table ).get( params.id ).run( this._connection.get(), ( error, entry ) => {
        if( entry ) {
          delete entry[ this._primaryKey ]
          entry = dataTransform.transformValueFromStorage( entry )
        }
        callback( error, entry )
      } )
    } else {
      callback( null, null )
    }
  }

  /**
   * Deletes an entry from the cache.
   *
   * @param   {String}   key
   * @param   {Function} callback Will be called with null for successful deletions or with
   *                     an error message string
   *
   * @public
   * @returns {void}
   */
  delete( key, callback ) {
    const params = this._getParams( key )

    if( this._tableManager.hasTable( params.table ) ) {
      rethinkdb.table( params.table ).get( params.id ).delete().run( this._connection.get(), callback )
    } else {
      callback( new Error( 'Table \'' + params.table + '\' does not exist' ) )
    }
  }

  /**
   * Callback for established connections
   *
   * @param   {Error} error
   *
   * @private
   * @returns {void}
   */
  _onConnection( error ) {
    if( error ) {
      this.emit( 'error', error )
    } else {
      this._tableManager.refreshTables( () => {
        this.isReady = true
        this.emit( 'ready' )
      } )
    }
  }

  /**
   * Parses the provided record name and returns an object
   * containing a table name and a record name
   *
   * @param   {String} key the name of the record
   *
   * @private
   * @returns {Object} params
   */
  _getParams( key ) {
    const parts = key.split( this._splitChar )

    if( parts.length === 2 ) {
      return { table: parts[ 0 ], id: parts[ 1 ] }
    } else {
      return { table: this._defaultTable, id: key }
    }
  }

  /**
   * Augments a value with a primary key and writes it to the database
   *
   * @param   {Object} params Map in the format { table: String, id: String }
   * @param   {Object} value The value that will be written
   * @param   {Function} callback called with error or null
   *
   * @private
   * @returns {void}
   */
  _insert( params, value, callback ) {
    value[ this._primaryKey ] = params.id

    rethinkdb
      .table( params.table )
      .insert( value, { returnChanges: false, conflict: 'replace' } )
      .run( this._connection.get(), callback )
  }

  /**
   * Makes sure that the options object contains all mandatory
   * settings
   *
   * @param   {Object} options
   *
   * @private
   * @returns {void}
   */
  _checkOptions( options ) {
    if( typeof options.host !== 'string' ) {
      throw new Error( 'Missing option host' )
    }
    if( typeof options.port !== 'number' ) {
      throw new Error( 'Missing option port' )
    }
  }
 }
module.exports = Connector<commit_msg>Use isNaN instead of typeof<commit_after>"use strict"

const EventEmitter = require( 'events' ).EventEmitter
const util = require( 'util' )
const rethinkdb = require( 'rethinkdb' )
const Connection = require( './connection' )
const TableManager = require( './table-manager' )
const dataTransform = require( './transform-data' )
const pckg = require( '../package.json' )
const PRIMARY_KEY = require( './primary-key')

class Connector extends EventEmitter {

  /**
   * Connects deepstream to a rethinkdb. RethinksDB is a great fit for deepstream due to its realtime capabilities.
   *
   * Similar to other storage connectors (e.g. MongoDB), this connector supports saving records to multiple tables.
   * In order to do this, specify a splitChar, e.g. '/' and use it in your record names. Naming your record
   *
   * user/i4vcg5j1-16n1qrnziuog
   *
   * for instance will create a user table and store it in it. This will allow for more sophisticated queries as
   * well as speed up read operations since there are less entries to look through
   *
   * @param {Object} options rethinkdb driver options. See rethinkdb.com/api/javascript/#connect
   *
   * e.g.
   *
   * {
   *     host: 'localhost',
   *     port: 28015,
   *     authKey: 'someString'
   *     database: 'deepstream',
   *     defaultTable: 'deepstream_records',
   *     splitChar: '/'
   * }
   *
   * Please note the three additional, optional keys:
   *
   * database   specifies which database to use. Defaults to 'deepstream'
   * defaultTable specifies which table records will be stored in that don't specify a table. Defaults to deepstream_records
   * splitChar   specifies a character that separates the record's id from the table it should be stored in. defaults to null
   *
   * @constructor
   */
  constructor( options ) {
    super()
    this.isReady = false
    this.name = pckg.name
    this.version = pckg.version
    this._checkOptions( options )
    this._options = options
    this._connection = new Connection( options, this._onConnection.bind( this ) )
    this._tableManager = new TableManager( this._connection )
    this._defaultTable = options.defaultTable || 'deepstream_records'
    this._splitChar = options.splitChar || null
    this._primaryKey = options.primaryKey || PRIMARY_KEY
  }

  /**
   * Writes a value to the database. If the specified table doesn't exist yet, it will be created
   * before the write is excecuted. If a table creation is already in progress, create table will
   * only add the method to its array of callbacks
   *
   * @param {String}   key
   * @param {Object}   value
   * @param {Function} callback Will be called with null for successful set operations or with an error message string
   *
   * @public
   * @returns {void}
   */
  set( key, value, callback ) {
    const params = this._getParams( key )
    const entry  = dataTransform.transformValueForStorage( value )
    const insert = this._insert.bind( this, params, entry, callback )

    if( this._tableManager.hasTable( params.table ) ) {
      insert()
    } else {
      this._tableManager.createTable( params.table, this._primaryKey, insert )
    }
  }

  /**
   * Retrieves a value from the cache
   *
   * @param {String}   key
   * @param {Function} callback Will be called with null and the stored object
   *                            for successful operations or with an error message string
   *
   * @public
   * @returns {void}
   */
  get( key, callback ) {
    const params = this._getParams( key )

    if( this._tableManager.hasTable( params.table ) ) {
      rethinkdb.table( params.table ).get( params.id ).run( this._connection.get(), ( error, entry ) => {
        if( entry ) {
          delete entry[ this._primaryKey ]
          entry = dataTransform.transformValueFromStorage( entry )
        }
        callback( error, entry )
      } )
    } else {
      callback( null, null )
    }
  }

  /**
   * Deletes an entry from the cache.
   *
   * @param   {String}   key
   * @param   {Function} callback Will be called with null for successful deletions or with
   *                     an error message string
   *
   * @public
   * @returns {void}
   */
  delete( key, callback ) {
    const params = this._getParams( key )

    if( this._tableManager.hasTable( params.table ) ) {
      rethinkdb.table( params.table ).get( params.id ).delete().run( this._connection.get(), callback )
    } else {
      callback( new Error( 'Table \'' + params.table + '\' does not exist' ) )
    }
  }

  /**
   * Callback for established connections
   *
   * @param   {Error} error
   *
   * @private
   * @returns {void}
   */
  _onConnection( error ) {
    if( error ) {
      this.emit( 'error', error )
    } else {
      this._tableManager.refreshTables( () => {
        this.isReady = true
        this.emit( 'ready' )
      } )
    }
  }

  /**
   * Parses the provided record name and returns an object
   * containing a table name and a record name
   *
   * @param   {String} key the name of the record
   *
   * @private
   * @returns {Object} params
   */
  _getParams( key ) {
    const parts = key.split( this._splitChar )

    if( parts.length === 2 ) {
      return { table: parts[ 0 ], id: parts[ 1 ] }
    } else {
      return { table: this._defaultTable, id: key }
    }
  }

  /**
   * Augments a value with a primary key and writes it to the database
   *
   * @param   {Object} params Map in the format { table: String, id: String }
   * @param   {Object} value The value that will be written
   * @param   {Function} callback called with error or null
   *
   * @private
   * @returns {void}
   */
  _insert( params, value, callback ) {
    value[ this._primaryKey ] = params.id

    rethinkdb
      .table( params.table )
      .insert( value, { returnChanges: false, conflict: 'replace' } )
      .run( this._connection.get(), callback )
  }

  /**
   * Makes sure that the options object contains all mandatory
   * settings
   *
   * @param   {Object} options
   *
   * @private
   * @returns {void}
   */
  _checkOptions( options ) {
    if( typeof options.host !== 'string' ) {
      throw new Error( 'Missing option host' )
    }
    if( isNaN( options.port ) ) {
      throw new Error( 'Missing option port' )
    }
  }
 }
module.exports = Connector<|endoftext|>


<commit_before>// Copyright (c) 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
'use strict';

var CLOSE_TIMEOUT = 2; /* Close window x seconds after disconnect */
var DEBUG_LEVEL = 2; /* If debug is enabled, use this level in NaCl */
var RESIZE_RATE_LIMIT = 300; /* No more than 1 resize query every x ms */

var KiwiModule_ = null; /* NaCl module */
var listener_ = null; /* listener div element */
var infodiv_ = null; /* info div (contains status, warning(s), error(s)) */
var statusdiv_ = null; /* status div */
var warningdiv_ = null; /* warning div */
var errordiv_ = null; /* error div */

var debug_ = 0; /* Debuging level, passed to NaCl module */
var hidpi_ = 0; /* HiDPI mode */
var display_ = -1; /* Display number to use */
var title_ = "crouton"; /* window title */
var connected_ = false;
var closing_ = false; /* Disconnected, and waiting for the window to close */
var error_ = false; /* An error has occured */

var prevstate_ = "maximized"; /* Previous window state (before full screen) */

 /* Rate limit resize events */
var resizePending_ = false;
var resizeLimited_ = false;

function registerWindow(register) {
    chrome.extension.getBackgroundPage().
        registerKiwi(display_, register ? window : null);
}

/* NaCl module loaded */
function moduleDidLoad() {
    KiwiModule_ = document.getElementById('kiwi');
    setStatus('Starting...');
    KiwiModule_.postMessage('debug:' + debug_);
    KiwiModule_.postMessage('hidpi:' + hidpi_);
    /* Sending the display command triggers a connection: send it last. */
    KiwiModule_.postMessage('display:' + display_);
    KiwiModule_.focus();
}

/* NaCl is loading... */
function handleProgress(event) {
    /* We could compute a percentage, but loading gets stuck at 89% (while
     * translating?), so it's not very useful... */
    setStatus('Loading...');
}

/* NaCl module failed to load */
function handleError(event) {
    // We can't use common.naclModule yet because the module has not been
    // loaded.
    KiwiModule_ = document.getElementById('kiwi');
    showError(KiwiModule_.lastError);
    registerWindow(false);
}

/* NaCl module crashed */
function handleCrash(event) {
    if (KiwiModule_.exitStatus == -1) {
        showError('NaCl module crashed.');
    } else {
        showError('NaCl module exited: ' + KiwiModule_.exitStatus);
    }
    registerWindow(false);
}

/* Handle requests from the background page (for tabs) */
function handleRequest(message, sender, sendResponse) {
    if (typeof(window[message.func]) == "function") {
        window[message.func](message.param);
    }
};

/* Change debugging level */
function setDebug(debug) {
    debug_ = (debug > 0) ? DEBUG_LEVEL : 0;
    if (debug_ > 0) {
        document.getElementById('content').style.paddingTop = "16px";
        document.getElementById('header').style.display = 'block';
    } else {
        document.getElementById('content').style.paddingTop = "0px";
        document.getElementById('header').style.display = 'none';
    }
    if (KiwiModule_) {
        KiwiModule_.postMessage('debug:' + debug_);
        kiwiResize();
    }
}

/* Change HiDPI mode */
function setHiDPI(hidpi) {
    hidpi_ = hidpi;
    if (KiwiModule_) {
        KiwiModule_.postMessage('hidpi:' + hidpi_);
        kiwiResize();
    }
}

function setTitle(title) {
    document.title = title;
}

/* Set status message */
function setStatus(message) {
    if (message) {
        statusdiv_.textContent = message;
        statusdiv_.style.display = 'block';
    } else {
        statusdiv_.style.display = 'none';
    }
}

/* Set warning message */
function showWarning(message) {
    var div = addInfoLine(warningdiv_, message);
    var warningclose = div.getElementsByClassName("close")[0];
    warningclose.onclick = function() { infodiv_.removeChild(div); };
}

/* Set error message */
function showError(message) {
    error_ = true;
    setStatus(null);
    addInfoLine(errordiv_, message);
}

/* Adds warning/error line to info div. Returns duplicated element */
function addInfoLine(div, message) {
    var newdiv = div.cloneNode(true);
    var divtext = newdiv.getElementsByClassName("text")[0];
    divtext.textContent = message;
    /* Insert all warnings/errors before the status line. */
    infodiv_.insertBefore(newdiv, statusdiv_);
    return newdiv;
}

/* This function is called when a message is received from the NaCl module. */
/* Message format is type:payload */
function handleMessage(message) {
    var str = message.data;
    var type, payload, i;
    if ((i = str.indexOf(":")) > 0) {
        type = str.substr(0, i);
        payload = str.substr(i+1);
    } else {
        type = "debug";
        payload = str;
    }

    console.log(message.data);

    if (type == "debug") {
        var debugEl = document.getElementById('debug');
        if (debugEl)
            debugEl.textContent = message.data;
    } else if (type == "status") {
        setStatus(payload);
    } else if (type == "warning") {
        showWarning(payload);
    } else if (type == "error") {
        showError(payload);
    } else if (type == "connected") {
        connected_ = true;
        setStatus(null);
    } else if (type == "disconnected") {
        connected_ = false;
        if (debug_ < 1 && !error_) {
            closing_ = true;
            setStatus("Disconnected, closing window in " +
                         CLOSE_TIMEOUT + " seconds.");
            setTimeout(function() { window.close() }, CLOSE_TIMEOUT*1000);
        } else {
            setStatus("Disconnected, please close the window.");
        }
        registerWindow(false);
    } else if (type == "state" && payload == "fullscreen") {
        /* Toggle full screen */
        chrome.windows.getCurrent(function(win) {
            var newstate = prevstate_;
            if (win.state != "fullscreen") {
                prevstate_ = win.state;
                newstate = "fullscreen";
            }
            chrome.windows.update(chrome.windows.WINDOW_ID_CURRENT,
                                  {state: newstate}, function(win) {});
        });
    } else if (type == "state" && payload == "hide") {
        /* Hide window */
        chrome.windows.getCurrent(function(win) {
            var minimize = function(win) {
                chrome.windows.update(chrome.windows.WINDOW_ID_CURRENT,
                                      {state: 'minimized'}, function(win) {})}
            /* To make restore nicer, first exit full screen, then minimize */
            if (win.state == "fullscreen") {
                chrome.windows.update(chrome.windows.WINDOW_ID_CURRENT,
                                      {state: 'maximized'}, minimize);
            } else {
                minimize();
            }
        });
    } else if (type == "resize") {
        i = payload.indexOf("/");
        if (i < 0) return;
        /* FIXME: Show scroll bars if the window is too small */
        var width = payload.substr(0, i);
        var height = payload.substr(i+1);
        var lwidth = listener_.clientWidth;
        var lheight = listener_.clientHeight;
        var marginleft = (lwidth-width)/2;
        var margintop = (lheight-height)/2;
        KiwiModule_.style.marginLeft = Math.max(marginleft, 0) + "px";
        KiwiModule_.style.marginTop = Math.max(margintop, 0) + "px";
        KiwiModule_.width = width;
        KiwiModule_.height = height;
    }
}

/* Tell the module that the window was resized (this triggers a change of
 * resolution, followed by a resize message. */
function kiwiResize() {
    console.log("resize! " + listener_.clientWidth + "/" + listener_.clientHeight);
    if (KiwiModule_)
        KiwiModule_.postMessage('resize:' + listener_.clientWidth + "/" + listener_.clientHeight);
}

/* Window was resize, limit to one event per second */
function handleResize() {
    if (!resizeLimited_) {
        kiwiResize();
        setTimeout(function() {
            if (resizePending_)
                kiwiResize();
            resizeLimited_ = resizePending_ = false;
        }, RESIZE_RATE_LIMIT);
        resizeLimited_ = true;
    } else {
        resizePending_ = true;
    }
}

/* Called when window changes focus/visiblity */
function handleFocusBlur(evt) {
    /* Unfortunately, hidden/visibilityState is not able to tell when a window
     * is not visible at all (e.g. in the background).
     * See http://crbug.com/403061 */
    console.log("focus/blur: " + evt.type + ", focus=" + document.hasFocus() +
                ", hidden=" + document.hidden + "/" + document.visibilityState);
    if (!KiwiModule_)
        return;

    if (document.hasFocus()) {
        KiwiModule_.postMessage("focus:");
    } else {
        if (closing_)
            window.close();

        if (!document.hidden)
            KiwiModule_.postMessage("blur:");
        else
            KiwiModule_.postMessage("hide:");
    }
    console.log("active: " + document.activeElement);
    KiwiModule_.focus();
}

/* Parse arguments */
(function() {
    var args = location.search.substring(1).split('&');
    for (var i = 0; i < args.length; i++) {
        var keyval = args[i].split('=');
        if (keyval[0] == "display") {
            display_ = keyval[1];
        } else if (keyval[0] == "title") {
            title_ = decodeURIComponent(keyval[1]);
        } else if (keyval[0] == "debug") {
            debug_ = keyval[1];
        } else if (keyval[0] == "hidpi") {
            hidpi_ = keyval[1];
        } else if (keyval[0] == "mode") {
            if (keyval[1] == 'f') {
                chrome.windows.update(chrome.windows.WINDOW_ID_CURRENT,
                                      {state: "fullscreen"});
            }
        }
    }
}());

document.addEventListener('DOMContentLoaded', function() {
    listener_ = document.getElementById('listener');
    listener_.addEventListener('load', moduleDidLoad, true);
    listener_.addEventListener('progress', handleProgress, true);
    listener_.addEventListener('error', handleError, true);
    listener_.addEventListener('crash', handleCrash, true);
    listener_.addEventListener('message', handleMessage, true);
    window.addEventListener('resize', handleResize);
    window.addEventListener('focus', handleFocusBlur);
    window.addEventListener('blur', handleFocusBlur);
    document.addEventListener('visibilitychange', handleFocusBlur);
    chrome.runtime.onMessage.addListener(handleRequest);

    infodiv_ = document.getElementById('info');
    statusdiv_ = document.getElementById('status');
    warningdiv_ = document.getElementById('warning');
    errordiv_ = document.getElementById('error');

    infodiv_.removeChild(warningdiv_);
    infodiv_.removeChild(errordiv_);

    warningdiv_.style.display = 'block';
    errordiv_.style.display = 'block';

    setDebug(debug_);
    setHiDPI(hidpi_);
    setTitle(title_);

    registerWindow(true);
});
<commit_msg>Clean up argument processing<commit_after>// Copyright (c) 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
'use strict';

var CLOSE_TIMEOUT = 2; /* Close window x seconds after disconnect */
var DEBUG_LEVEL = 2; /* If debug is enabled, use this level in NaCl */
var RESIZE_RATE_LIMIT = 300; /* No more than 1 resize query every x ms */

var KiwiModule_ = null; /* NaCl module */
var listener_ = null; /* listener div element */
var infodiv_ = null; /* info div (contains status, warning(s), error(s)) */
var statusdiv_ = null; /* status div */
var warningdiv_ = null; /* warning div */
var errordiv_ = null; /* error div */

var debug_ = 0; /* Debuging level, passed to NaCl module */
var hidpi_ = 0; /* HiDPI mode */
var display_ = -1; /* Display number to use */
var title_ = "crouton"; /* window title */
var connected_ = false;
var closing_ = false; /* Disconnected, and waiting for the window to close */
var error_ = false; /* An error has occured */

var prevstate_ = "maximized"; /* Previous window state (before full screen) */

 /* Rate limit resize events */
var resizePending_ = false;
var resizeLimited_ = false;

function registerWindow(register) {
    chrome.extension.getBackgroundPage().
        registerKiwi(display_, register ? window : null);
}

/* NaCl module loaded */
function moduleDidLoad() {
    KiwiModule_ = document.getElementById('kiwi');
    setStatus('Starting...');
    KiwiModule_.postMessage('debug:' + debug_);
    KiwiModule_.postMessage('hidpi:' + hidpi_);
    /* Sending the display command triggers a connection: send it last. */
    KiwiModule_.postMessage('display:' + display_);
    KiwiModule_.focus();
}

/* NaCl is loading... */
function handleProgress(event) {
    /* We could compute a percentage, but loading gets stuck at 89% (while
     * translating?), so it's not very useful... */
    setStatus('Loading...');
}

/* NaCl module failed to load */
function handleError(event) {
    // We can't use common.naclModule yet because the module has not been
    // loaded.
    KiwiModule_ = document.getElementById('kiwi');
    showError(KiwiModule_.lastError);
    registerWindow(false);
}

/* NaCl module crashed */
function handleCrash(event) {
    if (KiwiModule_.exitStatus == -1) {
        showError('NaCl module crashed.');
    } else {
        showError('NaCl module exited: ' + KiwiModule_.exitStatus);
    }
    registerWindow(false);
}

/* Handle requests from the background page (for tabs) */
function handleRequest(message, sender, sendResponse) {
    if (typeof(window[message.func]) == "function") {
        window[message.func](message.param);
    }
};

/* Change debugging level */
function setDebug(debug) {
    debug_ = (debug > 0) ? DEBUG_LEVEL : 0;
    if (debug_ > 0) {
        document.getElementById('content').style.paddingTop = "16px";
        document.getElementById('header').style.display = 'block';
    } else {
        document.getElementById('content').style.paddingTop = "0px";
        document.getElementById('header').style.display = 'none';
    }
    if (KiwiModule_) {
        KiwiModule_.postMessage('debug:' + debug_);
        kiwiResize();
    }
}

/* Change HiDPI mode */
function setHiDPI(hidpi) {
    hidpi_ = hidpi;
    if (KiwiModule_) {
        KiwiModule_.postMessage('hidpi:' + hidpi_);
        kiwiResize();
    }
}

function setTitle(title) {
    document.title = title;
}

/* Set status message */
function setStatus(message) {
    if (message) {
        statusdiv_.textContent = message;
        statusdiv_.style.display = 'block';
    } else {
        statusdiv_.style.display = 'none';
    }
}

/* Set warning message */
function showWarning(message) {
    var div = addInfoLine(warningdiv_, message);
    var warningclose = div.getElementsByClassName("close")[0];
    warningclose.onclick = function() { infodiv_.removeChild(div); };
}

/* Set error message */
function showError(message) {
    error_ = true;
    setStatus(null);
    addInfoLine(errordiv_, message);
}

/* Adds warning/error line to info div. Returns duplicated element */
function addInfoLine(div, message) {
    var newdiv = div.cloneNode(true);
    var divtext = newdiv.getElementsByClassName("text")[0];
    divtext.textContent = message;
    /* Insert all warnings/errors before the status line. */
    infodiv_.insertBefore(newdiv, statusdiv_);
    return newdiv;
}

/* This function is called when a message is received from the NaCl module. */
/* Message format is type:payload */
function handleMessage(message) {
    var str = message.data;
    var type, payload, i;
    if ((i = str.indexOf(":")) > 0) {
        type = str.substr(0, i);
        payload = str.substr(i+1);
    } else {
        type = "debug";
        payload = str;
    }

    console.log(message.data);

    if (type == "debug") {
        var debugEl = document.getElementById('debug');
        if (debugEl)
            debugEl.textContent = message.data;
    } else if (type == "status") {
        setStatus(payload);
    } else if (type == "warning") {
        showWarning(payload);
    } else if (type == "error") {
        showError(payload);
    } else if (type == "connected") {
        connected_ = true;
        setStatus(null);
    } else if (type == "disconnected") {
        connected_ = false;
        if (debug_ < 1 && !error_) {
            closing_ = true;
            setStatus("Disconnected, closing window in " +
                         CLOSE_TIMEOUT + " seconds.");
            setTimeout(function() { window.close() }, CLOSE_TIMEOUT*1000);
        } else {
            setStatus("Disconnected, please close the window.");
        }
        registerWindow(false);
    } else if (type == "state" && payload == "fullscreen") {
        /* Toggle full screen */
        chrome.windows.getCurrent(function(win) {
            var newstate = prevstate_;
            if (win.state != "fullscreen") {
                prevstate_ = win.state;
                newstate = "fullscreen";
            }
            chrome.windows.update(chrome.windows.WINDOW_ID_CURRENT,
                                  {state: newstate}, function(win) {});
        });
    } else if (type == "state" && payload == "hide") {
        /* Hide window */
        chrome.windows.getCurrent(function(win) {
            var minimize = function(win) {
                chrome.windows.update(chrome.windows.WINDOW_ID_CURRENT,
                                      {state: 'minimized'}, function(win) {})}
            /* To make restore nicer, first exit full screen, then minimize */
            if (win.state == "fullscreen") {
                chrome.windows.update(chrome.windows.WINDOW_ID_CURRENT,
                                      {state: 'maximized'}, minimize);
            } else {
                minimize();
            }
        });
    } else if (type == "resize") {
        i = payload.indexOf("/");
        if (i < 0) return;
        /* FIXME: Show scroll bars if the window is too small */
        var width = payload.substr(0, i);
        var height = payload.substr(i+1);
        var lwidth = listener_.clientWidth;
        var lheight = listener_.clientHeight;
        var marginleft = (lwidth-width)/2;
        var margintop = (lheight-height)/2;
        KiwiModule_.style.marginLeft = Math.max(marginleft, 0) + "px";
        KiwiModule_.style.marginTop = Math.max(margintop, 0) + "px";
        KiwiModule_.width = width;
        KiwiModule_.height = height;
    }
}

/* Tell the module that the window was resized (this triggers a change of
 * resolution, followed by a resize message. */
function kiwiResize() {
    console.log("resize! " + listener_.clientWidth + "/" + listener_.clientHeight);
    if (KiwiModule_)
        KiwiModule_.postMessage('resize:' + listener_.clientWidth + "/" + listener_.clientHeight);
}

/* Window was resize, limit to one event per second */
function handleResize() {
    if (!resizeLimited_) {
        kiwiResize();
        setTimeout(function() {
            if (resizePending_)
                kiwiResize();
            resizeLimited_ = resizePending_ = false;
        }, RESIZE_RATE_LIMIT);
        resizeLimited_ = true;
    } else {
        resizePending_ = true;
    }
}

/* Called when window changes focus/visiblity */
function handleFocusBlur(evt) {
    /* Unfortunately, hidden/visibilityState is not able to tell when a window
     * is not visible at all (e.g. in the background).
     * See http://crbug.com/403061 */
    console.log("focus/blur: " + evt.type + ", focus=" + document.hasFocus() +
                ", hidden=" + document.hidden + "/" + document.visibilityState);
    if (!KiwiModule_)
        return;

    if (document.hasFocus()) {
        KiwiModule_.postMessage("focus:");
    } else {
        if (closing_)
            window.close();

        if (!document.hidden)
            KiwiModule_.postMessage("blur:");
        else
            KiwiModule_.postMessage("hide:");
    }
    console.log("active: " + document.activeElement);
    KiwiModule_.focus();
}

/* Parse arguments */
location.search.substring(1).split('&').forEach(function(arg) {
    var keyval = arg.split('=');
    if (keyval[0] == "display") {
        display_ = keyval[1];
    } else if (keyval[0] == "title") {
        title_ = decodeURIComponent(keyval[1]);
    } else if (keyval[0] == "debug") {
        debug_ = keyval[1];
    } else if (keyval[0] == "hidpi") {
        hidpi_ = keyval[1];
    } else if (keyval[0] == "mode") {
        if (keyval[1] == 'f') {
            chrome.windows.update(chrome.windows.WINDOW_ID_CURRENT,
                                  {state: "fullscreen"});
        }
    }
});

document.addEventListener('DOMContentLoaded', function() {
    listener_ = document.getElementById('listener');
    listener_.addEventListener('load', moduleDidLoad, true);
    listener_.addEventListener('progress', handleProgress, true);
    listener_.addEventListener('error', handleError, true);
    listener_.addEventListener('crash', handleCrash, true);
    listener_.addEventListener('message', handleMessage, true);
    window.addEventListener('resize', handleResize);
    window.addEventListener('focus', handleFocusBlur);
    window.addEventListener('blur', handleFocusBlur);
    document.addEventListener('visibilitychange', handleFocusBlur);
    chrome.runtime.onMessage.addListener(handleRequest);

    infodiv_ = document.getElementById('info');
    statusdiv_ = document.getElementById('status');
    warningdiv_ = document.getElementById('warning');
    errordiv_ = document.getElementById('error');

    infodiv_.removeChild(warningdiv_);
    infodiv_.removeChild(errordiv_);

    warningdiv_.style.display = 'block';
    errordiv_.style.display = 'block';

    setDebug(debug_);
    setHiDPI(hidpi_);
    setTitle(title_);

    registerWindow(true);
});
<|endoftext|>


<commit_before>(function(){
	'use strict';
	var source = document.createElement('source');
	var requestAnimationFrame = window.requestAnimationFrame || setTimeout;
	window.createBeforeEach = function(params){
		params = $.param(params || {});
		return function(){
			var that = this;
			this.promise = $.Deferred();
			this.$iframe = $('#test-iframe');

			this.$iframe.css({width: 300, height: 300});

			this.$iframe.one('load', function(){
				that.promise.resolveWith(that, [that.$iframe.prop('contentWindow').jQuery, that.$iframe.prop('contentWindow')]);
			});

			this.$iframe.prop('src', 'test-files/content-file.html?'+params);
		};
	};

	window.createPicture = function($, srces){
		var $picture = $('<picture />');
		$.each(srces, function(i, attrs){
			var $elem;
			if(i >= srces.length -1){
				$elem = 'img';
			} else {
				$elem = 'source';
			}
			$elem = $('<' + $elem + '/>');
			$picture.append($elem);
			$elem.attr(attrs);
		});
		return $picture;
	};
	window.cleanUpDensity = function(ar){
		(ar || []).forEach(function(obj){
			if(obj.d){
				delete obj.d;
			}
			if(obj.cached){
				delete obj.cached;
			}
		});
		return ar;
	};

	window.isTrident = /rident/.test(navigator.userAgent);
	window.bustedSrcset = (('srcset' in document.createElement('img')) && !('sizes' in document.createElement('img')));
	window.supportsPicture = !window.bustedSrcset && window.HTMLPictureElement && ('sizes' in source) && ('srcset' in source);
	window.afterUnveil = (function(){
		return function(fn, delay){
			setTimeout(function(){
				requestAnimationFrame(function(){
					setTimeout(fn,0);
				});
			}, delay || 9);
		};
	})();
})();
<commit_msg>fix Safari 9.1 tests (Safari has no srcset/sizes IDLs, WTF)<commit_after>(function(){
	'use strict';
	var source = document.createElement('source');
	var requestAnimationFrame = window.requestAnimationFrame || setTimeout;
	window.createBeforeEach = function(params){
		params = $.param(params || {});
		return function(){
			var that = this;
			this.promise = $.Deferred();
			this.$iframe = $('#test-iframe');

			this.$iframe.css({width: 300, height: 300});

			this.$iframe.one('load', function(){
				that.promise.resolveWith(that, [that.$iframe.prop('contentWindow').jQuery, that.$iframe.prop('contentWindow')]);
			});

			this.$iframe.prop('src', 'test-files/content-file.html?'+params);
		};
	};

	window.createPicture = function($, srces){
		var $picture = $('<picture />');
		$.each(srces, function(i, attrs){
			var $elem;
			if(i >= srces.length -1){
				$elem = 'img';
			} else {
				$elem = 'source';
			}
			$elem = $('<' + $elem + '/>');
			$picture.append($elem);
			$elem.attr(attrs);
		});
		return $picture;
	};
	window.cleanUpDensity = function(ar){
		(ar || []).forEach(function(obj){
			if(obj.d){
				delete obj.d;
			}
			if(obj.cached){
				delete obj.cached;
			}
		});
		return ar;
	};

	window.isTrident = /rident/.test(navigator.userAgent);
	window.bustedSrcset = (('srcset' in document.createElement('img')) && !('sizes' in document.createElement('img')));
	window.supportsPicture = !window.bustedSrcset && window.HTMLPictureElement;
	window.afterUnveil = (function(){
		return function(fn, delay){
			setTimeout(function(){
				requestAnimationFrame(function(){
					setTimeout(fn,0);
				});
			}, delay || 9);
		};
	})();
})();
<|endoftext|>


<commit_before>import R from 'ramda'

export const currencies = {
  '¤': '¤',
  '¥': '¥',
  '£': '£',
  '$': '\\$',
  '\u20a0': '\u20a0',
  '\u20a1': '\u20a1',
  '\u20a2': '\u20a2',
  '\u20a3': '\u20a3',
  '\u20a4': '\u20a4',
  '\u20a5': '\u20a5',
  '\u20a6': '\u20a6',
  '\u20a7': '\u20a7',
  '\u20a8': '\u20a8',
  '\u20a9': '\u20a9',
  '\u20aa': '\u20aa',
  '\u20ab': '\u20ab',
  '\u20ac': '\u20ac',
  '\u20ad': '\u20ad',
  '\u20ae': '\u20ae',
  '\u20af': '\u20af',
  '\u20b0': '\u20b0',
  '\u20b1': '\u20b1',
  '\u20b2': '\u20b2',
  '\u20b3': '\u20b3',
  '\u20b4': '\u20b4',
  '\u20b5': '\u20b5',
  '\u20b6': '\u20b6',
  '\u20b7': '\u20b7',
  '\u20b8': '\u20b8',
  '\u20b9': '\u20b9',
  '\u20ba': '\u20ba',
  '\u20bb': '\u20bb',
  '\u20bc': '\u20bc',
  '\u20bd': '\u20bd',
  '\u20be': '\u20be'
}

export const currenciesRegExp = Object.keys(currencies).map((c) => {
  return currencies[c]
}).join('')

export const vulgarFractions = [
  [1, 4, '\u00bc'],
  [1, 2, '\u00bd'],
  [3, 4, '\u00be'],
  [1, 7, '\u2150'],
  [1, 9, '\u2151'],
  [1, 10, '\u2152'],
  [1, 3, '\u2153'],
  [2, 3, '\u2154'],
  [1, 5, '\u2155'],
  [2, 5, '\u2156'],
  [3, 5, '\u2157'],
  [4, 5, '\u2158'],
  [1, 6, '\u2159'],
  [5, 6, '\u215a'],
  [1, 8, '\u215b'],
  [3, 8, '\u215c'],
  [5, 8, '\u215d'],
  [7, 8, '\u215e'],
  [0, 3, '\u2189']
]

export const unitScales = [
  'y',
  'z',
  'a',
  'f',
  'p',
  'n',
  'µ',
  'm',
  'c',
  'd',
  '',
  'da',
  'h',
  'k',
  'M',
  'G',
  'T',
  'P',
  'E',
  'Z',
  'Y'
]

export const scalableUnits = [
  'm',
  'm²',
  'm³',
  'g',
  's',
  'l',
  'L',
  'b',
  'B',
  'K',
  'W',
  'V',
  'Hz',
  'Ω',
  'A',
  'mol',
  'cd'
]

export const surfaceUnits = [
  'mile',
  'in',
  'yd',
  'ft',
  'm'
]

export const volumeUnits = [
  'in',
  'yd',
  'ft',
  'm'
]

export const otherUnits = [
  // temperatures
  '°C',
  '°F',
  '°Ré',
  '°N',
  '°Ra',
  // distances
  'mi',
  'in',
  'ft',
  'yd',
  'nautical mile',
  // speed
  'kmph',
  'km/h',
  'mps',
  'm/s',
  'mph',
  'mi/h',
  'knot',
  'nautical mile/h',
  'ma',
  // surfaces
  'ha',
  'a',
  'ca',
  'mile²',
  'in²',
  'yd²',
  'ft²',
  'ro',
  'acre',
  'nautical mile²',
  // volumes
  'in³',
  'ft³',
  'yd³',
  'gal',
  'bbl',
  'pt',
  'fluid pt',
  'dry pt',
  // weight
  't',
  'carat',
  'grain',
  'oz',
  'lb',
  'cwt',
  'ton',
  'st',
  // time
  'h',
  'min',
  // electric
  'dBm',
  'dBW',
  'var',
  'VA',
  'F',
  'H',
  'S',
  'C',
  'Ah',
  'J',
  'kWh',
  'eV',
  'Ω∙m',
  'S/m',
  'V/m',
  'N/C',
  'V·m',
  'T',
  'G',
  'Wb',
  'dB',
  'ppm'
]

const combine = R.compose(R.map(R.join('')), R.xprod)

export const allUnits = otherUnits.concat(combine(unitScales, scalableUnits))
<commit_msg>Add missing currencies<commit_after>import R from 'ramda'

export const currencies = {
  '¤': '¤',
  '¥': '¥',
  '£': '£',
  '$': '\\$',
  '\u20a0': '\u20a0',
  '\u20a1': '\u20a1',
  '\u20a2': '\u20a2',
  '\u20a3': '\u20a3',
  '\u20a4': '\u20a4',
  '\u20a5': '\u20a5',
  '\u20a6': '\u20a6',
  '\u20a7': '\u20a7',
  '\u20a8': '\u20a8',
  '\u20a9': '\u20a9',
  '\u20aa': '\u20aa',
  '\u20ab': '\u20ab',
  '\u20ac': '\u20ac',
  '\u20ad': '\u20ad',
  '\u20ae': '\u20ae',
  '\u20af': '\u20af',
  '\u20b0': '\u20b0',
  '\u20b1': '\u20b1',
  '\u20b2': '\u20b2',
  '\u20b3': '\u20b3',
  '\u20b4': '\u20b4',
  '\u20b5': '\u20b5',
  '\u20b6': '\u20b6',
  '\u20b7': '\u20b7',
  '\u20b8': '\u20b8',
  '\u20b9': '\u20b9',
  '\u20ba': '\u20ba',
  '\u20bb': '\u20bb',
  '\u20bc': '\u20bc',
  '\u20bd': '\u20bd',
  '\u20be': '\u20be',
  '\u0E3F': '\u0E3F',
  '\u17DB': '\u17DB'
}

export const currenciesRegExp = Object.keys(currencies).map((c) => {
  return currencies[c]
}).join('')

export const vulgarFractions = [
  [1, 4, '\u00bc'],
  [1, 2, '\u00bd'],
  [3, 4, '\u00be'],
  [1, 7, '\u2150'],
  [1, 9, '\u2151'],
  [1, 10, '\u2152'],
  [1, 3, '\u2153'],
  [2, 3, '\u2154'],
  [1, 5, '\u2155'],
  [2, 5, '\u2156'],
  [3, 5, '\u2157'],
  [4, 5, '\u2158'],
  [1, 6, '\u2159'],
  [5, 6, '\u215a'],
  [1, 8, '\u215b'],
  [3, 8, '\u215c'],
  [5, 8, '\u215d'],
  [7, 8, '\u215e'],
  [0, 3, '\u2189']
]

export const unitScales = [
  'y',
  'z',
  'a',
  'f',
  'p',
  'n',
  'µ',
  'm',
  'c',
  'd',
  '',
  'da',
  'h',
  'k',
  'M',
  'G',
  'T',
  'P',
  'E',
  'Z',
  'Y'
]

export const scalableUnits = [
  'm',
  'm²',
  'm³',
  'g',
  's',
  'l',
  'L',
  'b',
  'B',
  'K',
  'W',
  'V',
  'Hz',
  'Ω',
  'A',
  'mol',
  'cd'
]

export const surfaceUnits = [
  'mile',
  'in',
  'yd',
  'ft',
  'm'
]

export const volumeUnits = [
  'in',
  'yd',
  'ft',
  'm'
]

export const otherUnits = [
  // temperatures
  '°C',
  '°F',
  '°Ré',
  '°N',
  '°Ra',
  // distances
  'mi',
  'in',
  'ft',
  'yd',
  'nautical mile',
  // speed
  'kmph',
  'km/h',
  'mps',
  'm/s',
  'mph',
  'mi/h',
  'knot',
  'nautical mile/h',
  'ma',
  // surfaces
  'ha',
  'a',
  'ca',
  'mile²',
  'in²',
  'yd²',
  'ft²',
  'ro',
  'acre',
  'nautical mile²',
  // volumes
  'in³',
  'ft³',
  'yd³',
  'gal',
  'bbl',
  'pt',
  'fluid pt',
  'dry pt',
  // weight
  't',
  'carat',
  'grain',
  'oz',
  'lb',
  'cwt',
  'ton',
  'st',
  // time
  'h',
  'min',
  // electric
  'dBm',
  'dBW',
  'var',
  'VA',
  'F',
  'H',
  'S',
  'C',
  'Ah',
  'J',
  'kWh',
  'eV',
  'Ω∙m',
  'S/m',
  'V/m',
  'N/C',
  'V·m',
  'T',
  'G',
  'Wb',
  'dB',
  'ppm'
]

const combine = R.compose(R.map(R.join('')), R.xprod)

export const allUnits = otherUnits.concat(combine(unitScales, scalableUnits))
<|endoftext|>