<commit_before>/*
 * == BSD2 LICENSE ==
 * Copyright (c) 2016, Tidepool Project
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the associated License, which is identical to the BSD 2-Clause
 * License as published by the Open Source Initiative at opensource.org.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the License for more details.
 *
 * You should have received a copy of the License along with this program; if
 * not, you can obtain one from Tidepool Project at tidepool.org.
 * == BSD2 LICENSE ==
 */

const CBG_READINGS_ONE_DAY = 86400000 / (1000 * 60 * 5);

import _ from 'lodash';
import bows from 'bows';
import { extent } from 'd3-array';
import { scaleLinear } from 'd3-scale';
import { utcDay } from 'd3-time';
import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';

import * as actions from '../../actions/';
import CBGTrendsContainer from './CBGTrendsContainer';
import * as datetime from '../../utils/datetime';

export class TrendsContainer extends React.Component {
  static propTypes = {
    activeDays: PropTypes.shape({
      monday: PropTypes.bool.isRequired,
      tuesday: PropTypes.bool.isRequired,
      wednesday: PropTypes.bool.isRequired,
      thursday: PropTypes.bool.isRequired,
      friday: PropTypes.bool.isRequired,
      saturday: PropTypes.bool.isRequired,
      sunday: PropTypes.bool.isRequired,
    }).isRequired,
    // new data structure to replace bgClasses
    bgBounds: PropTypes.shape({
      veryHighThreshold: PropTypes.number.isRequired,
      targetUpperBound: PropTypes.number.isRequired,
      targetLowerBound: PropTypes.number.isRequired,
      veryLowThreshold: PropTypes.number.isRequired,
    }).isRequired,
    // legacy data structure for representing target range, &c
    // needed for passed-in legacy tideline smbg component but will phase out!
    bgClasses: PropTypes.shape({
      'very-high': PropTypes.shape({ boundary: PropTypes.number.isRequired }).isRequired,
      high: PropTypes.shape({ boundary: PropTypes.number.isRequired }).isRequired,
      target: PropTypes.shape({ boundary: PropTypes.number.isRequired }).isRequired,
      low: PropTypes.shape({ boundary: PropTypes.number.isRequired }).isRequired,
      'very-low': PropTypes.shape({ boundary: PropTypes.number.isRequired }).isRequired,
    }).isRequired,
    bgUnits: PropTypes.oneOf(['mg/dL', 'mmol/L']).isRequired,
    extentSize: PropTypes.oneOf([7, 14, 28]).isRequired,
    initialDatetimeLocation: PropTypes.string,
    showingSmbg: PropTypes.bool.isRequired,
    showingCbg: PropTypes.bool.isRequired,
    smbgRangeOverlay: PropTypes.bool.isRequired,
    smbgGrouped: PropTypes.bool.isRequired,
    smbgLines: PropTypes.bool.isRequired,
    smbgTrendsComponent: PropTypes.func.isRequired,
    timePrefs: PropTypes.shape({
      timezoneAware: PropTypes.bool.isRequired,
      timezoneName: PropTypes.string.isRequired,
    }).isRequired,
    yScaleClampTop: PropTypes.object.isRequired,
    // data (crossfilter dimensions)
    cbgByDate: PropTypes.object.isRequired,
    cbgByDayOfWeek: PropTypes.object.isRequired,
    smbgByDate: PropTypes.object.isRequired,
    smbgByDayOfWeek: PropTypes.object.isRequired,
    // handlers
    onDatetimeLocationChange: PropTypes.func.isRequired,
    onSelectDay: PropTypes.func.isRequired,
    onSwitchBgDataSource: PropTypes.func.isRequired,
    // viz state
    viz: PropTypes.shape({
      trends: PropTypes.shape({
        focusedCbgSlice: PropTypes.object,
        focusedCbgSliceKeys: PropTypes.array,
        touched: PropTypes.bool.isRequired,
      }).isRequired,
    }).isRequired,
    // actions
    focusTrendsCbgSlice: PropTypes.func.isRequired,
    markTrendsViewed: PropTypes.func.isRequired,
    unfocusTrendsCbgSlice: PropTypes.func.isRequired,
  };

  static defaultProps = {
    yScaleClampTop: {
      'mg/dL': 400,
      'mmol/L': 22.5,
    },
  };

  constructor(props) {
    super(props);
    this.log = bows('TrendsContainer');
    this.state = {
      currentCbgData: [],
      currentSmbgData: [],
      dateDomain: null,
      mostRecent: null,
      xScale: null,
      yScale: null,
    };
  }

  componentWillMount() {
    // find BG domain (for yScale construction)
    const { cbgByDate, cbgByDayOfWeek, smbgByDate, smbgByDayOfWeek } = this.props;
    const allBg = cbgByDate.filterAll().top(Infinity).concat(smbgByDate.filterAll().top(Infinity));
    const bgDomain = extent(allBg, d => d.value);

    const { bgBounds, bgUnits, yScaleClampTop } = this.props;
    const upperBound = yScaleClampTop[bgUnits];
    const yScaleDomain = [bgDomain[0], upperBound];
    if (bgDomain[0] > bgBounds.targetLowerBound) {
      yScaleDomain[0] = bgBounds.targetLowerBound;
    }
    const yScale = scaleLinear().domain(yScaleDomain).clamp(true);

    // find initial date domain (based on initialDatetimeLocation or current time)
    const { extentSize, initialDatetimeLocation, timePrefs } = this.props;
    const timezone = datetime.getTimezoneFromTimePrefs(timePrefs);
    const mostRecent = datetime.timezoneAwareCeiling(new Date().valueOf(), timezone);
    const end = initialDatetimeLocation ?
      datetime.timezoneAwareCeiling(initialDatetimeLocation, timezone) : mostRecent;
    const start = utcDay.offset(end, -extentSize);
    const dateDomain = [start.toISOString(), end.toISOString()];

    // filter data according to current activeDays and dateDomain
    this.initialFiltering(cbgByDate, cbgByDayOfWeek, dateDomain);
    this.initialFiltering(smbgByDate, smbgByDayOfWeek, dateDomain);
    this.setState({
      bgDomain: { lo: bgDomain[0], hi: bgDomain[1] },
      currentCbgData: cbgByDate.top(Infinity).reverse(),
      currentSmbgData: smbgByDate.top(Infinity).reverse(),
      dateDomain: { start: dateDomain[0], end: dateDomain[1] },
      mostRecent: mostRecent.toISOString(),
      xScale: scaleLinear().domain([0, 864e5]),
      yScale,
    }, this.determineDataToShow);
    this.props.onDatetimeLocationChange(dateDomain);
  }

  /*
   * NB: we don't do as much here as one might expect
   * because we're using the "expose component functions"
   * strategy of communicating between components
   * (https://facebook.github.io/react/tips/expose-component-functions.html)
   * this is the legacy of blip's interface with the d3.chart-architected
   * smbg version of trends view and thus only remains
   * as a temporary compatibility interface
   */
  componentWillReceiveProps(nextProps) {
    if (!_.isEqual(nextProps.activeDays, this.props.activeDays)) {
      const { cbgByDayOfWeek, smbgByDayOfWeek } = this.props;
      this.refilterByDayOfWeek(cbgByDayOfWeek, nextProps.activeDays);
      this.refilterByDayOfWeek(smbgByDayOfWeek, nextProps.activeDays);
      this.setState({
        currentCbgData: cbgByDayOfWeek.top(Infinity).reverse(),
        currentSmbgData: smbgByDayOfWeek.top(Infinity).reverse(),
      });
    }
  }

  getCurrentDay() {
    const { timePrefs } = this.props;
    const { dateDomain: { end } } = this.state;
    return datetime.localNoonBeforeTimestamp(
      end,
      datetime.getTimezoneFromTimePrefs(timePrefs)
    ).toISOString();
  }

  setExtent(newDomain) {
    const { cbgByDate, smbgByDate } = this.props;
    this.refilterByDate(cbgByDate, newDomain);
    this.refilterByDate(smbgByDate, newDomain);
    this.setState({
      currentCbgData: cbgByDate.top(Infinity).reverse(),
      currentSmbgData: smbgByDate.top(Infinity).reverse(),
      dateDomain: { start: newDomain[0], end: newDomain[1] },
    });
  }

  goBack() {
    const { dateDomain: { start: newEnd } } = this.state;
    const { timePrefs } = this.props;
    const timezone = datetime.getTimezoneFromTimePrefs(timePrefs);
    const start = datetime.timezoneAwareOffset(newEnd, timezone, {
      amount: -this.props.extentSize,
      units: 'days',
    }).toISOString();
    const newDomain = [start, newEnd];
    this.setExtent(newDomain);
    this.props.onDatetimeLocationChange(newDomain);
  }

  goForward() {
    const { dateDomain: { end: newStart }, mostRecent } = this.state;
    const end = utcDay.offset(new Date(newStart), this.props.extentSize).toISOString();
    const newDomain = [newStart, end];
    this.setExtent(newDomain);
    this.props.onDatetimeLocationChange(newDomain);
    return (end >= mostRecent);
  }

  goToMostRecent() {
    const { mostRecent: end } = this.state;
    const start = utcDay.offset(new Date(end), -this.props.extentSize).toISOString();
    const newDomain = [start, end];
    this.setExtent(newDomain);
    this.props.onDatetimeLocationChange(newDomain);
  }

  refilterByDate(dataByDate, dateDomain) {
    dataByDate.filter(dateDomain);
  }

  refilterByDayOfWeek(dataByDayOfWeek, activeDays) {
    dataByDayOfWeek.filterFunction(this.filterActiveDaysFn(activeDays));
  }

  initialFiltering(dataByDate, dataByDayOfWeek, dateDomain) {
    const { activeDays } = this.props;
    // clear old filters
    dataByDayOfWeek.filterAll();

    // filter by day of week (Monday, Tuesday, etc.)
    dataByDayOfWeek.filterFunction(this.filterActiveDaysFn(activeDays));

    // filter within date domain
    dataByDate.filter(dateDomain);
  }

  filterActiveDaysFn(activeDays) {
    return (d) => (activeDays[d]);
  }

  determineDataToShow() {
    const { viz: { trends: { touched } } } = this.props;
    if (touched) {
      return;
    }
    const { currentCbgData } = this.state;
    const { extentSize, showingCbg } = this.props;
    const minimumCbgs = (extentSize * CBG_READINGS_ONE_DAY) / 2;
    if (showingCbg && currentCbgData.length < minimumCbgs) {
      this.props.onSwitchBgDataSource();
    }
    this.props.markTrendsViewed();
  }

  render() {
    const { timePrefs } = this.props;
    const timezone = datetime.getTimezoneFromTimePrefs(timePrefs);
    if (this.props.showingSmbg) {
      const { smbgTrendsComponent: SMBGTrendsContainer } = this.props;
      return (
        <SMBGTrendsContainer
          bgClasses={this.props.bgClasses}
          bgDomain={[this.state.bgDomain.lo, this.state.bgDomain.hi]}
          bgUnits={this.props.bgUnits}
          boxOverlay={this.props.smbgRangeOverlay}
          data={this.state.currentSmbgData}
          grouped={this.props.smbgGrouped}
          onSelectDay={this.props.onSelectDay}
          showingLines={this.props.smbgLines}
          timezone={timezone}
        />
      );
    } else if (this.props.showingCbg) {
      return (
        <CBGTrendsContainer
          bgBounds={this.props.bgBounds}
          bgUnits={this.props.bgUnits}
          data={this.state.currentCbgData}
          focusedSlice={this.props.viz.trends.focusedCbgSlice}
          focusedSliceKeys={this.props.viz.trends.focusedCbgSliceKeys}
          focusSlice={this.props.focusTrendsCbgSlice}
          unfocusSlice={this.props.unfocusTrendsCbgSlice}
          timezone={timezone}
          xScale={this.state.xScale}
          yScale={this.state.yScale}
        />
      );
    }
    return null;
  }
}

export function mapStateToProps(state) {
  return {
    viz: state.viz,
  };
}

export function mapDispatchToProps(dispatch) {
  return bindActionCreators({
    focusTrendsCbgSlice: actions.focusTrendsCbgSlice,
    markTrendsViewed: actions.markTrendsViewed,
    unfocusTrendsCbgSlice: actions.unfocusTrendsCbgSlice,
  }, dispatch);
}

export default connect(
  mapStateToProps,
  mapDispatchToProps,
  (stateProps, dispatchProps, ownProps) => (Object.assign({}, ownProps, stateProps, dispatchProps)),
  { withRef: true },
)(TrendsContainer);
<commit_msg>pass through Boolean re: atMostRecent status onDatetimeLocationChange<commit_after>/*
 * == BSD2 LICENSE ==
 * Copyright (c) 2016, Tidepool Project
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the associated License, which is identical to the BSD 2-Clause
 * License as published by the Open Source Initiative at opensource.org.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the License for more details.
 *
 * You should have received a copy of the License along with this program; if
 * not, you can obtain one from Tidepool Project at tidepool.org.
 * == BSD2 LICENSE ==
 */

const CBG_READINGS_ONE_DAY = 86400000 / (1000 * 60 * 5);

import _ from 'lodash';
import bows from 'bows';
import { extent } from 'd3-array';
import { scaleLinear } from 'd3-scale';
import { utcDay } from 'd3-time';
import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';

import * as actions from '../../actions/';
import CBGTrendsContainer from './CBGTrendsContainer';
import * as datetime from '../../utils/datetime';

export class TrendsContainer extends React.Component {
  static propTypes = {
    activeDays: PropTypes.shape({
      monday: PropTypes.bool.isRequired,
      tuesday: PropTypes.bool.isRequired,
      wednesday: PropTypes.bool.isRequired,
      thursday: PropTypes.bool.isRequired,
      friday: PropTypes.bool.isRequired,
      saturday: PropTypes.bool.isRequired,
      sunday: PropTypes.bool.isRequired,
    }).isRequired,
    // new data structure to replace bgClasses
    bgBounds: PropTypes.shape({
      veryHighThreshold: PropTypes.number.isRequired,
      targetUpperBound: PropTypes.number.isRequired,
      targetLowerBound: PropTypes.number.isRequired,
      veryLowThreshold: PropTypes.number.isRequired,
    }).isRequired,
    // legacy data structure for representing target range, &c
    // needed for passed-in legacy tideline smbg component but will phase out!
    bgClasses: PropTypes.shape({
      'very-high': PropTypes.shape({ boundary: PropTypes.number.isRequired }).isRequired,
      high: PropTypes.shape({ boundary: PropTypes.number.isRequired }).isRequired,
      target: PropTypes.shape({ boundary: PropTypes.number.isRequired }).isRequired,
      low: PropTypes.shape({ boundary: PropTypes.number.isRequired }).isRequired,
      'very-low': PropTypes.shape({ boundary: PropTypes.number.isRequired }).isRequired,
    }).isRequired,
    bgUnits: PropTypes.oneOf(['mg/dL', 'mmol/L']).isRequired,
    extentSize: PropTypes.oneOf([7, 14, 28]).isRequired,
    initialDatetimeLocation: PropTypes.string,
    showingSmbg: PropTypes.bool.isRequired,
    showingCbg: PropTypes.bool.isRequired,
    smbgRangeOverlay: PropTypes.bool.isRequired,
    smbgGrouped: PropTypes.bool.isRequired,
    smbgLines: PropTypes.bool.isRequired,
    smbgTrendsComponent: PropTypes.func.isRequired,
    timePrefs: PropTypes.shape({
      timezoneAware: PropTypes.bool.isRequired,
      timezoneName: PropTypes.string.isRequired,
    }).isRequired,
    yScaleClampTop: PropTypes.object.isRequired,
    // data (crossfilter dimensions)
    cbgByDate: PropTypes.object.isRequired,
    cbgByDayOfWeek: PropTypes.object.isRequired,
    smbgByDate: PropTypes.object.isRequired,
    smbgByDayOfWeek: PropTypes.object.isRequired,
    // handlers
    onDatetimeLocationChange: PropTypes.func.isRequired,
    onSelectDay: PropTypes.func.isRequired,
    onSwitchBgDataSource: PropTypes.func.isRequired,
    // viz state
    viz: PropTypes.shape({
      trends: PropTypes.shape({
        focusedCbgSlice: PropTypes.object,
        focusedCbgSliceKeys: PropTypes.array,
        touched: PropTypes.bool.isRequired,
      }).isRequired,
    }).isRequired,
    // actions
    focusTrendsCbgSlice: PropTypes.func.isRequired,
    markTrendsViewed: PropTypes.func.isRequired,
    unfocusTrendsCbgSlice: PropTypes.func.isRequired,
  };

  static defaultProps = {
    yScaleClampTop: {
      'mg/dL': 400,
      'mmol/L': 22.5,
    },
  };

  constructor(props) {
    super(props);
    this.log = bows('TrendsContainer');
    this.state = {
      currentCbgData: [],
      currentSmbgData: [],
      dateDomain: null,
      mostRecent: null,
      xScale: null,
      yScale: null,
    };
  }

  componentWillMount() {
    // find BG domain (for yScale construction)
    const { cbgByDate, cbgByDayOfWeek, smbgByDate, smbgByDayOfWeek } = this.props;
    const allBg = cbgByDate.filterAll().top(Infinity).concat(smbgByDate.filterAll().top(Infinity));
    const bgDomain = extent(allBg, d => d.value);

    const { bgBounds, bgUnits, yScaleClampTop } = this.props;
    const upperBound = yScaleClampTop[bgUnits];
    const yScaleDomain = [bgDomain[0], upperBound];
    if (bgDomain[0] > bgBounds.targetLowerBound) {
      yScaleDomain[0] = bgBounds.targetLowerBound;
    }
    const yScale = scaleLinear().domain(yScaleDomain).clamp(true);

    // find initial date domain (based on initialDatetimeLocation or current time)
    const { extentSize, initialDatetimeLocation, timePrefs } = this.props;
    const timezone = datetime.getTimezoneFromTimePrefs(timePrefs);
    const mostRecent = datetime.timezoneAwareCeiling(new Date().valueOf(), timezone);
    const end = initialDatetimeLocation ?
      datetime.timezoneAwareCeiling(initialDatetimeLocation, timezone) : mostRecent;
    const start = utcDay.offset(end, -extentSize);
    const dateDomain = [start.toISOString(), end.toISOString()];

    // filter data according to current activeDays and dateDomain
    this.initialFiltering(cbgByDate, cbgByDayOfWeek, dateDomain);
    this.initialFiltering(smbgByDate, smbgByDayOfWeek, dateDomain);
    this.setState({
      bgDomain: { lo: bgDomain[0], hi: bgDomain[1] },
      currentCbgData: cbgByDate.top(Infinity).reverse(),
      currentSmbgData: smbgByDate.top(Infinity).reverse(),
      dateDomain: { start: dateDomain[0], end: dateDomain[1] },
      mostRecent: mostRecent.toISOString(),
      xScale: scaleLinear().domain([0, 864e5]),
      yScale,
    }, this.determineDataToShow);
    this.props.onDatetimeLocationChange(dateDomain, end === mostRecent);
  }

  /*
   * NB: we don't do as much here as one might expect
   * because we're using the "expose component functions"
   * strategy of communicating between components
   * (https://facebook.github.io/react/tips/expose-component-functions.html)
   * this is the legacy of blip's interface with the d3.chart-architected
   * smbg version of trends view and thus only remains
   * as a temporary compatibility interface
   */
  componentWillReceiveProps(nextProps) {
    if (!_.isEqual(nextProps.activeDays, this.props.activeDays)) {
      const { cbgByDayOfWeek, smbgByDayOfWeek } = this.props;
      this.refilterByDayOfWeek(cbgByDayOfWeek, nextProps.activeDays);
      this.refilterByDayOfWeek(smbgByDayOfWeek, nextProps.activeDays);
      this.setState({
        currentCbgData: cbgByDayOfWeek.top(Infinity).reverse(),
        currentSmbgData: smbgByDayOfWeek.top(Infinity).reverse(),
      });
    }
  }

  getCurrentDay() {
    const { timePrefs } = this.props;
    const { dateDomain: { end } } = this.state;
    return datetime.localNoonBeforeTimestamp(
      end,
      datetime.getTimezoneFromTimePrefs(timePrefs)
    ).toISOString();
  }

  setExtent(newDomain) {
    const { cbgByDate, smbgByDate } = this.props;
    this.refilterByDate(cbgByDate, newDomain);
    this.refilterByDate(smbgByDate, newDomain);
    this.setState({
      currentCbgData: cbgByDate.top(Infinity).reverse(),
      currentSmbgData: smbgByDate.top(Infinity).reverse(),
      dateDomain: { start: newDomain[0], end: newDomain[1] },
    });
  }

  goBack() {
    const { dateDomain: { start: newEnd }, mostRecent } = this.state;
    const { timePrefs } = this.props;
    const timezone = datetime.getTimezoneFromTimePrefs(timePrefs);
    const start = datetime.timezoneAwareOffset(newEnd, timezone, {
      amount: -this.props.extentSize,
      units: 'days',
    }).toISOString();
    const newDomain = [start, newEnd];
    this.setExtent(newDomain);
    this.props.onDatetimeLocationChange(newDomain, newEnd >= mostRecent);
  }

  goForward() {
    const { dateDomain: { end: newStart }, mostRecent } = this.state;
    const end = utcDay.offset(new Date(newStart), this.props.extentSize).toISOString();
    const newDomain = [newStart, end];
    this.setExtent(newDomain);
    this.props.onDatetimeLocationChange(newDomain, end >= mostRecent);
  }

  goToMostRecent() {
    const { mostRecent: end } = this.state;
    const start = utcDay.offset(new Date(end), -this.props.extentSize).toISOString();
    const newDomain = [start, end];
    this.setExtent(newDomain);
    this.props.onDatetimeLocationChange(newDomain, true);
  }

  refilterByDate(dataByDate, dateDomain) {
    dataByDate.filter(dateDomain);
  }

  refilterByDayOfWeek(dataByDayOfWeek, activeDays) {
    dataByDayOfWeek.filterFunction(this.filterActiveDaysFn(activeDays));
  }

  initialFiltering(dataByDate, dataByDayOfWeek, dateDomain) {
    const { activeDays } = this.props;
    // clear old filters
    dataByDayOfWeek.filterAll();

    // filter by day of week (Monday, Tuesday, etc.)
    dataByDayOfWeek.filterFunction(this.filterActiveDaysFn(activeDays));

    // filter within date domain
    dataByDate.filter(dateDomain);
  }

  filterActiveDaysFn(activeDays) {
    return (d) => (activeDays[d]);
  }

  determineDataToShow() {
    const { viz: { trends: { touched } } } = this.props;
    if (touched) {
      return;
    }
    const { currentCbgData } = this.state;
    const { extentSize, showingCbg } = this.props;
    const minimumCbgs = (extentSize * CBG_READINGS_ONE_DAY) / 2;
    if (showingCbg && currentCbgData.length < minimumCbgs) {
      this.props.onSwitchBgDataSource();
    }
    this.props.markTrendsViewed();
  }

  render() {
    const { timePrefs } = this.props;
    const timezone = datetime.getTimezoneFromTimePrefs(timePrefs);
    if (this.props.showingSmbg) {
      const { smbgTrendsComponent: SMBGTrendsContainer } = this.props;
      return (
        <SMBGTrendsContainer
          bgClasses={this.props.bgClasses}
          bgDomain={[this.state.bgDomain.lo, this.state.bgDomain.hi]}
          bgUnits={this.props.bgUnits}
          boxOverlay={this.props.smbgRangeOverlay}
          data={this.state.currentSmbgData}
          grouped={this.props.smbgGrouped}
          onSelectDay={this.props.onSelectDay}
          showingLines={this.props.smbgLines}
          timezone={timezone}
        />
      );
    } else if (this.props.showingCbg) {
      return (
        <CBGTrendsContainer
          bgBounds={this.props.bgBounds}
          bgUnits={this.props.bgUnits}
          data={this.state.currentCbgData}
          focusedSlice={this.props.viz.trends.focusedCbgSlice}
          focusedSliceKeys={this.props.viz.trends.focusedCbgSliceKeys}
          focusSlice={this.props.focusTrendsCbgSlice}
          unfocusSlice={this.props.unfocusTrendsCbgSlice}
          timezone={timezone}
          xScale={this.state.xScale}
          yScale={this.state.yScale}
        />
      );
    }
    return null;
  }
}

export function mapStateToProps(state) {
  return {
    viz: state.viz,
  };
}

export function mapDispatchToProps(dispatch) {
  return bindActionCreators({
    focusTrendsCbgSlice: actions.focusTrendsCbgSlice,
    markTrendsViewed: actions.markTrendsViewed,
    unfocusTrendsCbgSlice: actions.unfocusTrendsCbgSlice,
  }, dispatch);
}

export default connect(
  mapStateToProps,
  mapDispatchToProps,
  (stateProps, dispatchProps, ownProps) => (Object.assign({}, ownProps, stateProps, dispatchProps)),
  { withRef: true },
)(TrendsContainer);
<|endoftext|>


<commit_before>/* Copyright 2015 Google Inc. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/

// This is a no-op module that shows what is leaking in the framework when we
// switch modules.
var MemoryDebugServer = function() {};
MemoryDebugServer.prototype = Object.create(ServerModuleInterface.prototype);

var MemoryDebugClient = function() {
  var client = this;
  network.on('_memory_debug', function memoryDebugHandler() {
    client.thing = (client.thing || 0) + 1;
  });
};
MemoryDebugClient.prototype = Object.create(ClientModuleInterface.prototype);
MemoryDebugClient.prototype.willBeShownSoon = function(container) {
  container.style.backgroundColor = 'red';
};

register(MemoryDebugServer, MemoryDebugClient);
<commit_msg>Fade to black between layout changes.<commit_after>/* Copyright 2015 Google Inc. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/

// This is a no-op module that shows what is leaking in the framework when we
// switch modules.
var MemoryDebugServer = function() {};
MemoryDebugServer.prototype = Object.create(ServerModuleInterface.prototype);

var MemoryDebugClient = function() {
  var client = this;
  network.on('_memory_debug', function memoryDebugHandler() {
    client.thing = (client.thing || 0) + 1;
  });
};
MemoryDebugClient.prototype = Object.create(ClientModuleInterface.prototype);
MemoryDebugClient.prototype.willBeShownSoon = function(container) {
  container.style.backgroundColor = 'black';
};

register(MemoryDebugServer, MemoryDebugClient);
<|endoftext|>


<commit_before>angular.module('lastifyFilters', []).
    filter('isAvailable', function () {
        return function (input) {
            return input.isAvailable === window.undefinded || input.isAvailable;
        };
    });

var phonecatApp =
    angular.
        module('lastifyApp', ['cfp.loadingBar', 'lastifyFilters', 'ngSanitize']).
            controller('SongListCtrl', function ($scope, $http, cfpLoadingBar, $sce) {

                $scope.trustPreview = function (song) {
                    if (!song)
                        return "";
                    return $sce.trustAsResourceUrl(song.spotifyPreview);
                };

                $scope.playPreview = function (song) {
                    $scope.songPlaying = song;
                };
                $scope.stopPreview = function () {
                    $scope.songPlaying = null;
                };
                $scope.isPlaying = function (song) {
                    return $scope.songPlaying === song;
                };

                $scope.songPlaying = null;
                $scope.songs = [];
                $scope.availableSongs = [];

                $scope.step = 1;

                // Pagination
                $scope.songsPageIndex = 0;
                $scope.songsPageMaxIndex = function () {
                    return Math.ceil($scope.songs.length / $scope.songsRange);
                };
                $scope.availableSongsPageMaxIndex = function () {
                    return Math.ceil($scope.availableSongs.length / $scope.songsRange);
                };
                $scope.songsRangeOptions = [10, 20, 50, 100];
                $scope.songsRange = $scope.songsRangeOptions[1];
                $scope.previousPage = function () {
                    if ($scope.songsPageIndex > 0)
                        $scope.songsPageIndex--;
                };
                $scope.nextPage = function () {
                    if ($scope.songsPageIndex < $scope.songsPageMaxIndex())
                        $scope.songsPageIndex++;
                };

                $scope.pushSong = function (artist, title) {
                    $scope.songs.push(new Song(artist, title));
                };

                $scope.showStep = function (step) {
                    return step == $scope.step;
                };

                $scope.setLoading = function (isLoading) {
                    if (isLoading) {
                        cfpLoadingBar.start();
                    } else {
                        cfpLoadingBar.complete();
                    }
                };

                $scope.paginatedSongs = function () {
                    var begin = $scope.songsRange * $scope.songsPageIndex;
                    return $scope.songs.slice(begin, begin + $scope.songsRange - 1);
                };

                $scope.paginatedAvailableSongs = function () {
                    var begin = $scope.songsRange * $scope.songsPageIndex;
                    return $scope.availableSongs.slice(begin, begin + $scope.songsRange - 1);
                };

                $scope.searchIndex = 0;
                $scope.searchOnSpotify = function () {
                    $scope.setLoading(true);
                    for (var i = 0; i < 10; i++) {
                        if (!$scope.searchNextSong())
                            return;
                    }
                };

                $scope.searchNextSong = function () {
                    if ($scope.step === 3)
                        return false;
                    if ($scope.searchIndex >= $scope.songs.length) {
                        $scope.setLoading(false);
                        $scope.step = 3;
                        $scope.songsToCopy = $scope.getURIs();
                        return false;
                    }

                    $http({
                        method: 'GET', url: 'https://api.spotify.com/v1/search?', params: {
                            'q': $scope.songs[$scope.searchIndex].artist +
                               ' ' + $scope.songs[$scope.searchIndex].title,
                            'type': 'track',
                            'index': $scope.searchIndex
                        }
                    }).
                    success(function (data, status, headers, config) {
                        if (data.tracks && data.tracks.items.length) {
                            $scope.songs[config.params.index].spotifyUri = data.tracks.items[0].uri;
                            $scope.songs[config.params.index].spotifyPreview = data.tracks.items[0].preview_url;
                            $sce.trustAsResourceUrl($scope.songs[config.params.index].spotifyPreview);
                            $scope.songs[config.params.index].isAvailable = true;
                            $scope.availableSongs.push($scope.songs[config.params.index]);
                        }
                        $scope.searchNextSong();
                    }).
                    error(function (data, status, headers, config) {
                        if (data.tracks && data.tracks.items.length) {
                            $scope.songs[config.params.index].isAvailable = false;
                        }
                        $scope.searchNextSong();
                    });

                    $scope.searchIndex++;
                    return true;
                };

                $scope.getURIs = function () {
                    var text = '';
                    for (var index in $scope.availableSongs) {
                        text += $scope.availableSongs[index].spotifyUri + '\r\n';
                    }
                    return text;
                };
            });
<commit_msg>fixed a bug that prevented collecting all the spotify URIs for the copy button<commit_after>angular.module('lastifyFilters', []).
    filter('isAvailable', function () {
        return function (input) {
            return input.isAvailable === window.undefinded || input.isAvailable;
        };
    });

var phonecatApp =
    angular.
        module('lastifyApp', ['cfp.loadingBar', 'lastifyFilters', 'ngSanitize']).
            controller('SongListCtrl', function ($scope, $http, cfpLoadingBar, $sce) {

                $scope.trustPreview = function (song) {
                    if (!song)
                        return "";
                    return $sce.trustAsResourceUrl(song.spotifyPreview);
                };

                $scope.playPreview = function (song) {
                    $scope.songPlaying = song;
                };
                $scope.stopPreview = function () {
                    $scope.songPlaying = null;
                };
                $scope.isPlaying = function (song) {
                    return $scope.songPlaying === song;
                };

                $scope.songPlaying = null;
                $scope.songs = [];
                $scope.availableSongs = [];

                $scope.step = 1;

                // Pagination
                $scope.songsPageIndex = 0;
                $scope.songsPageMaxIndex = function () {
                    return Math.ceil($scope.songs.length / $scope.songsRange);
                };
                $scope.availableSongsPageMaxIndex = function () {
                    return Math.ceil($scope.availableSongs.length / $scope.songsRange);
                };
                $scope.songsRangeOptions = [10, 20, 50, 100];
                $scope.songsRange = $scope.songsRangeOptions[1];
                $scope.previousPage = function () {
                    if ($scope.songsPageIndex > 0)
                        $scope.songsPageIndex--;
                };
                $scope.nextPage = function () {
                    if ($scope.songsPageIndex < $scope.songsPageMaxIndex())
                        $scope.songsPageIndex++;
                };

                $scope.pushSong = function (artist, title) {
                    $scope.songs.push(new Song(artist, title));
                };

                $scope.showStep = function (step) {
                    return step == $scope.step;
                };

                $scope.setLoading = function (isLoading) {
                    if (isLoading) {
                        cfpLoadingBar.start();
                    } else {
                        cfpLoadingBar.complete();
                    }
                };

                $scope.paginatedSongs = function () {
                    var begin = $scope.songsRange * $scope.songsPageIndex;
                    return $scope.songs.slice(begin, begin + $scope.songsRange - 1);
                };

                $scope.paginatedAvailableSongs = function () {
                    var begin = $scope.songsRange * $scope.songsPageIndex;
                    return $scope.availableSongs.slice(begin, begin + $scope.songsRange - 1);
                };

                $scope.searchIndex = 0;
                $scope.searchOnSpotify = function () {
                    $scope.setLoading(true);
                    for (var i = 0; i < 10; i++) {
                        if (!$scope.searchNextSong())
                            return;
                    }
                };

                $scope.searchNextSong = function () {
                    if ($scope.step === 3)
                        return false;
                    if ($scope.searchIndex >= $scope.songs.length) {
                        $scope.setLoading(false);
                        $scope.step = 3;
                        return false;
                    }

                    $http({
                        method: 'GET', url: 'https://api.spotify.com/v1/search?', params: {
                            'q': $scope.songs[$scope.searchIndex].artist +
                               ' ' + $scope.songs[$scope.searchIndex].title,
                            'type': 'track',
                            'index': $scope.searchIndex
                        }
                    }).
                    success(function (data, status, headers, config) {
                        if (data.tracks && data.tracks.items.length) {
                            $scope.songs[config.params.index].spotifyUri = data.tracks.items[0].uri;
                            $scope.songs[config.params.index].spotifyPreview = data.tracks.items[0].preview_url;
                            $sce.trustAsResourceUrl($scope.songs[config.params.index].spotifyPreview);
                            $scope.songs[config.params.index].isAvailable = true;
                            $scope.availableSongs.push($scope.songs[config.params.index]);
                        }
                        $scope.incrementRequestCounter();
                    }).
                    error(function (data, status, headers, config) {
                        if (data.tracks && data.tracks.items.length) {
                            $scope.songs[config.params.index].isAvailable = false;
                        }
                        $scope.incrementRequestCounter();
                    });

                    $scope.searchIndex++;
                    return true;
                };

                $scope.requestCount = 0;
                $scope.incrementRequestCounter = function () {
                    $scope.requestCount++;
                    if ($scope.requestCount === $scope.songs.length) {
                        $scope.songsToCopy = $scope.getURIs();
                    } else {
                        $scope.searchNextSong();
                    }
                };

                $scope.getURIs = function () {
                    var text = '';
                    for (var index in $scope.availableSongs) {
                        text += $scope.availableSongs[index].spotifyUri + '\r\n';
                    }
                    return text;
                };
            });
<|endoftext|>


<commit_before>test("Popcorn 0.3 JSON Parser Plugin", function () {

  var expects = 9,
      count = 0,
      timeOut = 0,
      numLoadingEvents = 5,
      trackData,
      trackEvents,
      interval,
      poppercorn = Popcorn( "#video" );

  function plus() {
    if ( ++count === expects ) {
      start();
      // clean up added events after tests
      clearInterval( interval );
    }
  }

  poppercorn.parseJSON("data/data.json");

  expect(expects);

  stop( 10000 );

  setTimeout(function () {

    trackData = poppercorn.data;
    trackEvents = trackData.trackEvents;

    equals( trackEvents.byStart.length,  numLoadingEvents + 2 , "trackEvents.byStart.length === (5 loaded, 2 padding) " );
    plus();

    Popcorn.xhr({
      url: 'data/data.json',
      success: function( data ) {

        var idx = 0;

        Popcorn.forEach( data.json.data, function (dataObj) {
          Popcorn.forEach( dataObj, function ( obj, key ) {

            equals( trackData.history[idx].indexOf(key), 0, "history item '" + trackData.history[idx] + "' matches data key '"+ key+ "' at correct index" );
            plus();

            idx++;
          });
        });

      }
    });

    poppercorn.listen("timeupdate", function ( event ) {

      if ( Math.round( this.currentTime()) === 3 ) {

        equals( $("#iframe-container").children().length, 2, '$("#iframe-container").children().length' )
        plus();
        equals( $("#map-container").children().length, 1, '$("#map-container").children().length'  );
        plus();
        equals( $("#footnote-container").children().length, 2, '$("#footnote-container").children().length'  );
        plus();

        this.pause();

      }

    });

    poppercorn.currentTime(0).play()

  }, 500);

  // interval used to wait for data to be parsed
  //interval = setInterval( function() {
    //poppercorn.currentTime(5).play().currentTime(6);
  //}, 2000);

});
<commit_msg>[#245] passing unit tests<commit_after>test("Popcorn 0.3 JSON Parser Plugin", function () {

  var expects = 9,
      count = 0,
      timeOut = 0,
      numLoadingEvents = 5,
      finished = false,
      trackData,
      trackEvents,
      interval,
      poppercorn = Popcorn( "#video" );

  function plus() {
    if ( ++count === expects ) {
      start();
      // clean up added events after tests
      clearInterval( interval );
    }
  }

  poppercorn.parseJSON("data/data.json");

  expect(expects);

  stop( 10000 );

  trackData = poppercorn.data;
  trackEvents = trackData.trackEvents;

  Popcorn.xhr({
    url: 'data/data.json',
    success: function( data ) {

      var idx = 0;

      Popcorn.forEach( data.json.data, function (dataObj) {
        Popcorn.forEach( dataObj, function ( obj, key ) {

          equals( trackData.history[idx].indexOf(key), 0, "history item '" + trackData.history[idx] + "' matches data key '"+ key+ "' at correct index" );
          plus();

          idx++;
        });
      });

    }
  });
  poppercorn.listen("timeupdate", function ( event ) {

    if ( Math.round( this.currentTime()) === 3 && !finished ) {

      finished = true;

      equals( trackEvents.byStart.length,  numLoadingEvents + 2 , "trackEvents.byStart.length === (5 loaded, 2 padding) " );
      plus();

      equals( $("#iframe-container").children().length, 2, '$("#iframe-container").children().length' )
      plus();
      equals( $("#map-container").children().length, 1, '$("#map-container").children().length'  );
      plus();
      equals( $("#footnote-container").children().length, 2, '$("#footnote-container").children().length'  );
      plus();

      this.pause();

    }

  });

  setTimeout(function () {

    poppercorn.currentTime(0).play()

  }, 500);

});
<|endoftext|>


<commit_before>(function (window, undefined) {
    var Callbacks = window.Callbacks,
        Deferred = function (func) {
            var state = 'pending', //当前代码状态
            deferred = {}, //正统deferred
            promise = {}, //阉割版的deferred对象
            resolve = Callbacks({ once: true, auto: true }), //成功后执行的回调函数集合
            reject = Callbacks({ once: true, auto: true }), //失败后执行的回调函数集合
            progress = Callbacks({ auto: true }); //注意这里并没有清空过去的函数库，每次标识notify的时候都会执行的回调函数集合
            //开始逻辑代码———————promise的扩展

            //获取当前promise的状态
            promise.state = function () {
                return state;
            }
            //将外面传递进来的一个对象扩展promise的方法，主要用于一个对象扩展promise行为
            promise.promise = function (obj) {
                //把自己的方法扩展进去，注意这个方法等一下可以用来扩展deferred
                return obj != null ? window.$.extend(obj, promise) : promise;
            };

            //———————deferred和promise公共部分的扩展

            //为promise扩展done/fail/progress方法，并不扩展改变状态的resolve/reject/notify，同时扩展到deferred
            //因为Callbacks里面返回是this，因为这里的引用关系，依赖在不同对象上面的this都指向了该对象，例如deferred.done返回的是deferred，可以继续支持链式回调
            promise.promise(promise.done = resolve.add); //注意这里调用了promise的promise方法，相当于给deferred扩展了同名的done,fail,progress方法
            promise.promise(promise.fail = reject.add);
            promise.promise(promise.progress = progress.add);
            //扩展always方法
            promise.promise(promise.always = function () {
                deferred.done(arguments).fail(arguments);
                return this;
            });
            /*
            promise对象构建完成，它有如下方法：
            promise.state() - 获取deferred状态
            promise.promise() - 返回deferred的promise，也用于扩展参数传递进来的对象[主要是把这些最上面定义的回调函数和对象关联上]
            promise.done() - 追加一个(或一组)成功状态下执行的回调函数
            promise.fail() - 追加一个(或一组)失败状态下执行的回调函数
            promise.progress() - 追加一个(或一组)无状态下执行的回调函数
            promise.always() - 追加一个(或一组)无论成功失败都会执行的回调函数
            promise.then() - 尚未扩展，最后扩展
            还有一个pipe()没有看懂，也应该扩展进去
            */
            //———————promise已经完成，下面是deferred的扩展
            //可以看见这些函数的本质就是Callbacks的fireWith()，传递的参数最终都会到达各自委托的Callbacks对象中的函数里
            deferred.resolveWith = resolve.fireWith;
            deferred.rejectWith = reject.fireWith;
            deferred.progressWith = progress.fireWith;

            //给成功的回调函数系列追加一组方法，执行done()的时候会执行这组方法
            resolve.add(function () {
                //触发成功系列的回调函数
                deferred.resolveWith(this === deferred ? promise : this, arguments);
            }, reject.disable,  //把互斥状态的回调函数都给废掉
               function () {
                   //因为我自己编写的Callbacks里面的lock支持无参(获取这个Callbacks是否已经被锁)，所以这里需要再封一层
                   progress.lock(true);
               });
            //给失败的回调函数系列追加一组方法，执行fail()的时候会执行这组方法
            reject.add(function () {
                //触发失败系列的回调函数
                deferred.rejectWith(this === deferred ? promise : this, arguments);
            }, resolve.disable, //把互斥状态的回调函数都给废掉
               function () {
                   //因为我自己编写的Callbacks里面的lock支持无参(获取这个Callbacks是否已经被锁)，所以这里需要再封一层
                   progress.lock(true);
               });
            //给无状态的回调函数系列追加一组方法，执行notify()的时候会执行这组方法
            progress.add(function () {
                //触发无状态系列的回调函数
                deferred.progressWith(this === deferred ? promise : this, arguments);
            }, reject.disable, //把互斥状态的回调函数都给废掉
               function () {
                   //因为我自己编写的Callbacks里面的lock支持无参(获取这个Callbacks是否已经被锁)，所以这里需要再封一层
                   progress.lock(true);
               });

            //这是最顶层Deferred()传递进来的参数，意思就是只要传递进来那么就执行它，这个获取是方便then里面的操作
            if (func) {
                func.call(deferred, deferred);
            }
            //下面是重头戏，promise的then()方法
            promise.then = function () {
                //then方法的本质就是把函数压到对应的Callbacks里面去
                var fns = arguments;
                //注意上面的这个匿名函数就是上面的func
                //参数是闭包里的deferred对象，所以data就是deferred
                return Deferred(function (data) {
                    //————————————————————————
                    //对应的方法都是Callbacks的add
                    deferred.done(function () {//这个匿名函数是Callbacks.fireWith()的时候执行的
                        //我们假设参数都是正确的函数，这里不对fn进行检测了
                        //这个方法在状态改变的时候就应该执行
                        var fn = fns[0];
                        //我们执行它
                        var returnData = fn && fn.apply(this, arguments);
                        //如果有promise(promise/A)的行为
                        if (returnData && returnData.promise) {
                            returnData.promise()
                            //想想resolve()方法，不就是Callbacks.fireWith()么？
                            //这样不就间接性的把一组函数给压到新的deferred对象中了么？
                              .done(data.resolve)
                              .fail(data.reject)
                              .progress(newDefer.notify);
                        } else {
                            //data一定是deferred对象
                            //如果fn()方法返回的对象没有promise(promise/A)行为[在这里，绝大多数promise行为源于Deferred]
                            //那么我们判定是否是then返回的promise
                            //判断这个触发的行为是否是promise的触发行为?但是promise有触发接口？？
                            //什么情况下有this===promise？？
                            //如果外面的fn返回有结果，那么把这个结果传递给下一层
                            data.resolveWith(this === promise ? data.promise() : this, fn ? [returnData] : arguments);
                        }
                    });
                    //这只是done，后面还有fail()和progress()，暂略....
                    fns = null;
                }).promise(); //返回被切掉小jj的promise
            };

            return deferred;
        };
    window.$.Deferred = window.Deferred;
} (window));
<commit_msg>加上了Deferred.then()的注释，但是仍然没有读懂then最里层的那个if方法的代码会在哪里执行<commit_after>(function (window, undefined) {
    var Callbacks = window.Callbacks,
        Deferred = function (func) {
            var state = 'pending', //当前代码状态
            deferred = {}, //正统deferred
            promise = {}, //阉割版的deferred对象
            resolve = Callbacks({ once: true, auto: true }), //成功后执行的回调函数集合
            reject = Callbacks({ once: true, auto: true }), //失败后执行的回调函数集合
            progress = Callbacks({ auto: true }); //注意这里并没有清空过去的函数库，每次标识notify的时候都会执行的回调函数集合
            //开始逻辑代码———————promise的扩展

            //获取当前promise的状态
            promise.state = function () {
                return state;
            }
            //将外面传递进来的一个对象扩展promise的方法，主要用于一个对象扩展promise行为
            promise.promise = function (obj) {
                //把自己的方法扩展进去，注意这个方法等一下可以用来扩展deferred
                return obj != null ? window.$.extend(obj, promise) : promise;
            };

            //———————deferred和promise公共部分的扩展

            //为promise扩展done/fail/progress方法，并不扩展改变状态的resolve/reject/notify，同时扩展到deferred
            //因为Callbacks里面返回是this，因为这里的引用关系，依赖在不同对象上面的this都指向了该对象，例如deferred.done返回的是deferred，可以继续支持链式回调
            promise.promise(promise.done = resolve.add); //注意这里调用了promise的promise方法，相当于给deferred扩展了同名的done,fail,progress方法
            promise.promise(promise.fail = reject.add);
            promise.promise(promise.progress = progress.add);
            //扩展always方法
            promise.promise(promise.always = function () {
                deferred.done(arguments).fail(arguments);
                return this;
            });
            /*
            promise对象构建完成，它有如下方法：
            promise.state() - 获取deferred状态
            promise.promise() - 返回deferred的promise，也用于扩展参数传递进来的对象[主要是把这些最上面定义的回调函数和对象关联上]
            promise.done() - 追加一个(或一组)成功状态下执行的回调函数
            promise.fail() - 追加一个(或一组)失败状态下执行的回调函数
            promise.progress() - 追加一个(或一组)无状态下执行的回调函数
            promise.always() - 追加一个(或一组)无论成功失败都会执行的回调函数
            promise.then() - 尚未扩展，最后扩展
            还有一个pipe()没有看懂，也应该扩展进去
            */
            //———————promise已经完成，下面是deferred的扩展
            //可以看见这些函数的本质就是Callbacks的fireWith()，传递的参数最终都会到达各自委托的Callbacks对象中的函数里
            deferred.resolveWith = resolve.fireWith;
            deferred.rejectWith = reject.fireWith;
            deferred.progressWith = progress.fireWith;

            //给成功的回调函数系列追加一组方法，执行done()的时候会执行这组方法
            resolve.add(function () {
                //触发成功系列的回调函数
                deferred.resolveWith(this === deferred ? promise : this, arguments);
            }, reject.disable,  //把互斥状态的回调函数都给废掉
               function () {
                   //因为我自己编写的Callbacks里面的lock支持无参(获取这个Callbacks是否已经被锁)，所以这里需要再封一层
                   progress.lock(true);
               });
            //给失败的回调函数系列追加一组方法，执行fail()的时候会执行这组方法
            reject.add(function () {
                //触发失败系列的回调函数
                deferred.rejectWith(this === deferred ? promise : this, arguments);
            }, resolve.disable, //把互斥状态的回调函数都给废掉
               function () {
                   //因为我自己编写的Callbacks里面的lock支持无参(获取这个Callbacks是否已经被锁)，所以这里需要再封一层
                   progress.lock(true);
               });
            //给无状态的回调函数系列追加一组方法，执行notify()的时候会执行这组方法
            progress.add(function () {
                //触发无状态系列的回调函数
                deferred.progressWith(this === deferred ? promise : this, arguments);
            }, reject.disable, //把互斥状态的回调函数都给废掉
               function () {
                   //因为我自己编写的Callbacks里面的lock支持无参(获取这个Callbacks是否已经被锁)，所以这里需要再封一层
                   progress.lock(true);
               });

            //这是最顶层Deferred()传递进来的参数，意思就是只要传递进来那么就执行它，这个获取是方便then里面的操作
            if (func) {
                func.call(deferred, deferred);
            }
            //下面是重头戏，promise的then()方法
            promise.then = function () {
                //then方法的本质就是把函数压到对应的Callbacks里面去
                var fns = arguments;
                //注意上面的这个匿名函数就是上面的func
                //参数是闭包里的deferred对象，所以data就是deferred
                return Deferred(function (data) {
                    //————————————————————————
                    //对应的方法都是Callbacks的add
                    deferred.done(function () {//这个匿名函数是Callbacks.fireWith()的时候执行的
                        //我们假设参数都是正确的函数，这里不对fn进行检测了
                        //这个方法在状态改变的时候就应该执行
                        var fn = fns[0];
                        //我们执行它
                        var returnData = fn && fn.apply(this, arguments);
                        //如果有promise(promise/A)的行为
                        if (returnData && returnData.promise) {
                            returnData.promise()
                            //想想resolve()方法，不就是Callbacks.fireWith()么？
                            //这样不就间接性的把一组函数给压到新的deferred对象中了么？
                              .done(data.resolve)
                              .fail(data.reject)
                              .progress(newDefer.notify);
                        } else {
                            //data一定是deferred对象
                            //如果fn()方法返回的对象没有promise(promise/A)行为[在这里，绝大多数promise行为源于Deferred]
                            //那么我们判定是否是then返回的promise
                            //判断这个触发的行为是否是promise的触发行为?但是promise有触发接口？？
                            //什么情况下有this===promise？？
                            //如果外面的fn返回有结果，那么把这个结果传递给下一层
                            //这里指明了this，Callbacks里最终返回是this，这里的this指向一个promise对象
                            data.resolveWith(this === promise ? data.promise() : this, fn ? [returnData] : arguments);
                        }
                    });
                    //这只是done，后面还有fail()和progress()，暂略....
                    fns = null;
                }).promise(); //返回被切掉小jj的promise
            };

            return deferred;
        };
    window.$.Deferred = window.Deferred;
} (window));
<|endoftext|>


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

var LOCK_URL = 'http://192.168.0.3:8080/';
var SECONDS_TO_LEAVE = 300;

var URL_WHITELIST = new Set([
    'https://straylight.jp/one/00001',
    'https://straylight.jp/one/00002',
    'https://straylight.jp/one/vk2g7',
    'https://straylight.jp/one/b4cz6',
    'https://straylight.jp/one/6ej7n',
    'https://straylight.jp/one/u36bx',
    'https://straylight.jp/one/9y2tk',
    'https://straylight.jp/one/33fxm',
    'https://straylight.jp/one/ujv3w',
    'https://straylight.jp/one/zz6n7',
]);
var MAC_ADDRESS_WHITELIST = new Map([
    ['F0:2A:63:5C:E3:E5', ['SLBeacon99998', 'Ryo']],
    ['EB:B4:73:21:AC:3C', ['SLBeacon99997', '']],
    ['D7:AF:DA:DF:43:85', ['SLBeacon99999', 'Taj']],
    ['E6:64:E0:C6:40:F1', ['SLBeacon00001', 'Alisaun']],
    ['CA:5F:63:5B:17:D5', ['SLBeacon00002', 'Lauren']],
    ['D6:60:A7:9F:E5:DF', ['SLBeacon00003', 'Daniel']],
    ['C7:D7:61:92:28:85', ['SLBeacon00004', 'Roy']],
    ['EF:EA:6A:BD:C7:29', ['SLBeacon00005', 'Jake']],
    ['D5:DB:E9:EE:D8:BB', ['SLBeacon00006', 'Keigo']],
    ['E7:A1:7A:F0:41:A6', ['SLBeacon00007', 'Ikue']],
    // ['D8:3E:FB:33:28:FC', ['SLBeacon00008', '']],
    // ['C2:12:FB:37:74:C6', ['SLBeacon00009', '']],
    // ['C2:12:FB:37:74:C6', ['SLBeacon00010', '']],
    // ['E3:DA:76:79:72:A2', ['SLBeacon00011', '']],
    // ['CD:E9:12:5F:27:42', ['SLBeacon00012', '']],
]);

var presentMacAddressSet = new Set();
var leavingMacAddressSet = new Set();
var leftoverMacAddressSet = new Set();
var leavingMacAddressClearTimer;
var latestJpegData;

var sendUnlockAction = utils.throttle(5000, function() {
  utils.get(LOCK_URL + 'unlock');
});

function pulseLEDs() {
  utils.get('http://192.168.0.6:8080/pulse(200,200,200,1,3)');
}

function clearAfterUnlock() {
  leavingMacAddressSet.clear();
  leftoverMacAddressSet.clear();
  clearTimeout(leavingMacAddressClearTimer);
}

function unlock() {
  sendUnlockAction();
  clearAfterUnlock();
  pulseLEDs();
}

function processNfc(url) {
  console.info('processNfc: ' + url);
  if (URL_WHITELIST.has(url)) {
    console.info('UNLOCKING with NFC: ' + url);
    unlock();
  }
}

function processBle(macAddress, rssi) {
  console.info('processBle: ' + macAddress + ' RSSI=' + rssi);
  if (rssi >= 0) {
    presentMacAddressSet.delete(macAddress);
    logPresentMembers();
  } else if (MAC_ADDRESS_WHITELIST.has(macAddress) &&
             !leavingMacAddressSet.has(macAddress) &&
             !leftoverMacAddressSet.has(macAddress) &&
             !presentMacAddressSet.has(macAddress)) {
    console.info('UNLOCKING with BLE: ' + macAddress);
    presentMacAddressSet.add(macAddress);
    logPresentMembers();
    unlock();
  }
}

function logPresentMembers() {
  var presentNames = [];
  presentMacAddressSet.forEach(function(macAddress) {
    var name = MAC_ADDRESS_WHITELIST.get(macAddress)[1];
    presentNames.push(name);
  });
  console.info('Present members: ' + presentNames);
}

function processLockStateChange(state) {
  console.info('processLockStateChange: ' + state);
  if (state == 'locked') {
    console.info('LOCKED');

    leavingMacAddressSet = new Set(presentMacAddressSet);
    console.info('Leaving IDs: ' + [...leavingMacAddressSet]);

    clearTimeout(leavingMacAddressClearTimer);
    leavingMacAddressClearTimer = setTimeout(function() {
      leavingMacAddressSet.clear();

      leftoverMacAddressSet = new Set(presentMacAddressSet);
      console.info('Leftover IDs: ' + [...leftoverMacAddressSet]);
    }, SECONDS_TO_LEAVE * 1000);
  } else if (state == 'unlocked') {
    console.info('UNLOCKED');
    clearAfterUnlock();
  } else if (state == 'unreachable') {
    console.info('UNREACHABLE');
  } else {
    console.error('Unknown lock state: ' + state);
  }
}

function processImage(data) {
  latestJpegData = Buffer.from(data, 'base64');
}

function process(data) {
  if (data.type == 'nfc') {
    processNfc(data.url);
  } else if (data.type == 'ble') {
    processBle(data.macAddress, data.rssi);
  } else if (data.type == 'lockStateChange') {
    processLockStateChange(data.state);
  } else if (data.type == 'image') {
    processImage(data.data);
  } else {
    console.error('Unknown message type: ' + data.type);
  }
}

exports.post = function(req, res) {
  process(req.body);
  res.send('OK');
}

exports.socket = function(message) {
  var data = JSON.parse(message);
  process(data);
}

exports.getLatestImage = function(req, res) {
  if (!latestJpegData) {
    return res.status(404).send('Not found');
  }
  res.writeHead(200, {'Content-Type': 'image/jpeg'});
  res.end(latestJpegData);
}

<commit_msg>Detect lost ble signals.<commit_after>const utils = require('./utils');

var LOCK_URL = 'http://192.168.0.3:8080/';
var SECONDS_TO_LEAVE = 300;

var URL_WHITELIST = new Set([
    'https://straylight.jp/one/00001',
    'https://straylight.jp/one/00002',
    'https://straylight.jp/one/vk2g7',
    'https://straylight.jp/one/b4cz6',
    'https://straylight.jp/one/6ej7n',
    'https://straylight.jp/one/u36bx',
    'https://straylight.jp/one/9y2tk',
    'https://straylight.jp/one/33fxm',
    'https://straylight.jp/one/ujv3w',
    'https://straylight.jp/one/zz6n7',
]);
var MAC_ADDRESS_WHITELIST = new Map([
    ['F0:2A:63:5C:E3:E5', ['SLBeacon99998', 'Ryo']],
    ['EB:B4:73:21:AC:3C', ['SLBeacon99997', '']],
    ['D7:AF:DA:DF:43:85', ['SLBeacon99999', 'Taj']],
    ['E6:64:E0:C6:40:F1', ['SLBeacon00001', 'Alisaun']],
    ['CA:5F:63:5B:17:D5', ['SLBeacon00002', 'Lauren']],
    ['D6:60:A7:9F:E5:DF', ['SLBeacon00003', 'Daniel']],
    ['C7:D7:61:92:28:85', ['SLBeacon00004', 'Roy']],
    ['EF:EA:6A:BD:C7:29', ['SLBeacon00005', 'Jake']],
    ['D5:DB:E9:EE:D8:BB', ['SLBeacon00006', 'Keigo']],
    ['E7:A1:7A:F0:41:A6', ['SLBeacon00007', 'Ikue']],
    // ['D8:3E:FB:33:28:FC', ['SLBeacon00008', '']],
    // ['C2:12:FB:37:74:C6', ['SLBeacon00009', '']],
    // ['C2:12:FB:37:74:C6', ['SLBeacon00010', '']],
    // ['E3:DA:76:79:72:A2', ['SLBeacon00011', '']],
    // ['CD:E9:12:5F:27:42', ['SLBeacon00012', '']],
]);

var lastSeenMap = new Map();
var presentMacAddressSet = new Set();
var leavingMacAddressSet = new Set();
var leftoverMacAddressSet = new Set();
var leavingMacAddressClearTimer;
var latestJpegData;

var sendUnlockAction = utils.throttle(5000, function() {
  utils.get(LOCK_URL + 'unlock');
});

function pulseLEDs() {
  utils.get('http://192.168.0.6:8080/pulse(200,200,200,1,3)');
}

function clearAfterUnlock() {
  leavingMacAddressSet.clear();
  leftoverMacAddressSet.clear();
  clearTimeout(leavingMacAddressClearTimer);
}

function unlock() {
  sendUnlockAction();
  clearAfterUnlock();
  pulseLEDs();
}

function processNfc(url) {
  console.info('processNfc: ' + url);
  if (URL_WHITELIST.has(url)) {
    console.info('UNLOCKING with NFC: ' + url);
    unlock();
  }
}

function processBle(macAddress, rssi) {
  console.info('processBle: ' + macAddress + ' RSSI=' + rssi);
  lastSeenMap.set(macAddress, new Date().getTime());
  if (MAC_ADDRESS_WHITELIST.has(macAddress) &&
      !leavingMacAddressSet.has(macAddress) &&
      !leftoverMacAddressSet.has(macAddress) &&
      !presentMacAddressSet.has(macAddress)) {
    console.info('UNLOCKING with BLE: ' + macAddress);
    presentMacAddressSet.add(macAddress);
    logPresentMembers();
    unlock();
  }
}

setInterval(function() {
  var now = new Date().getTime();
  for (var [macAddress, lastSeen] of lastSeenMap) {
    if (lastSeen !== undefined && now - lastSeen >= 300) {
      presentMacAddressSet.delete(macAddress);
      logPresentMembers();
      lastSeenMap[macAddress] = undefined;
    }
  }
}, 60 * 1000);

function logPresentMembers() {
  var presentNames = [];
  presentMacAddressSet.forEach(function(macAddress) {
    var name = MAC_ADDRESS_WHITELIST.get(macAddress)[1];
    presentNames.push(name);
  });
  console.info('Present members: ' + presentNames);
}

function processLockStateChange(state) {
  console.info('processLockStateChange: ' + state);
  if (state == 'locked') {
    console.info('LOCKED');

    leavingMacAddressSet = new Set(presentMacAddressSet);
    console.info('Leaving IDs: ' + [...leavingMacAddressSet]);

    clearTimeout(leavingMacAddressClearTimer);
    leavingMacAddressClearTimer = setTimeout(function() {
      leavingMacAddressSet.clear();

      leftoverMacAddressSet = new Set(presentMacAddressSet);
      console.info('Leftover IDs: ' + [...leftoverMacAddressSet]);
    }, SECONDS_TO_LEAVE * 1000);
  } else if (state == 'unlocked') {
    console.info('UNLOCKED');
    clearAfterUnlock();
  } else if (state == 'unreachable') {
    console.info('UNREACHABLE');
  } else {
    console.error('Unknown lock state: ' + state);
  }
}

function processImage(data) {
  latestJpegData = Buffer.from(data, 'base64');
}

function process(data) {
  if (data.type == 'nfc') {
    processNfc(data.url);
  } else if (data.type == 'ble') {
    processBle(data.macAddress, data.rssi);
  } else if (data.type == 'lockStateChange') {
    processLockStateChange(data.state);
  } else if (data.type == 'image') {
    processImage(data.data);
  } else {
    console.error('Unknown message type: ' + data.type);
  }
}

exports.post = function(req, res) {
  process(req.body);
  res.send('OK');
}

exports.socket = function(message) {
  var data = JSON.parse(message);
  process(data);
}

exports.getLatestImage = function(req, res) {
  if (!latestJpegData) {
    return res.status(404).send('Not found');
  }
  res.writeHead(200, {'Content-Type': 'image/jpeg'});
  res.end(latestJpegData);
}

<|endoftext|>


<commit_before>var d3 = require('d3-selection')
  , partialRight = require('./partial-right')

module.exports = function (tree) {
  return function (enter, transformStyle, cssClasses) {
    var transitions = tree.el.select('.tree').classed('transitions')

    enter.attr('class', 'node ' + (cssClasses || ''))
         .classed('selected', function (d) {
           return d.selected
         })
         .on('click', partialRight(tree._onSelect.bind(tree), tree.options))
         .style(tree.prefix + 'transform', transformStyle)
         .call(tree.options.contents, tree, transitions)

    if (transitions) {
      enter.classed('transitioning-node', true)
      tree._forceRedraw() // Force a redraw so we see the updates transitioning
      enter.classed('transitioning-node', false)
    }

    return enter
  }
}
<commit_msg>Uses `classed` on enter nodes instead of overriding classes.<commit_after>var d3 = require('d3-selection')
  , partialRight = require('./partial-right')

module.exports = function (tree) {
  return function (enter, transformStyle, cssClasses) {
    var transitions = tree.el.select('.tree').classed('transitions')

    enter.classed('node ' + (cssClasses || ''), true) // Don't set `attr('class')` b/c it could overwrite some previous setting
         .classed('selected', function (d) {
           return d.selected
         })
         .on('click', partialRight(tree._onSelect.bind(tree), tree.options))
         .style(tree.prefix + 'transform', transformStyle)
         .call(tree.options.contents, tree, transitions)

    if (transitions) {
      enter.classed('transitioning-node', true)
      tree._forceRedraw() // Force a redraw so we see the updates transitioning
      enter.classed('transitioning-node', false)
    }

    return enter
  }
}
<|endoftext|>


<commit_before>// System Objects
// ...

// Third Party Dependencies
var async = require('async');

// Internal
var log = require('./log');

// Constants
var PAGE = 256;
var CHIP_ID = new Buffer([0x01, 0x02, 0x19]);

function address(addr) {
  return [(addr >> 24) & 0xFF, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF];
}

// Generate a mediatek factory partition
function factoryPartition(mac1, mac2) {
  return toBuffer(
    [0x20, 0x76, 0x03, 0x01],
    mac1,
    Array(30).fill(0xff),
    mac2
  );
}

function transaction (usbConnection, write, read, status_poll, wren, next) {
  read = read || 0;
  status_poll = status_poll || false;
  wren = wren || false;

  if (write.length > 500 || read >= Math.pow(2, 24)) {
    return next(new Error('Transaction too large'));
  }
  var flags = (status_poll ? 1 : 0) | ((wren ? 1 : 0) << 1);
  var hdr = [(read >> 0) & 0xff, (read >> 8) & 0xff, (read >> 16) & 0xff, flags];

  var data = toBuffer(hdr, write);

  usbConnection.epOut.transfer(data, (err) => {
    if (err) {
      return next(err);
    }
    if (read > 0) {
      return usbConnection.epIn.transfer(read, next);
    }
    next();
  });
}

function readChipId(usbConnection, next) {
  transaction(usbConnection, [0x9f], 3, null, null, next);
}

function checkChipId(chipId, next) {
  if (Buffer.compare(chipId, CHIP_ID) !== 0) {
    return next(new Error('Invalid chip ID (flash communication error)'));
  }
  next();
}

function setWriteEnabled(usbConnection, next) {
  transaction(usbConnection, [0x06], null, null, null, next);
}

function eraseChip(usbConnection, next) {
  transaction(usbConnection, [0x60], null, null, null, next);
}

// Poll for the WIP bit in the status register to go low
function waitTransactionComplete (usbConnection, next) {
  setTimeout(function onWait () {
    getStatus(usbConnection, (err, status) => {
      if (err) {
        return next(err);
      }
      if (status === 0) {
        return next();
      }
      process.nextTick(waitTransactionComplete, usbConnection, next);
    });
  }, 200);
}

// Read the status register
function getStatus(usbConnection, next) {
  transaction(usbConnection, [0x05], 1, null, null,(err, data) => {
    next(err, data ? data[0] : data);
  });
}

function write(usbConnection, writeAddress, data, sliceStart, next) {
  if (!next) {
    next = sliceStart;
    sliceStart = 0;
  }
  if (sliceStart >= data.length) {
    return next();
  }

  var sliceEnd = sliceStart + PAGE;
  var pageData = data.slice(sliceStart, sliceEnd);
  writePage(usbConnection, writeAddress, pageData, (err) => {
    if (err) {
      return next(err);
    }
    process.nextTick(write, usbConnection, writeAddress + PAGE, data, sliceEnd, next);
  });
}

function toBuffer() {
  return Buffer.concat( Array.from(arguments).map (
    (arg) => Array.isArray(arg) ? new Buffer(arg) : arg
  ));
}

// Write a page to flash
function writePage(usbConnection, addr, data, next) {
  var status_poll = true;
  var wren = true;
  transaction(
    usbConnection,
    toBuffer([0x12], address(addr), data),
    null,
    status_poll,
    wren,
    next
  );
}

// Returns a random integer, both low and high are inclusive
function randint(low, high) {
  return Math.floor(Math.random() * (high - low + 1) + low);
}

function randbyte() {
  return randint(0, 255);
}

function asyncLog(msg) {
  return function onAsyncLog(next) {
    log.info(msg);
    next();
  };
}

module.exports = function flashDevice(usbConnection, ubootBuffer, squashfsBuffer) {
  return new Promise((resolve, reject)=> {
    var uid = [randbyte(), randbyte(), randbyte(), randbyte()];
    var mac1 = [0x02, 0xa3].concat(uid);
    var mac2 = [0x02, 0xa4].concat(uid);

    async.waterfall([
      asyncLog('Checking the chip id...'),
      readChipId.bind(null, usbConnection),
      checkChipId,

      asyncLog('Erasing the chip...'),
      setWriteEnabled.bind(null, usbConnection),
      eraseChip.bind(null, usbConnection),
      waitTransactionComplete.bind(null, usbConnection),

      asyncLog('Writing uboot...'),
      write.bind(null, usbConnection, 0, ubootBuffer),
      waitTransactionComplete.bind(null, usbConnection),

      asyncLog('Writing mediatek factory partition...'),
      write.bind(null, usbConnection, 0x40000, factoryPartition(mac1, mac2)),
      waitTransactionComplete.bind(null, usbConnection),

      asyncLog('Writing squashfs...'),
      write.bind(null, usbConnection, 0x50000, squashfsBuffer),
      waitTransactionComplete.bind(null, usbConnection),
      asyncLog('The update was successful.'),
      asyncLog('Please power cycle your Tessel.')

    ], (err) => err ? reject(err) : resolve());
  });
};
<commit_msg>Add time notice for erasing the chip.<commit_after>// System Objects
// ...

// Third Party Dependencies
var async = require('async');

// Internal
var log = require('./log');

// Constants
var PAGE = 256;
var CHIP_ID = new Buffer([0x01, 0x02, 0x19]);

function address(addr) {
  return [(addr >> 24) & 0xFF, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF];
}

// Generate a mediatek factory partition
function factoryPartition(mac1, mac2) {
  return toBuffer(
    [0x20, 0x76, 0x03, 0x01],
    mac1,
    Array(30).fill(0xff),
    mac2
  );
}

function transaction (usbConnection, write, read, status_poll, wren, next) {
  read = read || 0;
  status_poll = status_poll || false;
  wren = wren || false;

  if (write.length > 500 || read >= Math.pow(2, 24)) {
    return next(new Error('Transaction too large'));
  }
  var flags = (status_poll ? 1 : 0) | ((wren ? 1 : 0) << 1);
  var hdr = [(read >> 0) & 0xff, (read >> 8) & 0xff, (read >> 16) & 0xff, flags];

  var data = toBuffer(hdr, write);

  usbConnection.epOut.transfer(data, (err) => {
    if (err) {
      return next(err);
    }
    if (read > 0) {
      return usbConnection.epIn.transfer(read, next);
    }
    next();
  });
}

function readChipId(usbConnection, next) {
  transaction(usbConnection, [0x9f], 3, null, null, next);
}

function checkChipId(chipId, next) {
  if (Buffer.compare(chipId, CHIP_ID) !== 0) {
    return next(new Error('Invalid chip ID (flash communication error)'));
  }
  next();
}

function setWriteEnabled(usbConnection, next) {
  transaction(usbConnection, [0x06], null, null, null, next);
}

function eraseChip(usbConnection, next) {
  transaction(usbConnection, [0x60], null, null, null, next);
}

// Poll for the WIP bit in the status register to go low
function waitTransactionComplete (usbConnection, next) {
  setTimeout(function onWait () {
    getStatus(usbConnection, (err, status) => {
      if (err) {
        return next(err);
      }
      if (status === 0) {
        return next();
      }
      process.nextTick(waitTransactionComplete, usbConnection, next);
    });
  }, 200);
}

// Read the status register
function getStatus(usbConnection, next) {
  transaction(usbConnection, [0x05], 1, null, null,(err, data) => {
    next(err, data ? data[0] : data);
  });
}

function write(usbConnection, writeAddress, data, sliceStart, next) {
  if (!next) {
    next = sliceStart;
    sliceStart = 0;
  }
  if (sliceStart >= data.length) {
    return next();
  }

  var sliceEnd = sliceStart + PAGE;
  var pageData = data.slice(sliceStart, sliceEnd);
  writePage(usbConnection, writeAddress, pageData, (err) => {
    if (err) {
      return next(err);
    }
    process.nextTick(write, usbConnection, writeAddress + PAGE, data, sliceEnd, next);
  });
}

function toBuffer() {
  return Buffer.concat( Array.from(arguments).map (
    (arg) => Array.isArray(arg) ? new Buffer(arg) : arg
  ));
}

// Write a page to flash
function writePage(usbConnection, addr, data, next) {
  var status_poll = true;
  var wren = true;
  transaction(
    usbConnection,
    toBuffer([0x12], address(addr), data),
    null,
    status_poll,
    wren,
    next
  );
}

// Returns a random integer, both low and high are inclusive
function randint(low, high) {
  return Math.floor(Math.random() * (high - low + 1) + low);
}

function randbyte() {
  return randint(0, 255);
}

function asyncLog(msg) {
  return function onAsyncLog(next) {
    log.info(msg);
    next();
  };
}

module.exports = function flashDevice(usbConnection, ubootBuffer, squashfsBuffer) {
  return new Promise((resolve, reject)=> {
    var uid = [randbyte(), randbyte(), randbyte(), randbyte()];
    var mac1 = [0x02, 0xa3].concat(uid);
    var mac2 = [0x02, 0xa4].concat(uid);

    async.waterfall([
      asyncLog('Checking the chip id...'),
      readChipId.bind(null, usbConnection),
      checkChipId,

      asyncLog('Erasing the chip.  This step takes about a minute...'),
      setWriteEnabled.bind(null, usbConnection),
      eraseChip.bind(null, usbConnection),
      waitTransactionComplete.bind(null, usbConnection),

      asyncLog('Writing uboot...'),
      write.bind(null, usbConnection, 0, ubootBuffer),
      waitTransactionComplete.bind(null, usbConnection),

      asyncLog('Writing mediatek factory partition...'),
      write.bind(null, usbConnection, 0x40000, factoryPartition(mac1, mac2)),
      waitTransactionComplete.bind(null, usbConnection),

      asyncLog('Writing squashfs...'),
      write.bind(null, usbConnection, 0x50000, squashfsBuffer),
      waitTransactionComplete.bind(null, usbConnection),
      asyncLog('The update was successful.'),
      asyncLog('Please power cycle your Tessel.')

    ], (err) => err ? reject(err) : resolve());
  });
};
<|endoftext|>


<commit_before>'use strict';

let request = require('request');
let q = require('q');
let moment = require('moment');
let {
	extractZip,
	readFile
} = require('../../filesdk');

let {
	config
} = require('../config');

let {
	generateOriginalJWTs
} = require('../helpers/cloud.helper.js');

let getState = () => {
	let {
		userToken,
		authToken,
		baseUrl,
		searchUrlSuffix,
		filter
	} = config.cloudAPI;

	let { keysToUse } = filter;

	return {
		userToken(token) {
			userToken = token || userToken;
			return userToken;
		},

		authToken(token) {
			authToken = token || authToken;
			return authToken;
		},

		searchUrl(suffix) {
			searchUrlSuffix = suffix || searchUrlSuffix;
			return `${baseUrl}${searchUrlSuffix}`;
		},

		keysToUse(keys) {
			keysToUse = keys || keysToUse;
			return keysToUse;
		}
	};
};

let getTimestamp = (pattern) => moment().format(pattern);

let requestWithPromise = (options) => {
	let defer = q.defer();

	request(options, (err, res) => {
		if (err) {
			defer.reject(err);
		} else {
			defer.resolve(res);
		}
	});

	return defer.promise;
};

let getSearchHeaders = () => {
	const pattern = 'YYYY-MM-DD HH:mm:ss:SSSZZ';
	const timestamp = getTimestamp(pattern);

	const state = getState();

	return {
		'Accept': 'application/json',
		'Content-Type': 'application/json',
		'X-Consumer-ID': '029c72b5-4691-4bf2-a6de-72b18df0b748',
		'ts': timestamp,
		'X-msgid': '8e27cbf5-e299-43b0-bca7-8347f7e5abcf',
		'X-Device-ID': 'X-Device-ID',
		'X-Authenticated-User-Token': state.userToken(),
		'X-Source': 'app',
		'Authorization': `Bearer ${state.authToken()}`
	};
};

let getSearchBody = (queryString = '', limit, offset, filters = {}, fields = []) => {
	const pattern = 'YYYY/MM/DD HH:mm:ss';
	const timestamp = getTimestamp(pattern);

	return {
		'id': 'unique API ID',
		'ts': timestamp,
		'params': {
		},
		'request': {
			'query': queryString,
			'filters': {
				...filters,
				'status': ['Live'],
				'contentType': ['Collection', 'Story', 'Worksheet', 'TextBook', 'Course', 'LessonPlan', 'Resource']
			},
			fields,
			limit,
			offset
		}
	};
};

let getRequestOptions = (method, uri, body, headers, json = true) => ({
	method,
	uri,
	body,
	headers,
	json
});

// Search diksha cloud with the query string, limit of results to be returned, and offset of results
let searchDikshaCloud = ({ query, limit, offset, filters, fields }) => {
	const state = getState();
	const body = getSearchBody(query, +limit, +offset, filters, fields);
	const headers = getSearchHeaders();
	const uri = state.searchUrl();
	const options = getRequestOptions('POST', uri, body, headers);

	return requestWithPromise(options);
};

let fetchImage = (src) => {
	const options = {
		...getRequestOptions('GET', src),
		encoding: null
	};

	return requestWithPromise(options);
};

let replaceUrlWithImageData = (obj) => {
	const src = obj.appIcon || 'https://react.semantic-ui.com/images/wireframe/image.png';

	return fetchImage(src)
		.then(({ body }) => {
			const res = {
				...obj,
				appIcon: body.toString('base64')
			};

			return res;
		});
};

let filterObjectKeysAndAddImageData = (obj = {}, keysToUse) => {
	const state = getState();
	let filteredObj = {};

	keysToUse = keysToUse || state.keysToUse();

	keysToUse.forEach(key => filteredObj[key] = obj[key]);

	return replaceUrlWithImageData(filteredObj);
};

let manipulateKeysInObjectList = (objectList = [], manipulator) => {
	return objectList.map(obj => manipulator(obj));
};

let searchContent = (req, res) => {
	let response = {
		success: false,
		err: null,
		hits: null
	};

	const state = getState();

	let count = 0;
	let content = [];

	generateOriginalJWTs()
		.then(searchDikshaCloud(req.query))
		.then(({ body }) => {
			if (!(body.params.status === 'successful')) {
				throw new Error(body.params.err);
			}

			count = body.result.count;

			const contentPromises = manipulateKeysInObjectList(body.result.content, filterObjectKeysAndAddImageData);
			return q.allSettled(contentPromises);
		})
		.then(data => {
			content = data.map(item => item.value);

			response = {
				...response,
				success: true,
				hits: {
					count,
					content
				}
			};

			res.set('Cache-Control', ' no-store, no-cache');
			res.status(200).json(response);
		})
		.catch(err => {
			console.log('Error occurred while searching.');
			console.log(err);

			response = {
				...response,
				err: 'Some error occurred while searching.'
			};

			res.status(200).json(response);
		});
};

let extractDependencyIdList = (manifest, parentId) => {
	const items = manifest.archive.items;
	const depIdList = items
		.map(({ identifier }) => identifier !== parentId ? identifier : null)
		.filter(item => item !== null && item !== undefined);
	const uniqueDepIdList = Array.from(new Set(depIdList));

	return uniqueDepIdList;
}

let getDependencies = (req, res) => {
	const { parent } = req.params;
	const src = `${config.FS_ROOT}diksha/${parent}`; // TODO: Make configurable
	const dest = `/tmp/${parent}`;

	let responseData = {
		success: false,
		err: null,
		data: null
	};

	extractZip(src, dest)
		.then(data => {
			return readFile(`${dest}/manifest.json`);
		})
		.then(manifest => {
			const parentId = parent.substring(0, parent.lastIndexOf('_'));
			const depIdList = extractDependencyIdList(JSON.parse(manifest), parentId);

			if (depIdList && depIdList.length > 0) {
				const reqQuery = {
					filters: {
						identifier: depIdList
					},
					fields: ['downloadUrl', 'pkgVersion', 'size', 'name']
				};

				return searchDikshaCloud(reqQuery);
			} else {
				return null;
			}
		})
		.then(depSearchResults => {
			const depDataList = depSearchResults !== null ? depSearchResults.body.result.content : [];

			responseData = {
				...responseData,
				success: true,
				data: depDataList
			};

			res.status(200).json(responseData);
		})
		.catch(err => {
			console.log(`Error occured while fetching ${parent}'s deps.`);
			console.log(err);

			responseData = {
				...responseData,
				success: false,
				err
			};

			res.status(200).json(responseData);
		});
};

module.exports = {
	searchContent,
	getDependencies
};
<commit_msg>Minor syntactical change<commit_after>'use strict';

let request = require('request');
let q = require('q');
let moment = require('moment');
let {
	extractZip,
	readFile
} = require('../../filesdk');

let {
	config
} = require('../config');

let {
	generateOriginalJWTs
} = require('../helpers/cloud.helper.js');

let getState = () => {
	let {
		userToken,
		authToken,
		baseUrl,
		searchUrlSuffix,
		filter
	} = config.cloudAPI;

	let { keysToUse } = filter;

	return {
		userToken(token) {
			userToken = token || userToken;
			return userToken;
		},

		authToken(token) {
			authToken = token || authToken;
			return authToken;
		},

		searchUrl(suffix) {
			searchUrlSuffix = suffix || searchUrlSuffix;
			return `${baseUrl}${searchUrlSuffix}`;
		},

		keysToUse(keys) {
			keysToUse = keys || keysToUse;
			return keysToUse;
		}
	};
};

let getTimestamp = (pattern) => moment().format(pattern);

let requestWithPromise = (options) => {
	let defer = q.defer();

	request(options, (err, res) => {
		if (err) {
			defer.reject(err);
		} else {
			defer.resolve(res);
		}
	});

	return defer.promise;
};

let getSearchHeaders = () => {
	const pattern = 'YYYY-MM-DD HH:mm:ss:SSSZZ';
	const timestamp = getTimestamp(pattern);

	const state = getState();

	return {
		'Accept': 'application/json',
		'Content-Type': 'application/json',
		'X-Consumer-ID': '029c72b5-4691-4bf2-a6de-72b18df0b748',
		'ts': timestamp,
		'X-msgid': '8e27cbf5-e299-43b0-bca7-8347f7e5abcf',
		'X-Device-ID': 'X-Device-ID',
		'X-Authenticated-User-Token': state.userToken(),
		'X-Source': 'app',
		'Authorization': `Bearer ${state.authToken()}`
	};
};

let getSearchBody = (queryString = '', limit, offset, filters = {}, fields = []) => {
	const pattern = 'YYYY/MM/DD HH:mm:ss';
	const timestamp = getTimestamp(pattern);

	return {
		'id': 'unique API ID',
		'ts': timestamp,
		'params': {
		},
		'request': {
			'query': queryString,
			'filters': {
				...filters,
				'status': ['Live'],
				'contentType': ['Collection', 'Story', 'Worksheet', 'TextBook', 'Course', 'LessonPlan', 'Resource']
			},
			fields,
			limit,
			offset
		}
	};
};

let getRequestOptions = (method, uri, body, headers, json = true) => ({
	method,
	uri,
	body,
	headers,
	json
});

// Search diksha cloud with the query string, limit of results to be returned, and offset of results
let searchDikshaCloud = ({ query, limit, offset, filters, fields }) => {
	const state = getState();
	const body = getSearchBody(query, +limit, +offset, filters, fields);
	const headers = getSearchHeaders();
	const uri = state.searchUrl();
	const options = getRequestOptions('POST', uri, body, headers);

	return requestWithPromise(options);
};

let fetchImage = (src) => {
	const options = {
		...getRequestOptions('GET', src),
		encoding: null
	};

	return requestWithPromise(options);
};

let replaceUrlWithImageData = (obj) => {
	const src = obj.appIcon || 'https://react.semantic-ui.com/images/wireframe/image.png';

	return fetchImage(src)
		.then(({ body }) => {
			const res = {
				...obj,
				appIcon: body.toString('base64')
			};

			return res;
		});
};

let filterObjectKeysAndAddImageData = (obj = {}, keysToUse) => {
	const state = getState();
	let filteredObj = {};

	keysToUse = keysToUse || state.keysToUse();

	keysToUse.forEach(key => filteredObj[key] = obj[key]);

	return replaceUrlWithImageData(filteredObj);
};

let manipulateKeysInObjectList = (objectList = [], manipulator) => {
	return objectList.map(obj => manipulator(obj));
};

let searchContent = (req, res) => {
	let response = {
		success: false,
		err: null,
		hits: null
	};

	const state = getState();

	let count = 0;
	let content = [];

	generateOriginalJWTs()
		.then(() => searchDikshaCloud(req.query))
		.then(({ body }) => {
			if (!(body.params.status === 'successful')) {
				throw new Error(body.params.err);
			}

			count = body.result.count;

			const contentPromises = manipulateKeysInObjectList(body.result.content, filterObjectKeysAndAddImageData);
			return q.allSettled(contentPromises);
		})
		.then(data => {
			content = data.map(item => item.value);

			response = {
				...response,
				success: true,
				hits: {
					count,
					content
				}
			};

			res.set('Cache-Control', ' no-store, no-cache');
			res.status(200).json(response);
		})
		.catch(err => {
			console.log('Error occurred while searching.');
			console.log(err);

			response = {
				...response,
				err: 'Some error occurred while searching.'
			};

			res.status(200).json(response);
		});
};

let extractDependencyIdList = (manifest, parentId) => {
	const items = manifest.archive.items;
	const depIdList = items
		.map(({ identifier }) => identifier !== parentId ? identifier : null)
		.filter(item => item !== null && item !== undefined);
	const uniqueDepIdList = Array.from(new Set(depIdList));

	return uniqueDepIdList;
}

let getDependencies = (req, res) => {
	const { parent } = req.params;
	const src = `${config.FS_ROOT}diksha/${parent}`; // TODO: Make configurable
	const dest = `/tmp/${parent}`;

	let responseData = {
		success: false,
		err: null,
		data: null
	};

	extractZip(src, dest)
		.then(data => {
			return readFile(`${dest}/manifest.json`);
		})
		.then(manifest => {
			const parentId = parent.substring(0, parent.lastIndexOf('_'));
			const depIdList = extractDependencyIdList(JSON.parse(manifest), parentId);

			if (depIdList && depIdList.length > 0) {
				const reqQuery = {
					filters: {
						identifier: depIdList
					},
					fields: ['downloadUrl', 'pkgVersion', 'size', 'name']
				};

				return searchDikshaCloud(reqQuery);
			} else {
				return null;
			}
		})
		.then(depSearchResults => {
			const depDataList = depSearchResults !== null ? depSearchResults.body.result.content : [];

			responseData = {
				...responseData,
				success: true,
				data: depDataList
			};

			res.status(200).json(responseData);
		})
		.catch(err => {
			console.log(`Error occured while fetching ${parent}'s deps.`);
			console.log(err);

			responseData = {
				...responseData,
				success: false,
				err
			};

			res.status(200).json(responseData);
		});
};

module.exports = {
	searchContent,
	getDependencies
};
<|endoftext|>