<commit_before>"use strict";

var App = App || {};

let ChartListView = function(listID) {
  let self = {
    chartHeight: 200,
    chartMargins: {
      top: 10,
      right: 10,
      bottom: 25,
      left: 25
    },
    chartList: null,
    selections: {},
    propertyScales: null,
  };

  init();

  function init() {
    self.chartList = d3.select(listID)

    self.propertyScales = {
      census: {
        "TOTAL_POPULATION": {
          "Total": d3.scaleLinear()
        },
      }
    }
  }

  function createChart(property_data) {
    // let rgb = d3.rgb(property_data.color);

    console.log("createChart",property_data);

    self.chartList.append("div")
      .datum(property_data)
      .attr("class", "panel panel-default chartEntry")
      .attr("id", createPropertyID(property_data))
      // .style("height", "200px")
      // .style("border", `5px solid rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.75)`)
      // .style("background-color", `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.25)`)
      // .style("background-color", "rgba(255,255,255,0.75)")
      .each(setupChart);

    //already done in setupChart
    // self.chartList.selectAll(".chartEntry")
    //   .each(updateChart);
  }

  function setupChart(property_data) {
    // let rgb = d3.rgb(d.color);

    let panel = d3.select(this);

    let heading = panel.append("div").attr("class", "panel-heading").append('div').classed('row',true);
      // .style("background-color", d.color);

    let mainTypeTitle = property_data.mainType.split("_").map((d) => { return `${d[0].toUpperCase()}${d.slice(1).toLowerCase()}`; }).join(" ");
    let propertyTitle = heading.append('h4').attr('class','col-md-10 propertyTitle').html(`<b>${mainTypeTitle}:</b> ${property_data.subType}`);
    let closeButton = heading.append('h4').append('span').attr('class', 'col-md-2 glyphicon glyphicon-remove')
      .on('click',function(){
        removePropertyChart(property_data);
        App.controllers.mapData.removeChartFromList(property_data);
      });

    // let area = heading.append("h4").attr("class", "rectArea").html(`Area (mi<sup>2</sup>): ${d.area.toFixed(2)}`);

    let body = panel.append("div").attr("class", "panel-body");

    let chart = body.append("svg");

    let graph = chart.append('g').classed('graph-group',true);

    graph.background = graph.append("rect")
      .attr("x", self.chartMargins.left)
      .attr("y", self.chartMargins.top)
      .attr("width", chart.node().clientWidth - self.chartMargins.left - self.chartMargins.right)
      .attr("height", chart.node().clientHeight - self.chartMargins.top - self.chartMargins.bottom)
      .classed('graph-background',true).attr('property-data', JSON.stringify(property_data));
    graph.content = graph.append('g').classed('graph-content',true);

    updateChart(createPropertyID(property_data));
  }

  //redraw selection bars
  function updateChart(chartID) {
    if(typeof chartID === "object"){ //given a property object
      chartID = createPropertyID(chartID);
    }
    console.log("updating chart",chartID);
    let chart = self.chartList.select(`#${chartID}`);
    let graph = chart.select('.graph-group');
    try{
      graph.selectAll('.error-text').remove();
      graph.selectAll('g.graph-content').remove();
      graph.background = graph.select('.graph-background');
      graph.content = graph.append('g').classed('graph-content',true);
      let property_data = JSON.parse(graph.background.attr('property-data'));

      let selectionKeys = Object.keys(self.selections);
      let boundsX = [0, +graph.background.attr('width')];
      let xScale = d3.scaleLinear().domain([0, selectionKeys.length]).range(boundsX);
      let barWidth = xScale.range()[1] / selectionKeys.length;
      let data = [];
      for(let s of selectionKeys){
        let curSelection = self.selections[s];
        let area = curSelection.area;
        let propertyValue = curSelection.data.census[property_data.mainType][property_data.subType];
        //show data relative to area (i.e. density)
        data.push({
          value: propertyValue / area,
          color: curSelection.color
        });
      }
      if(data.length > 1){

        let boundsY = [+graph.background.attr('height'), 0];
        let yScale = d3.scaleLinear().domain([0, d3.max(data,(d) => {return d.value;})]).range(boundsY);
        let xOffset = +graph.background.attr('x'), yOffset = +graph.background.attr('y');
        let yMax = yScale.domain()[1];
        graph.content.selectAll('.bar').data(data)
        .enter().append('rect').each(function (data_entry, index) {
          console.log(data_entry);
          d3.select(this).classed('bar', true)
          .attr('x', xOffset + xScale(index)).attr('y', yOffset + yScale(data_entry.value))
          .attr('width', barWidth).attr('height', yScale(yMax - data_entry.value))
          .style('fill', data_entry.color)//.attr('title', race);
        });

        let yAxis = d3.axisLeft(yScale).tickValues([yScale.domain()[0],(yScale.domain()[1]-yScale.domain()[0])/2,yScale.domain()[1]]);
        graph.content.append('g').classed('axis',true)
        .attr('transform',`translate(${xOffset},${yOffset})`).call(yAxis);
      }else{
        graph.content.append('text')
          .attr("x", self.chartMargins.left + 5)
          .attr("y", self.chartMargins.top + 15)
          .text(`Please make ${2 - data.length} more ${2-data.length === 1 ? "selection":"selections"}`);
      }
    }catch(err){
      //fall back for error
      console.log("chart draw error", err);
      graph.select('.graph-content').remove();

      graph.append("text").classed('error-text', true)
        .attr("x", self.chartMargins.left + 5)
        .attr("y", self.chartMargins.top + 15)
        .text(`Error occurred while drawing selections`);
    }
  }

  function createPropertyID(property_data){
    let mainTypeTitle = property_data.mainType.split("_").map((d) => { return `${d[0].toUpperCase()}${d.slice(1).toLowerCase()}`; }).join("_");
    let subTypeTitle = property_data.subType.split(" ").join("_");
    return `${mainTypeTitle}__${subTypeTitle}`.replace(/[^a-zA-Z0-9_]/g,""); //remove any non-alpha numberic characters except underscores
  }

  function addPropertyChart(property_data){
    console.log("addProperty",property_data);
    if(self.chartList.selectAll(`#${createPropertyID(property_data)}`).empty()){
      createChart(property_data);
    }else{
      console.log("Already exists");
    }
  }

  function removePropertyChart(property_data){
    removeChart(createPropertyID(property_data));
  }

  function addSelection(properties){
    console.log("addSelection",properties);
    self.selections[properties.id.toString()] = properties;

    updatePropertyExtents();

    self.chartList.selectAll('.chartEntry')
      .each(updateChart);
  }

  function removeSelection(id){
    if(typeof id === "object"){ //id is a properties object
      delete self.selections[id.id.toString()];
    }else{
      delete self.selections[id.toString()];
    }

    updatePropertyExtents();
    self.chartList.selectAll(".chartEntry")
      .each(updateChart);
  }

  function removeChart(id) {
    self.chartList.select(`#${id}`).remove();

    updatePropertyExtents();

    self.chartList.selectAll(".chartEntry")
      .each(updateChart);
  }

  function updatePropertyExtents() {
    console.log("Temporarily disabled updatePropertyExtents");
    // for (let dataSource of Object.keys(self.propertyScales)) {
    //   for (let property of Object.keys(self.propertyScales[dataSource])) {
    //     for (let subproperty of Object.keys(self.propertyScales[dataSource][property])) {
    //       self.propertyScales[dataSource][property][subproperty].domain(
    //         d3.extent(d3.selectAll(".chartEntry").nodes(), (node) => {
    //           // get that data value from node
    //           let rect = d3.select(node).datum();
    //           return rect.data[dataSource][property][subproperty] / rect.area;
    //         }));

    //       console.log(dataSource, property, subproperty, self.propertyScales[dataSource][property][subproperty].domain());
    //     }
    //   }
    // }
  }

  return {
    createChart,
    removeChart,
    addPropertyChart,
    removePropertyChart,
    addSelection,
    removeSelection
  };
};
<commit_msg>Add area units and clean subcategory text in panel title of analytics page<commit_after>"use strict";

var App = App || {};

let ChartListView = function(listID) {
  let self = {
    chartHeight: 200,
    chartMargins: {
      top: 10,
      right: 10,
      bottom: 25,
      left: 25
    },
    chartList: null,
    selections: {},
    propertyScales: null,
  };

  init();

  function init() {
    self.chartList = d3.select(listID)

    self.propertyScales = {
      census: {
        "TOTAL_POPULATION": {
          "Total": d3.scaleLinear()
        },
      }
    }
  }

  function createChart(property_data) {
    // let rgb = d3.rgb(property_data.color);

    console.log("createChart",property_data);

    self.chartList.append("div")
      .datum(property_data)
      .attr("class", "panel panel-default chartEntry")
      .attr("id", createPropertyID(property_data))
      // .style("height", "200px")
      // .style("border", `5px solid rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.75)`)
      // .style("background-color", `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.25)`)
      // .style("background-color", "rgba(255,255,255,0.75)")
      .each(setupChart);

    //already done in setupChart
    // self.chartList.selectAll(".chartEntry")
    //   .each(updateChart);
  }

  function setupChart(property_data) {
    // let rgb = d3.rgb(d.color);

    let panel = d3.select(this);

    let heading = panel.append("div").attr("class", "panel-heading").append('div').classed('row',true);
      // .style("background-color", d.color);

    let mainTypeTitle = property_data.mainType.split("_").map((d) => { return `${d[0].toUpperCase()}${d.slice(1).toLowerCase()}`; }).join(" ");
    let propertyTitle = heading.append('h4').attr('class', 'col-md-10 propertyTitle').html(`<b>${mainTypeTitle}:</b> ${property_data.subType.replace(/[^a-zA-Z0-9 ]/g, "")} (/mi<sup>2</sup>)`);
    let closeButton = heading.append('h4').append('span').attr('class', 'col-md-2 glyphicon glyphicon-remove')
      .on('click',function(){
        removePropertyChart(property_data);
        App.controllers.mapData.removeChartFromList(property_data);
      });

    // let area = heading.append("h4").attr("class", "rectArea").html(`Area (mi<sup>2</sup>): ${d.area.toFixed(2)}`);

    let body = panel.append("div").attr("class", "panel-body");

    let chart = body.append("svg");

    let graph = chart.append('g').classed('graph-group',true);

    graph.background = graph.append("rect")
      .attr("x", self.chartMargins.left)
      .attr("y", self.chartMargins.top)
      .attr("width", chart.node().clientWidth - self.chartMargins.left - self.chartMargins.right)
      .attr("height", chart.node().clientHeight - self.chartMargins.top - self.chartMargins.bottom)
      .classed('graph-background',true).attr('property-data', JSON.stringify(property_data));
    graph.content = graph.append('g').classed('graph-content',true);

    updateChart(createPropertyID(property_data));
  }

  //redraw selection bars
  function updateChart(chartID) {
    if(typeof chartID === "object"){ //given a property object
      chartID = createPropertyID(chartID);
    }
    console.log("updating chart",chartID);
    let chart = self.chartList.select(`#${chartID}`);
    let graph = chart.select('.graph-group');
    try{
      graph.selectAll('.error-text').remove();
      graph.selectAll('g.graph-content').remove();
      graph.background = graph.select('.graph-background');
      graph.content = graph.append('g').classed('graph-content',true);
      let property_data = JSON.parse(graph.background.attr('property-data'));

      let selectionKeys = Object.keys(self.selections);
      let boundsX = [0, +graph.background.attr('width')];
      let xScale = d3.scaleLinear().domain([0, selectionKeys.length]).range(boundsX);
      let barWidth = xScale.range()[1] / selectionKeys.length;
      let data = [];
      for(let s of selectionKeys){
        let curSelection = self.selections[s];
        let area = curSelection.area;
        let propertyValue = curSelection.data.census[property_data.mainType][property_data.subType];
        //show data relative to area (i.e. density)
        data.push({
          value: propertyValue / area,
          color: curSelection.color
        });
      }
      if(data.length > 1){

        let boundsY = [+graph.background.attr('height'), 0];
        let yScale = d3.scaleLinear().domain([0, d3.max(data,(d) => {return d.value;})]).range(boundsY);
        let xOffset = +graph.background.attr('x'), yOffset = +graph.background.attr('y');
        let yMax = yScale.domain()[1];
        graph.content.selectAll('.bar').data(data)
        .enter().append('rect').each(function (data_entry, index) {
          console.log(data_entry);
          d3.select(this).classed('bar', true)
          .attr('x', xOffset + xScale(index)).attr('y', yOffset + yScale(data_entry.value))
          .attr('width', barWidth).attr('height', yScale(yMax - data_entry.value))
          .style('fill', data_entry.color)//.attr('title', race);
        });

        let yAxis = d3.axisLeft(yScale).tickValues([yScale.domain()[0],(yScale.domain()[1]-yScale.domain()[0])/2,yScale.domain()[1]]);
        graph.content.append('g').classed('axis',true)
        .attr('transform',`translate(${xOffset},${yOffset})`).call(yAxis);
      }else{
        graph.content.append('text')
          .attr("x", self.chartMargins.left + 5)
          .attr("y", self.chartMargins.top + 15)
          .text(`Please make ${2 - data.length} more ${2-data.length === 1 ? "selection":"selections"}`);
      }
    }catch(err){
      //fall back for error
      console.log("chart draw error", err);
      graph.select('.graph-content').remove();

      graph.append("text").classed('error-text', true)
        .attr("x", self.chartMargins.left + 5)
        .attr("y", self.chartMargins.top + 15)
        .text(`Error occurred while drawing selections`);
    }
  }

  function createPropertyID(property_data){
    let mainTypeTitle = property_data.mainType.split("_").map((d) => { return `${d[0].toUpperCase()}${d.slice(1).toLowerCase()}`; }).join("_");
    let subTypeTitle = property_data.subType.split(" ").join("_");
    return `${mainTypeTitle}__${subTypeTitle}`.replace(/[^a-zA-Z0-9_]/g,""); //remove any non-alpha numberic characters except underscores
  }

  function addPropertyChart(property_data){
    console.log("addProperty",property_data);
    if(self.chartList.selectAll(`#${createPropertyID(property_data)}`).empty()){
      createChart(property_data);
    }else{
      console.log("Already exists");
    }
  }

  function removePropertyChart(property_data){
    removeChart(createPropertyID(property_data));
  }

  function addSelection(properties){
    console.log("addSelection",properties);
    self.selections[properties.id.toString()] = properties;

    updatePropertyExtents();

    self.chartList.selectAll('.chartEntry')
      .each(updateChart);
  }

  function removeSelection(id){
    if(typeof id === "object"){ //id is a properties object
      delete self.selections[id.id.toString()];
    }else{
      delete self.selections[id.toString()];
    }

    updatePropertyExtents();
    self.chartList.selectAll(".chartEntry")
      .each(updateChart);
  }

  function removeChart(id) {
    self.chartList.select(`#${id}`).remove();

    updatePropertyExtents();

    self.chartList.selectAll(".chartEntry")
      .each(updateChart);
  }

  function updatePropertyExtents() {
    console.log("Temporarily disabled updatePropertyExtents");
    // for (let dataSource of Object.keys(self.propertyScales)) {
    //   for (let property of Object.keys(self.propertyScales[dataSource])) {
    //     for (let subproperty of Object.keys(self.propertyScales[dataSource][property])) {
    //       self.propertyScales[dataSource][property][subproperty].domain(
    //         d3.extent(d3.selectAll(".chartEntry").nodes(), (node) => {
    //           // get that data value from node
    //           let rect = d3.select(node).datum();
    //           return rect.data[dataSource][property][subproperty] / rect.area;
    //         }));

    //       console.log(dataSource, property, subproperty, self.propertyScales[dataSource][property][subproperty].domain());
    //     }
    //   }
    // }
  }

  return {
    createChart,
    removeChart,
    addPropertyChart,
    removePropertyChart,
    addSelection,
    removeSelection
  };
};
<|endoftext|>


<commit_before>// @flow
import React from 'react'
import {View, Text, StyleSheet, Platform} from 'react-native'
import type {BusLineType, FancyBusTimeListType, BusScheduleType} from './types'
import {getScheduleForNow, getSetOfStopsForNow} from './lib'
import get from 'lodash/get'
import zip from 'lodash/zip'
import head from 'lodash/head'
import last from 'lodash/last'
import moment from 'moment-timezone'
import * as c from '../../components/colors'
import {Separator} from '../../components/separator'
import {BusStopRow} from './bus-stop-row'
import {ListRow, ListSectionHeader, Title} from '../../components/list'

const TIME_FORMAT = 'h:mma'
const TIMEZONE = 'America/Winnipeg'

const styles = StyleSheet.create({
  separator: {
    marginLeft: 45,
    // erase the gap in the bar caused by the separators' block-ness
    marginTop: -1,
  },
})

const barColors = {
  'Blue Line': c.steelBlue,
  'Express Bus': c.moneyGreen,
  'Red Line': c.salmon,
}
const stopColors = {
  'Blue Line': c.midnightBlue,
  'Express Bus': c.hollyGreen,
  'Red Line': c.brickRed,
}

function makeSubtitle({now, moments, isLastBus}) {
  let lineDetail = 'Running'

  if (now.isBefore(head(moments))) {
    lineDetail = `Starts ${now.clone().seconds(0).to(head(moments))}`
  } else if (now.isAfter(last(moments))) {
    lineDetail = 'Over for Today'
  } else if (isLastBus) {
    lineDetail = 'Last Bus'
  }

  return lineDetail
}

const parseTime = (now: moment) => (time: string | false) => {
  // either pass `false` through or return a parsed time
  if (time === false) {
    return false
  }

  return (
    moment
      // interpret in Central time
      .tz(time, TIME_FORMAT, true, TIMEZONE)
      // and set the date to today
      .dayOfYear(now.dayOfYear())
  )
}

type Props = {
  line: BusLineType,
  now: moment,
  openMap: () => any,
}

type State = {
  schedule: ?BusScheduleType,
  scheduledMoments: Array<FancyBusTimeListType>,
  currentMoments: FancyBusTimeListType,
  stopTitleTimePairs: Array<[string, moment]>,
}

export class BusLine extends React.Component<void, Props, State> {
  state = {
    schedule: null,
    scheduledMoments: [],
    currentMoments: [],
    stopTitleTimePairs: [],
  }

  componentWillMount() {
    this.setStateFromProps(this.props)
  }

  componentWillReceiveProps(nextProps: Props) {
    this.setStateFromProps(nextProps)
  }

  shouldComponentUpdate(nextProps: Props) {
    return (
      this.props.now.minute() !== nextProps.now.minute() ||
      this.props.now.hour() !== nextProps.now.hour() ||
      this.props.now.dayOfYear() !== nextProps.now.dayOfYear() ||
      this.props.line !== nextProps.line ||
      this.props.openMap !== nextProps.openMap
    )
  }

  setStateFromProps(nextProps: Props) {
    const {line, now} = nextProps

    const schedule = getScheduleForNow(line.schedules, now)
    if (!schedule) {
      return
    }

    const parseTimes = timeset => timeset.map(parseTime(now))
    const scheduledMoments: Array<FancyBusTimeListType> = schedule.times.map(
      parseTimes,
    )

    const currentMoments: FancyBusTimeListType = getSetOfStopsForNow(
      scheduledMoments,
      now,
    )

    const stopTitleTimePairs: Array<[string, moment]> = zip(
      schedule.stops,
      currentMoments,
    )

    this.setState(() => ({
      schedule,
      scheduledMoments,
      currentMoments,
      stopTitleTimePairs,
    }))
  }

  render() {
    const {line, now} = this.props
    const {
      schedule,
      scheduledMoments,
      currentMoments,
      stopTitleTimePairs,
    } = this.state

    // grab the colors (with fallbacks) via _.get
    const barColor = get(barColors, line.line, c.black)
    const currentStopColor = get(stopColors, line.line, c.gray)
    const androidColor = Platform.OS === 'android' ? {color: barColor} : null

    if (!schedule) {
      return (
        <View>
          <ListSectionHeader title={line.line} titleStyle={androidColor} />
          <ListRow>
            <Title><Text>This line is not running today.</Text></Title>
          </ListRow>
        </View>
      )
    }

    const timesIndex = scheduledMoments.indexOf(currentMoments)
    const isLastBus = timesIndex === scheduledMoments.length - 1
    const subtitle = makeSubtitle({now, moments: currentMoments, isLastBus})

    return (
      <View>
        <ListSectionHeader
          title={line.line}
          subtitle={subtitle}
          titleStyle={androidColor}
        />

        {stopTitleTimePairs.map(([placeTitle, moment], i, list) =>
          <View key={i}>
            <BusStopRow
              // get the arrival time for this stop from each bus loop after
              // the current time (as given by `now`)
              times={scheduledMoments.slice(timesIndex).map(set => set[i])}
              place={placeTitle}
              now={now}
              time={moment}
              barColor={barColor}
              currentStopColor={currentStopColor}
              isFirstRow={i === 0}
              isLastRow={i === list.length - 1}
            />
            {i < list.length - 1
              ? <Separator style={styles.separator} />
              : null}
          </View>,
        )}

        {/*<ListRow
          onPress={this.props.openMap}
          fullWidth={true}
          spacing={{left: 45}}
        >
          <Row alignItems="center">
            <Column alignItems="center" width={45} paddingRight={5}>
              <Icon
                name={
                  Platform.OS === 'ios' ? 'ios-navigate-outline' : 'md-navigate'
                }
                size={24}
                style={{color: c.iosDisabledText}}
              />
            </Column>

            <Column>
              <Title>
                <Text>Open Map</Text>
              </Title>

              <Detail>
                <Text>See the planned bus route on a map!</Text>
              </Detail>
            </Column>
          </Row>
        </ListRow>*/}
      </View>
    )
  }
}
<commit_msg>bind setStateFromProps method<commit_after>// @flow
import React from 'react'
import {View, Text, StyleSheet, Platform} from 'react-native'
import type {BusLineType, FancyBusTimeListType, BusScheduleType} from './types'
import {getScheduleForNow, getSetOfStopsForNow} from './lib'
import get from 'lodash/get'
import zip from 'lodash/zip'
import head from 'lodash/head'
import last from 'lodash/last'
import moment from 'moment-timezone'
import * as c from '../../components/colors'
import {Separator} from '../../components/separator'
import {BusStopRow} from './bus-stop-row'
import {ListRow, ListSectionHeader, Title} from '../../components/list'

const TIME_FORMAT = 'h:mma'
const TIMEZONE = 'America/Winnipeg'

const styles = StyleSheet.create({
  separator: {
    marginLeft: 45,
    // erase the gap in the bar caused by the separators' block-ness
    marginTop: -1,
  },
})

const barColors = {
  'Blue Line': c.steelBlue,
  'Express Bus': c.moneyGreen,
  'Red Line': c.salmon,
}
const stopColors = {
  'Blue Line': c.midnightBlue,
  'Express Bus': c.hollyGreen,
  'Red Line': c.brickRed,
}

function makeSubtitle({now, moments, isLastBus}) {
  let lineDetail = 'Running'

  if (now.isBefore(head(moments))) {
    lineDetail = `Starts ${now.clone().seconds(0).to(head(moments))}`
  } else if (now.isAfter(last(moments))) {
    lineDetail = 'Over for Today'
  } else if (isLastBus) {
    lineDetail = 'Last Bus'
  }

  return lineDetail
}

const parseTime = (now: moment) => (time: string | false) => {
  // either pass `false` through or return a parsed time
  if (time === false) {
    return false
  }

  return (
    moment
      // interpret in Central time
      .tz(time, TIME_FORMAT, true, TIMEZONE)
      // and set the date to today
      .dayOfYear(now.dayOfYear())
  )
}

type Props = {
  line: BusLineType,
  now: moment,
  openMap: () => any,
}

type State = {
  schedule: ?BusScheduleType,
  scheduledMoments: Array<FancyBusTimeListType>,
  currentMoments: FancyBusTimeListType,
  stopTitleTimePairs: Array<[string, moment]>,
}

export class BusLine extends React.Component<void, Props, State> {
  state = {
    schedule: null,
    scheduledMoments: [],
    currentMoments: [],
    stopTitleTimePairs: [],
  }

  componentWillMount() {
    this.setStateFromProps(this.props)
  }

  componentWillReceiveProps(nextProps: Props) {
    this.setStateFromProps(nextProps)
  }

  shouldComponentUpdate(nextProps: Props) {
    return (
      this.props.now.minute() !== nextProps.now.minute() ||
      this.props.now.hour() !== nextProps.now.hour() ||
      this.props.now.dayOfYear() !== nextProps.now.dayOfYear() ||
      this.props.line !== nextProps.line ||
      this.props.openMap !== nextProps.openMap
    )
  }

  setStateFromProps = (nextProps: Props) => {
    const {line, now} = nextProps

    const schedule = getScheduleForNow(line.schedules, now)
    if (!schedule) {
      return
    }

    const parseTimes = timeset => timeset.map(parseTime(now))
    const scheduledMoments: Array<FancyBusTimeListType> = schedule.times.map(
      parseTimes,
    )

    const currentMoments: FancyBusTimeListType = getSetOfStopsForNow(
      scheduledMoments,
      now,
    )

    const stopTitleTimePairs: Array<[string, moment]> = zip(
      schedule.stops,
      currentMoments,
    )

    this.setState(() => ({
      schedule,
      scheduledMoments,
      currentMoments,
      stopTitleTimePairs,
    }))
  }

  render() {
    const {line, now} = this.props
    const {
      schedule,
      scheduledMoments,
      currentMoments,
      stopTitleTimePairs,
    } = this.state

    // grab the colors (with fallbacks) via _.get
    const barColor = get(barColors, line.line, c.black)
    const currentStopColor = get(stopColors, line.line, c.gray)
    const androidColor = Platform.OS === 'android' ? {color: barColor} : null

    if (!schedule) {
      return (
        <View>
          <ListSectionHeader title={line.line} titleStyle={androidColor} />
          <ListRow>
            <Title><Text>This line is not running today.</Text></Title>
          </ListRow>
        </View>
      )
    }

    const timesIndex = scheduledMoments.indexOf(currentMoments)
    const isLastBus = timesIndex === scheduledMoments.length - 1
    const subtitle = makeSubtitle({now, moments: currentMoments, isLastBus})

    return (
      <View>
        <ListSectionHeader
          title={line.line}
          subtitle={subtitle}
          titleStyle={androidColor}
        />

        {stopTitleTimePairs.map(([placeTitle, moment], i, list) =>
          <View key={i}>
            <BusStopRow
              // get the arrival time for this stop from each bus loop after
              // the current time (as given by `now`)
              times={scheduledMoments.slice(timesIndex).map(set => set[i])}
              place={placeTitle}
              now={now}
              time={moment}
              barColor={barColor}
              currentStopColor={currentStopColor}
              isFirstRow={i === 0}
              isLastRow={i === list.length - 1}
            />
            {i < list.length - 1
              ? <Separator style={styles.separator} />
              : null}
          </View>,
        )}

        {/*<ListRow
          onPress={this.props.openMap}
          fullWidth={true}
          spacing={{left: 45}}
        >
          <Row alignItems="center">
            <Column alignItems="center" width={45} paddingRight={5}>
              <Icon
                name={
                  Platform.OS === 'ios' ? 'ios-navigate-outline' : 'md-navigate'
                }
                size={24}
                style={{color: c.iosDisabledText}}
              />
            </Column>

            <Column>
              <Title>
                <Text>Open Map</Text>
              </Title>

              <Detail>
                <Text>See the planned bus route on a map!</Text>
              </Detail>
            </Column>
          </Row>
        </ListRow>*/}
      </View>
    )
  }
}
<|endoftext|>


<commit_before>import {addIndex, findIndex, findLastIndex, map, memoize} from "ramda";
import moment from "moment";

const mapIndexed = addIndex(map);

function getDailyAggregate (dailyAggregates, chartState) {
    const {sensorId, day, measurementType, source} = chartState;
    const date = [
        moment.utc(day).subtract({day: 1}).format("YYYY-MM-DD"),
        day,
        moment.utc(day).add({day: 1}).format("YYYY-MM-DD")
    ];
    const aggregates = date.map(dataDay => dailyAggregates.get(
        `${sensorId}-${dataDay}-${source}-${measurementType}`
    ));
    return aggregates.reduce((acc, aggregate) => {
        if (!aggregate) {
            return acc;
        }
        const times = aggregate.get("measurementTimes").split(",");
        const values = aggregate.get("measurementValues").split(",");
        return {
            values: [...acc.values, ...values],
            times: [...acc.times, ...times]
        };
    }, {values: [], times: []});
}

export default memoize((aggregates, chartsState) => {
    if (aggregates.isEmpty()) {
        return [{
            data: []
        }];
    }
    const sortedAggregate = aggregates.sortBy(x => x.get("day"));
    const timeFromAggregate = !chartsState[0].period || chartsState[0].period === "day";
    var result;
    if (timeFromAggregate) {
        const chartsData = map(chartState => {
            const {sensorId, day} = chartState;
            // Get daily aggregate with correct offset per timezone
            const aggregate = getDailyAggregate(sortedAggregate, chartState);
            const firstIndex = findIndex(time =>
                parseInt(time) >= moment(day).valueOf()
            )(aggregate.times);
            var lastIndex = findLastIndex(time =>
                parseInt(time) <= moment(day).endOf("day").valueOf()
            )(aggregate.times);
            if (lastIndex < 0 && firstIndex >= 0) {
                lastIndex = aggregate.values.length;
            }
            const values = aggregate.values.slice(firstIndex, lastIndex + 1);
            const times = aggregate.times.slice(firstIndex, lastIndex + 1);
            var data = [];
            data = mapIndexed((value, index) => {
                return {
                    hour: moment.utc(parseInt(times[index])).add({minutes: moment().utcOffset()}).format("H"),
                    value: parseFloat(value)
                };
            }, values);
            return {
                toFill: sensorId.includes("-standby"),
                data
            };
        }, chartsState);

        const filledChartsData = chartsData.map(chartData => {
            if (chartData.data.length == 0) {
                return [];
            }
            let filledChartData = [];
            for (var index = 0; index < 24; index++) {

                let measurements = chartData.data.filter(x => parseInt(x.hour) === index);
                let measurement = measurements.reduce((state, current) => {
                    return {
                        hour: current.hour,
                        value: state ? state.value + current.value : current.value
                    };
                }, null);

                measurement ? filledChartData[index] = measurement : filledChartData[index] = {
                    hour: index.toString(),
                    value: chartData.toFill ? (filledChartData[index - 1] ? filledChartData[index - 1].value : chartData.data.find(x => x.value !== 0) ? chartData.data.find(x => x.value !== 0).value : 0) : 0
                };
            }
            return filledChartData;
        });

        result = filledChartsData.map(data => {
            const chart = data.map(measurement => {
                return [measurement.hour, Math.round(measurement.value * 10) / 10];
            });

            const categories = data.map(measurement => {
                return [measurement.hour];
            });
            return {
                data: chart,
                categories: categories
            };
        });
    } else {

        const chartData = map(chartState => {
            const {sensorId, day, measurementType, source, period} = chartState;
            const time = moment.utc(day);
            const dayTime = moment.utc(day).startOf("year");
            const aggregate = sortedAggregate.get(
                `${sensorId}-${dayTime.year()}-${source}-${measurementType}`
            );
            var data = [];
            if (aggregate) {
                const values = aggregate.get("measurementValues").split(",");
                data = values.map((value, index) => {
                    const obj = {
                        period: dayTime.get(period),
                        dayTime: dayTime,
                        formatted: getFormat(period, dayTime),
                        formattedTooltip: getFormatTooltip(period, dayTime),
                        value: parseFloat(values[index]) || 0
                    };
                    dayTime.add(1, "day");
                    return obj;
                }).filter(x => x.period === time.get(period));
            }

            if ("year" === period) {
                data = data.reduce((state, current) => {
                    const finded = state.find(value => {
                        return value.formattedTooltip === current.formattedTooltip;
                    });
                    if (finded) {
                        finded.value += current.value;
                        return state;
                    } else {
                        return [
                            ...state,
                            current
                        ];
                    }
                }, []);
            }
            return data;
        }, chartsState);

        result = chartData.map(data => {
            const series = data.map(serie => {
                return [serie.formattedTooltip, Math.round(serie.value * 10) / 10];
            });
            const categories = data.map(serie => {
                return serie.formatted;
            });

            const completedCategories = completeCategories(chartsState[0].period, categories);
            const completedSeries = completeChart(chartsState[0].period, series);
            return {
                data: completedSeries,
                categories: completedCategories
            };
        });
    }
    return result;
});

function completeCategories (period, categories) {
    switch (period) {
        case "week":
            for (var x= categories.length; x<=6; x++) {
                categories.push(moment().weekday(x).format("ddd").substring(0, 1));
            }
            break;
        case "month":
            var endOfMonth = moment().endOf("month").format("D")-1;
            for (x=categories.length; x<=endOfMonth; x++) {
                categories.push((x+1).toString());
            }
            break;
        case "year":
            for (x= categories.length; x<=11; x++) {
                categories.push(moment().month(x).format("MMM").substring(0, 1).toUpperCase());
            }
            break;
    }
    return categories;
}

function completeChart (period, chart) {
    switch (period) {
        case "week":
            for (var x= chart.length; x<=6; x++) {
                chart.push([x, 0]);
            }
            break;
        case "month":
            var endOfMonth = moment().endOf("month").format("D") -1;
            for (x=chart.length; x<=endOfMonth; x++) {
                chart.push([x, 0]);
            }
            break;
        case "year":
            for (x= chart.length; x<=11; x++) {
                chart.push([x, 0]);
            }
            break;
    }
    return chart;
}

function getFormat (period, dayTime) {
    switch (period) {
        case "week":
            return dayTime.format("ddd").substring(0, 1);
        case "month":
            return dayTime.format("D");
        case "year":
            return dayTime.format("MMM").substring(0, 1).toUpperCase();
    }
}

function getFormatTooltip (period, dayTime) {
    switch (period) {
        case "week":
            return dayTime.format("dddd");
        case "month":
            return dayTime.format("D") + " " + dayTime.format("MMMM");
        case "year":
            return dayTime.format("MMMM");
    }
}
<commit_msg>Fix unit-test<commit_after>import {addIndex, findIndex, findLastIndex, map, memoize} from "ramda";
import moment from "moment";

const mapIndexed = addIndex(map);

function getDailyAggregate (dailyAggregates, chartState) {
    const {sensorId, day, measurementType, source} = chartState;
    const date = [
        moment.utc(day).subtract({day: 1}).format("YYYY-MM-DD"),
        day,
        moment.utc(day).add({day: 1}).format("YYYY-MM-DD")
    ];
    const aggregates = date.map(dataDay => dailyAggregates.get(
        `${sensorId}-${dataDay}-${source}-${measurementType}`
    ));
    return aggregates.reduce((acc, aggregate) => {
        if (!aggregate) {
            return acc;
        }
        const times = aggregate.get("measurementTimes").split(",");
        const values = aggregate.get("measurementValues").split(",");
        return {
            values: [...acc.values, ...values],
            times: [...acc.times, ...times]
        };
    }, {values: [], times: []});
}

export default memoize((aggregates, chartsState) => {
    if (aggregates.isEmpty()) {
        return [{
            data: []
        }];
    }
    const sortedAggregate = aggregates.sortBy(x => x.get("day"));
    const timeFromAggregate = !chartsState[0].period || chartsState[0].period === "day";
    var result;
    if (timeFromAggregate) {
        const chartsData = map(chartState => {
            const {sensorId, day} = chartState;
            // Get daily aggregate with correct offset per timezone
            const aggregate = getDailyAggregate(sortedAggregate, chartState);
            const firstIndex = findIndex(time =>
                parseInt(time) >= moment(day).valueOf()
            )(aggregate.times);
            var lastIndex = findLastIndex(time =>
                parseInt(time) <= moment(day).endOf("day").valueOf()
            )(aggregate.times);
            if (lastIndex < 0 && firstIndex >= 0) {
                lastIndex = aggregate.values.length;
            }
            const values = aggregate.values.slice(firstIndex, lastIndex + 1);
            const times = aggregate.times.slice(firstIndex, lastIndex + 1);
            var data = [];
            data = mapIndexed((value, index) => {
                return {
                    hour: moment.utc(parseInt(times[index])).add({minutes: moment().utcOffset()}).format("H"),
                    value: parseFloat(value)
                };
            }, values);
            return {
                toFill: sensorId.includes("-standby"),
                data
            };
        }, chartsState);

        const filledChartsData = chartsData.map(chartData => {
            if (chartData.data.length == 0) {
                return [];
            }
            let filledChartData = [];
            for (var index = 0; index < 24; index++) {

                let measurements = chartData.data.filter(x => parseInt(x.hour) === index);
                let measurement = measurements.reduce((state, current) => {
                    return {
                        hour: current.hour,
                        value: state ? state.value + current.value : current.value
                    };
                }, null);

                measurement ? filledChartData[index] = measurement : filledChartData[index] = {
                    hour: index.toString(),
                    value: chartData.toFill ? (filledChartData[index - 1] ? filledChartData[index - 1].value : chartData.data.find(x => x.value !== 0) ? chartData.data.find(x => x.value !== 0).value : 0) : 0
                };
            }
            return filledChartData;
        });

        result = filledChartsData.map(data => {
            const chart = data.map(measurement => {
                return [measurement.hour, Math.round(measurement.value * 10) / 10];
            });

            const categories = data.map(measurement => {
                return [measurement.hour];
            });
            return {
                data: chart,
                categories: categories
            };
        });
    } else {

        const chartData = map(chartState => {
            const {sensorId, day, measurementType, source, period} = chartState;
            const time = moment.utc(day);
            const dayTime = moment.utc(day).startOf("year");
            const aggregate = sortedAggregate.get(
                `${sensorId}-${dayTime.year()}-${source}-${measurementType}`
            );
            var data = [];
            if (aggregate) {
                const values = aggregate.get("measurementValues").split(",");
                data = values.map((value, index) => {
                    const obj = {
                        period: dayTime.get(period),
                        dayTime: dayTime,
                        formatted: getFormat(period, dayTime),
                        formattedTooltip: getFormatTooltip(period, dayTime),
                        value: parseFloat(values[index]) || 0
                    };
                    dayTime.add(1, "day");
                    return obj;
                }).filter(x => x.period === time.get(period));
            }

            if ("year" === period) {
                data = data.reduce((state, current) => {
                    const finded = state.find(value => {
                        return value.formattedTooltip === current.formattedTooltip;
                    });
                    if (finded) {
                        finded.value += current.value;
                        return state;
                    } else {
                        return [
                            ...state,
                            current
                        ];
                    }
                }, []);
            }
            return data;
        }, chartsState);

        result = chartData.map(data => {
            const series = data.map(serie => {
                return [serie.formattedTooltip, Math.round(serie.value * 10) / 10];
            });
            const categories = data.map(serie => {
                return serie.formatted;
            });

            const completedCategories = completeCategories(chartsState[0].period, categories);
            const completedSeries = completeChart(chartsState[0].period, series);
            return {
                data: completedSeries,
                categories: completedCategories
            };
        });
    }
    return result;
});

function completeCategories (period, categories) {
    switch (period) {
        case "week":
            for (var x= categories.length; x<=6; x++) {
                categories.push(moment().ISOWeekday(x).format("ddd").substring(0, 1));
            }
            break;
        case "month":
            var endOfMonth = moment().endOf("month").format("D")-1;
            for (x=categories.length; x<=endOfMonth; x++) {
                categories.push((x+1).toString());
            }
            break;
        case "year":
            for (x= categories.length; x<=11; x++) {
                categories.push(moment().month(x).format("MMM").substring(0, 1).toUpperCase());
            }
            break;
    }
    return categories;
}

function completeChart (period, chart) {
    switch (period) {
        case "week":
            for (var x= chart.length; x<=6; x++) {
                chart.push([x, 0]);
            }
            break;
        case "month":
            var endOfMonth = moment().endOf("month").format("D") -1;
            for (x=chart.length; x<=endOfMonth; x++) {
                chart.push([x, 0]);
            }
            break;
        case "year":
            for (x= chart.length; x<=11; x++) {
                chart.push([x, 0]);
            }
            break;
    }
    return chart;
}

function getFormat (period, dayTime) {
    switch (period) {
        case "week":
            return dayTime.format("ddd").substring(0, 1);
        case "month":
            return dayTime.format("D");
        case "year":
            return dayTime.format("MMM").substring(0, 1).toUpperCase();
    }
}

function getFormatTooltip (period, dayTime) {
    switch (period) {
        case "week":
            return dayTime.format("dddd");
        case "month":
            return dayTime.format("D") + " " + dayTime.format("MMMM");
        case "year":
            return dayTime.format("MMMM");
    }
}
<|endoftext|>


<commit_before>var express = require('express')
var router = express.Router()

var sanity_check = require('../common/sanity.js')
var stopLoc = require('../common/stopLoc.js')
var util = require('./util.js')

router.get('/', function(req, res) {
  res.send('INFO API');
});

router.post('/bus', function(req, res) {
  var query = req.body;
  if (
    sanity_check.isRequired(query.bus_no) &&
    sanity_check.isRequired(query.start_point) &&
    sanity_check.isRequired(query.end_point) &&
    sanity_check.isRequired(query.coord) &&
    sanity_check.isRequired(query.coord.lat) &&
    sanity_check.isRequired(query.coord.lng)
  ) {
    var db = req.db;
    var nearestStopPromise;
    if (query.src) {
      nearestStopPromise = new Promise((resolve, reject) => {
        resolve(query.src);
      });
    } else {
      nearestStopPromise = new Promise((resolve, reject) => {
        var collection = db.get('MasterBus');
        collection.findOne(
          {
            busNo : query.bus_no,
            source : query.start_point,
            destination : query.end_point,
          },
          {},
          function(err, entity) {
            if (err) reject(err);
            else {
              coordinatePromise = []
              entity.busStopList.forEach((busStop, i) => {
                var p = new Promise((accept, decline) => {
                  var StopCollection = db.get('BusStop');
                  StopCollection.findOne(
                    {"stopName": busStop },
                    {},
                    function(err, element) {
                      if (err) decline(err);
                      var d = util.distance(element.coord, query.coord);
                      accept({
                          "distance" : d,
                          "stopName" : busStop
                      });
                    }
                  );
                });
                coordinatePromise.push(p);
              });
              Promise.all(coordinatePromise).then((distances) => {
                distances.sort((A,B) => {
                  return A.distance - B.distance;
                })
                resolve(distances[0].stopName);
              }).catch( (reason) => {
                reject(reason);
              });

            }
          }
        );
      });
    }

    nearestStopPromise.then((nearestStop) => {

      var BusCollection = db.get('Bus');
      BusCollection.find(
        {
          busNo : query.bus_no,
          source : query.start_point,
          destination : query.end_point,
        },
        {},
        function(err, documents) {
          documents = documents.map((document) => {
            var time = document.Timings.filter((entry) => {
              return (entry.busStop === nearestStop)
            })[0].time;

            var stopList = document.Timings.map((entry) => {
              return entry.busStop;
            });

            var src;
            if (query.src ) {
              src = query.src;
            } else {
              src = nearestStop;
            }

            var dest;
            if (query.dest) {
              dest = query.dest;
            } else {
              dest = query.end_point;
            }

            var src_index, dest_index;
            for(var i=0; i < stopList.length; i++) {
              if (stopList[i] == src)
                src_index = i;
              else if (stopList[i] == dest)
                dest_index = i;
              else
                continue;
            }
            stopList = stopList.splice(src_index, dest_index+1);

            var busLocation;
            if (document.currLoc && ((document.updateAt - new Date()) <= 600000) ) {
              busLocation = document.currLoc;
            } else {
              var timings = document.Timings;
              timings.sort((A,B) => {
                return A.time - B.time;
              });

              var date = new Date();
              var threshold = date.getHours()*60 + date.getMinutes();

              timings = timings.filter((A) => {
                return A.time > threshold;
              });

              if (timings.length === 0) {
                timings.push(document.Timings[0]);
              }

              busLocation = stopLoc[timings[0].busStop];
            }

            return {
              "id"  : document.id,
              "stop" : nearestStop,
              "busLoc" : busLocation,
              "time" : time,
              "stopList": stopList
            }
          });

          var date = new Date();
          var threshold = date.getHours()*60 + date.getMinutes();

          documents = documents.filter((document) => {
            return (document.time >= threshold);
          });

          documents.sort((A,B) => {
            return A.time - B.time;
          });

          res.send(documents[0]);
        }
      );

    }).catch((reason) => {
      console.error(reason);
      res.status(500);
      res.send();
    });

  } else {
    res.status(400);
    res.send();
  }
});

module.exports = router;
<commit_msg>typo in info.js<commit_after>var express = require('express')
var router = express.Router()

var sanity_check = require('../common/sanity.js')
var stopLoc = require('../common/stopLoc.js')
var util = require('./util.js')

router.get('/', function(req, res) {
  res.send('INFO API');
});

router.post('/bus', function(req, res) {
  var query = req.body;
  if (
    sanity_check.isRequired(query.bus_no) &&
    sanity_check.isRequired(query.start_point) &&
    sanity_check.isRequired(query.end_point) &&
    sanity_check.isRequired(query.coord) &&
    sanity_check.isRequired(query.coord.lat) &&
    sanity_check.isRequired(query.coord.lng)
  ) {
    var db = req.db;
    var nearestStopPromise;
    if (query.src) {
      nearestStopPromise = new Promise((resolve, reject) => {
        resolve(query.src);
      });
    } else {
      nearestStopPromise = new Promise((resolve, reject) => {
        var collection = db.get('MasterBus');
        collection.findOne(
          {
            busNo : query.bus_no,
            source : query.start_point,
            destination : query.end_point,
          },
          {},
          function(err, entity) {
            if (err) reject(err);
            else {
              coordinatePromise = []
              entity.busStopList.forEach((busStop, i) => {
                var p = new Promise((accept, decline) => {
                  var StopCollection = db.get('BusStop');
                  StopCollection.findOne(
                    {"stopName": busStop },
                    {},
                    function(err, element) {
                      if (err) decline(err);
                      var d = util.distance(element.coord, query.coord);
                      accept({
                          "distance" : d,
                          "stopName" : busStop
                      });
                    }
                  );
                });
                coordinatePromise.push(p);
              });
              Promise.all(coordinatePromise).then((distances) => {
                distances.sort((A,B) => {
                  return A.distance - B.distance;
                })
                resolve(distances[0].stopName);
              }).catch( (reason) => {
                reject(reason);
              });

            }
          }
        );
      });
    }

    nearestStopPromise.then((nearestStop) => {

      var BusCollection = db.get('Bus');
      BusCollection.find(
        {
          busNo : query.bus_no,
          source : query.start_point,
          destination : query.end_point,
        },
        {},
        function(err, documents) {
          documents = documents.map((document) => {
            var time = document.Timings.filter((entry) => {
              return (entry.busStop === nearestStop)
            })[0].time;

            var stopList = document.Timings.map((entry) => {
              return entry.busStop;
            });

            var src;
            if (query.src ) {
              src = query.src;
            } else {
              src = nearestStop;
            }

            var dest;
            if (query.dest) {
              dest = query.dest;
            } else {
              dest = query.end_point;
            }

            var src_index, dest_index;
            for(var i=0; i < stopList.length; i++) {
              if (stopList[i] == src)
                src_index = i;
              else if (stopList[i] == dest)
                dest_index = i;
              else
                continue;
            }
            stopList = stopList.splice(src_index, dest_index+1);

            var busLocation;
            if (document.currLoc && ((new Date() - document.updateAt) <= 600000) ) {
              busLocation = document.currLoc;
            } else {
              var timings = document.Timings;
              timings.sort((A,B) => {
                return A.time - B.time;
              });

              var date = new Date();
              var threshold = date.getHours()*60 + date.getMinutes();

              timings = timings.filter((A) => {
                return A.time > threshold;
              });

              if (timings.length === 0) {
                timings.push(document.Timings[0]);
              }

              busLocation = stopLoc[timings[0].busStop];
            }

            return {
              "id"  : document.id,
              "stop" : nearestStop,
              "busLoc" : busLocation,
              "time" : time,
              "stopList": stopList
            }
          });

          var date = new Date();
          var threshold = date.getHours()*60 + date.getMinutes();

          documents = documents.filter((document) => {
            return (document.time >= threshold);
          });

          documents.sort((A,B) => {
            return A.time - B.time;
          });

          res.send(documents[0]);
        }
      );

    }).catch((reason) => {
      console.error(reason);
      res.status(500);
      res.send();
    });

  } else {
    res.status(400);
    res.send();
  }
});

module.exports = router;
<|endoftext|>


<commit_before>/**
 * Copyright 2020 Google LLC
 *
 * 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
 *
 *      https://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.
 */
const fs = require('fs');
const path = require('path');
const minimist = require('minimist');
const n3 = require('n3');

const ShexValidator = require('./shexValidator').Validator;
const utils = require('./util');
const parser = require('./parser');

class CliError extends Error {
    constructor(message) {
        super(message);
        this.name = "CliError";
    }
}

function cliFormatToN3(val) {
    switch (val) {
        case undefined:
            return 'N-Quads';
        case 'nquads':
            return 'N-Quads';
        case 'ntriples':
            return 'N-Triples';
        case 'turtle':
            return 'application/turtle';
        case 'trig':
            return 'application/trig';
        default:
            throw new CliError('Unknown output format');
    }
}

function writeResult(val, args) {
    if (!args.output) {
        console.log(result);
    } else {
        fs.writeFileSync(args.output, val);
    }
}

function writeQuads(quads, output, format) {
    const writer = new n3.Writer({format: cliFormatToN3(format)});
    quads.forEach(quad => writer.addQuad(quad.subject, quad.predicate, quad.object));
    writer.end((error, result) => {
        if (error) throw new CliError(error);
        writeResult(result, args);
    });
}

async function validateShEx(data, base, args) {
    args.service = args.service || '';
    const shapes = await utils.loadData(args.shex);
    const annotations = args.annotations ? JSON.parse(await utils.loadData(args.annotations)) : undefined;
    const validator = new ShexValidator(shapes, {annotations: annotations});
    const report = await validator.validate(data, args.target, {baseUrl: base});
    writeResult(JSON.stringify(report.failures, undefined, 2), args);
}

async function validateShacl(data, base, args) {
    const shapes = await utils.loadData(args.shacl);
    let subclasses, annotations;
    if (args.subclasses) {
        subclasses = await utils.loadData(args.subclasses);
    }
    if (args.annotations) {
        annotations = JSON.parse(await utils.loadData(args.annotations));
    }
    const validator = new ShaclValidator(shapes, {
        subclasses: subclasses,
        annotations: annotations
    });
    const report = validator.validate(data, {baseUrl: base});
    writeResult(JSON.stringify(report.failures, undefined, 2), args);
}

/**
 * @param {args} args
 * @return {Promise<void>}
 */
async function main(args) {
    if (!args.input) throw new CliError('No input file path specified');
    if (args.output && !fs.existsSync(path.parse(args.output).dir)) throw new CliError(`Output directory \'${path.parse(args.output).dir}\' doesn\'t exist`);
    const data = fs.readFileSync(args.input).toString();
    const base = args.base || utils.randomUrl()
    if (args.parse) {
        const quads = await parser.stringToQuads(data, base);
        writeQuads(quads, args.output, args.format);
    } else if (args.validate) {
        if (args.shex && args.shacl) throw new CliError('Validation is possible for ShEx or SHACL, but not for both');
        else if (args.shex) await validateShEx(data, base, args);
        else if (args.shacl) await validateShacl(data, base, args);
        else throw new CliError('No shapes provided for validation');
    }
}

/**
 * @typedef {{
 *     input: string
 *     output: string|undefined
 *     parse: boolean|undefined
 *     format: string|undefined
 *     validate: boolean|undefined
 *     shex: string|undefined
 *     shacl: string|undefined
 *     base: string|undefined
 *     target: string|undefined
 *     annotations: string|undefined
 *     subclasses: string|undefined
 * }} args - possible options for args
 */

const args = minimist(process.argv.slice(2));
main(args);

<commit_msg>Cli console output fix<commit_after>/**
 * Copyright 2020 Google LLC
 *
 * 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
 *
 *      https://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.
 */
const fs = require('fs');
const path = require('path');
const minimist = require('minimist');
const n3 = require('n3');

const ShexValidator = require('./shexValidator').Validator;
const utils = require('./util');
const parser = require('./parser');

class CliError extends Error {
    constructor(message) {
        super(message);
        this.name = "CliError";
    }
}

function cliFormatToN3(val) {
    switch (val) {
        case undefined:
            return 'N-Quads';
        case 'nquads':
            return 'N-Quads';
        case 'ntriples':
            return 'N-Triples';
        case 'turtle':
            return 'application/turtle';
        case 'trig':
            return 'application/trig';
        default:
            throw new CliError('Unknown output format');
    }
}

function writeResult(val, args) {
    if (!args.output) {
        console.log(val);
    } else {
        fs.writeFileSync(args.output, val);
    }
}

function writeQuads(quads, output, format) {
    const writer = new n3.Writer({format: cliFormatToN3(format)});
    quads.forEach(quad => writer.addQuad(quad.subject, quad.predicate, quad.object));
    writer.end((error, result) => {
        if (error) throw new CliError(error);
        writeResult(result, args);
    });
}

async function validateShEx(data, base, args) {
    args.service = args.service || '';
    const shapes = await utils.loadData(args.shex);
    const annotations = args.annotations ? JSON.parse(await utils.loadData(args.annotations)) : undefined;
    const validator = new ShexValidator(shapes, {annotations: annotations});
    const report = await validator.validate(data, args.target, {baseUrl: base});
    writeResult(JSON.stringify(report.failures, undefined, 2), args);
}

async function validateShacl(data, base, args) {
    const shapes = await utils.loadData(args.shacl);
    let subclasses, annotations;
    if (args.subclasses) {
        subclasses = await utils.loadData(args.subclasses);
    }
    if (args.annotations) {
        annotations = JSON.parse(await utils.loadData(args.annotations));
    }
    const validator = new ShaclValidator(shapes, {
        subclasses: subclasses,
        annotations: annotations
    });
    const report = validator.validate(data, {baseUrl: base});
    writeResult(JSON.stringify(report.failures, undefined, 2), args);
}

/**
 * @param {args} args
 * @return {Promise<void>}
 */
async function main(args) {
    if (!args.input) throw new CliError('No input file path specified');
    if (args.output && !fs.existsSync(path.parse(args.output).dir)) throw new CliError(`Output directory \'${path.parse(args.output).dir}\' doesn\'t exist`);
    const data = fs.readFileSync(args.input).toString();
    const base = args.base || utils.randomUrl()
    if (args.parse) {
        const quads = await parser.stringToQuads(data, base);
        writeQuads(quads, args.output, args.format);
    } else if (args.validate) {
        if (args.shex && args.shacl) throw new CliError('Validation is possible for ShEx or SHACL, but not for both');
        else if (args.shex) await validateShEx(data, base, args);
        else if (args.shacl) await validateShacl(data, base, args);
        else throw new CliError('No shapes provided for validation');
    }
}

/**
 * @typedef {{
 *     input: string
 *     output: string|undefined
 *     parse: boolean|undefined
 *     format: string|undefined
 *     validate: boolean|undefined
 *     shex: string|undefined
 *     shacl: string|undefined
 *     base: string|undefined
 *     target: string|undefined
 *     annotations: string|undefined
 *     subclasses: string|undefined
 * }} args - possible options for args
 */

const args = minimist(process.argv.slice(2));
main(args);

<|endoftext|>


<commit_before>/**
 * @license
 * Code City: Server
 *
 * Copyright 2017 Google Inc.
 *
 * 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.
 */

/**
 * @fileoverview A virtual world of collaborative coding.
 * @author fraser@google.com (Neil Fraser)
 */

// Start with: node codecity.js <DB directory>
'use strict';

const fs = require('fs');
const path = require('path');
const Interpreter = require('./interpreter');
const Serializer = require('./serialize');

var CodeCity = {};
CodeCity.databaseDirectory = '';
CodeCity.interpreter = null;

/**
 * Start a running instance of Code City.  May be called on a command line.
 * @param {string=} opt_databaseDirectory Directory containing either a .city
 * database or startup files.  If not present, look for the directory
 * as a command line parameter.
 */
CodeCity.startup = function(opt_databaseDirectory) {
  // process.argv is a list containing: ['node', 'codecity.js', 'databaseDir']
  CodeCity.databaseDirectory = opt_databaseDirectory || process.argv[2];

  // Check that the directory was specified and exists.
  try {
    var files = fs.readdirSync(CodeCity.databaseDirectory);
  } catch (e) {
    console.error('Database directory not found.\n' +
        'Usage: node %s <DB directory>', process.argv[1]);
    if (CodeCity.databaseDirectory) {
      console.info(e);
    }
    process.exit(1);
  }

  // Find the most recent database file.
  files.sort();
  for (var i = files.length - 1; i >= 0; i--) {
    if (files[i].match(
        /^\d{4}-\d\d-\d\dT\d\d\.\d\d\.\d\d(\.\d{1,3})?Z?\.city$/)) {
      break;
    }
  }
  // Load the interpreter.
  CodeCity.interpreter = new Interpreter({
    trimEval: true,
    trimProgram: true,
    methodNames: true,
  });
  CodeCity.initSystemFunctions();
  if (i === -1) {
    // Database not found, load one or more startup files instead.
    console.log('Unable to find database file in %s, looking for startup ' +
        'file(s) instead.', CodeCity.databaseDirectory);
    var fileCount = 0;
    for (var i = 0; i < files.length; i++) {
      if (files[i].match(/^(core|test).*\.js$/)) {
        var filename = path.join(CodeCity.databaseDirectory, files[i]);
        var contents = CodeCity.loadFile(filename);
        console.log('Loading startup file %s', filename);
        CodeCity.interpreter.createThreadForSrc(contents);
        fileCount++;
      }
    }
    if (fileCount === 0) {
      console.error('Unable to find startup file(s) in %s',
                    CodeCity.databaseDirectory);
      process.exit(1);
    }
    console.log('Loaded %d startup file(s).', fileCount);
  } else {
    var filename = path.join(CodeCity.databaseDirectory, files[i]);
    var contents = CodeCity.loadFile(filename);

    // Convert from text to JSON.
    try {
      contents = JSON.parse(contents);
    } catch (e) {
      console.error('Syntax error in parsing JSON: %s', filename);
      console.info(e);
      process.exit(1);
    }

    Serializer.deserialize(contents, CodeCity.interpreter);
    console.log('Database loaded: %s', filename);
  }

  // Checkpoint at regular intervals.
  // TODO: Let the interval be configurable from the database.
  setInterval(CodeCity.checkpoint, 60 * 1000);

  console.log('Load complete.  Starting Code City.');
  CodeCity.interpreter.start();
};

/**
 * Open a file and read its contents.  Die if there's an error.
 * @param {string} filename
 * @return {string} File contents.
 */
CodeCity.loadFile = function(filename) {
  // Load the specified file from disk.
  try {
    return fs.readFileSync(filename, 'utf8').toString();
  } catch (e) {
    console.error('Unable to open file: %s\nCheck permissions.', filename);
    console.info(e);
    process.exit(1);
  }
};

/**
 * Save the database to disk.
 * @param {boolean} sync True if Code City intends to shutdown afterwards.
 * False if Code City is running this in the background.
 */
CodeCity.checkpoint = function(sync) {
  console.log('Checkpointing...');
  try {
    CodeCity.interpreter.pause();
    var json = Serializer.serialize(CodeCity.interpreter);
  } finally {
    sync || CodeCity.interpreter.start();
  }
  // JSON.stringify(json) would work, but adding linebreaks so that every
  // object is on its own line makes the output more readable.
  var text = [];
  for (var i = 0; i < json.length; i++) {
    text.push(JSON.stringify(json[i]));
  }
  text = '[' + text.join(',\n') + ']';

  var filename = (new Date()).toISOString().replace(/:/g, '.') + '.city';
  filename = path.join(CodeCity.databaseDirectory, filename);
  var tmpFilename = filename + '.partial';
  try {
    fs.writeFileSync(tmpFilename, text);
    fs.renameSync(tmpFilename, filename);
    console.log('Checkpoint complete.');
  } catch (e) {
    console.error('Checkpoint failed!  ' + e);
  } finally {
    // Attempt to remove partially-written checkpoint if it still exists.
    try {
      fs.unlinkSync(tmpFilename);
    } catch (e) {
    }
  }
};

/**
 * Shutdown Code City.  Checkpoint the database before terminating.
 * Optional parameter is exit code (if numeric) or signal to (re-)kill
 * process with (if string).  Re-killing after checkpointing allows
 * systemd to accurately determine cause of death.  Defaults to 0.
 * @param {string|number=} code Exit code or signal.
 */
CodeCity.shutdown = function(code) {
  CodeCity.checkpoint(true);
  if (typeof code === 'string') {
    process.kill(process.pid, code);
  } else {
    process.exit(code || 0);
  }
};

/**
 * Print one line to the log.  Allows for interpolated strings.
 * @param {...*} var_args Arbitrary arguments for console.log.
 */
CodeCity.log = function(var_args) {
  console.log.apply(console, arguments);
};

/**
 * Initialize user-callable system functions.
 * These are not part of any JavaScript standard.
 */
CodeCity.initSystemFunctions = function() {
  var intrp = CodeCity.interpreter;
  intrp.createNativeFunction('CC.log', CodeCity.log, false);
  intrp.createNativeFunction('CC.checkpoint', CodeCity.checkpoint, false);
  intrp.createNativeFunction('CC.shutdown', function(code) {
    CodeCity.shutdown(Number(code));
  }, false);
};

// If this file is executed form a command line, startup Code City.
// Otherwise, if it is required as a library, do nothing.
if (require.main === module) {
  CodeCity.startup();

  // SIGTERM and SIGINT shut down server.
  process.once('SIGTERM', CodeCity.shutdown.bind(null, 'SIGTERM'));
  process.once('SIGINT', CodeCity.shutdown.bind(null, 'SIGINT'));

  // SIGHUP forces checkpoint.
  process.on('SIGHUP', CodeCity.checkpoint.bind(null, false));
}

module.exports = CodeCity;
<commit_msg>Change checkpoint interval to 10m<commit_after>/**
 * @license
 * Code City: Server
 *
 * Copyright 2017 Google Inc.
 *
 * 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.
 */

/**
 * @fileoverview A virtual world of collaborative coding.
 * @author fraser@google.com (Neil Fraser)
 */

// Start with: node codecity.js <DB directory>
'use strict';

const fs = require('fs');
const path = require('path');
const Interpreter = require('./interpreter');
const Serializer = require('./serialize');

var CodeCity = {};
CodeCity.databaseDirectory = '';
CodeCity.interpreter = null;

/**
 * Start a running instance of Code City.  May be called on a command line.
 * @param {string=} opt_databaseDirectory Directory containing either a .city
 * database or startup files.  If not present, look for the directory
 * as a command line parameter.
 */
CodeCity.startup = function(opt_databaseDirectory) {
  // process.argv is a list containing: ['node', 'codecity.js', 'databaseDir']
  CodeCity.databaseDirectory = opt_databaseDirectory || process.argv[2];

  // Check that the directory was specified and exists.
  try {
    var files = fs.readdirSync(CodeCity.databaseDirectory);
  } catch (e) {
    console.error('Database directory not found.\n' +
        'Usage: node %s <DB directory>', process.argv[1]);
    if (CodeCity.databaseDirectory) {
      console.info(e);
    }
    process.exit(1);
  }

  // Find the most recent database file.
  files.sort();
  for (var i = files.length - 1; i >= 0; i--) {
    if (files[i].match(
        /^\d{4}-\d\d-\d\dT\d\d\.\d\d\.\d\d(\.\d{1,3})?Z?\.city$/)) {
      break;
    }
  }
  // Load the interpreter.
  CodeCity.interpreter = new Interpreter({
    trimEval: true,
    trimProgram: true,
    methodNames: true,
  });
  CodeCity.initSystemFunctions();
  if (i === -1) {
    // Database not found, load one or more startup files instead.
    console.log('Unable to find database file in %s, looking for startup ' +
        'file(s) instead.', CodeCity.databaseDirectory);
    var fileCount = 0;
    for (var i = 0; i < files.length; i++) {
      if (files[i].match(/^(core|test).*\.js$/)) {
        var filename = path.join(CodeCity.databaseDirectory, files[i]);
        var contents = CodeCity.loadFile(filename);
        console.log('Loading startup file %s', filename);
        CodeCity.interpreter.createThreadForSrc(contents);
        fileCount++;
      }
    }
    if (fileCount === 0) {
      console.error('Unable to find startup file(s) in %s',
                    CodeCity.databaseDirectory);
      process.exit(1);
    }
    console.log('Loaded %d startup file(s).', fileCount);
  } else {
    var filename = path.join(CodeCity.databaseDirectory, files[i]);
    var contents = CodeCity.loadFile(filename);

    // Convert from text to JSON.
    try {
      contents = JSON.parse(contents);
    } catch (e) {
      console.error('Syntax error in parsing JSON: %s', filename);
      console.info(e);
      process.exit(1);
    }

    Serializer.deserialize(contents, CodeCity.interpreter);
    console.log('Database loaded: %s', filename);
  }

  // Checkpoint at regular intervals.
  // TODO: Let the interval be configurable from the database.
  setInterval(CodeCity.checkpoint, 600 * 1000);

  console.log('Load complete.  Starting Code City.');
  CodeCity.interpreter.start();
};

/**
 * Open a file and read its contents.  Die if there's an error.
 * @param {string} filename
 * @return {string} File contents.
 */
CodeCity.loadFile = function(filename) {
  // Load the specified file from disk.
  try {
    return fs.readFileSync(filename, 'utf8').toString();
  } catch (e) {
    console.error('Unable to open file: %s\nCheck permissions.', filename);
    console.info(e);
    process.exit(1);
  }
};

/**
 * Save the database to disk.
 * @param {boolean} sync True if Code City intends to shutdown afterwards.
 * False if Code City is running this in the background.
 */
CodeCity.checkpoint = function(sync) {
  console.log('Checkpointing...');
  try {
    CodeCity.interpreter.pause();
    var json = Serializer.serialize(CodeCity.interpreter);
  } finally {
    sync || CodeCity.interpreter.start();
  }
  // JSON.stringify(json) would work, but adding linebreaks so that every
  // object is on its own line makes the output more readable.
  var text = [];
  for (var i = 0; i < json.length; i++) {
    text.push(JSON.stringify(json[i]));
  }
  text = '[' + text.join(',\n') + ']';

  var filename = (new Date()).toISOString().replace(/:/g, '.') + '.city';
  filename = path.join(CodeCity.databaseDirectory, filename);
  var tmpFilename = filename + '.partial';
  try {
    fs.writeFileSync(tmpFilename, text);
    fs.renameSync(tmpFilename, filename);
    console.log('Checkpoint complete.');
  } catch (e) {
    console.error('Checkpoint failed!  ' + e);
  } finally {
    // Attempt to remove partially-written checkpoint if it still exists.
    try {
      fs.unlinkSync(tmpFilename);
    } catch (e) {
    }
  }
};

/**
 * Shutdown Code City.  Checkpoint the database before terminating.
 * Optional parameter is exit code (if numeric) or signal to (re-)kill
 * process with (if string).  Re-killing after checkpointing allows
 * systemd to accurately determine cause of death.  Defaults to 0.
 * @param {string|number=} code Exit code or signal.
 */
CodeCity.shutdown = function(code) {
  CodeCity.checkpoint(true);
  if (typeof code === 'string') {
    process.kill(process.pid, code);
  } else {
    process.exit(code || 0);
  }
};

/**
 * Print one line to the log.  Allows for interpolated strings.
 * @param {...*} var_args Arbitrary arguments for console.log.
 */
CodeCity.log = function(var_args) {
  console.log.apply(console, arguments);
};

/**
 * Initialize user-callable system functions.
 * These are not part of any JavaScript standard.
 */
CodeCity.initSystemFunctions = function() {
  var intrp = CodeCity.interpreter;
  intrp.createNativeFunction('CC.log', CodeCity.log, false);
  intrp.createNativeFunction('CC.checkpoint', CodeCity.checkpoint, false);
  intrp.createNativeFunction('CC.shutdown', function(code) {
    CodeCity.shutdown(Number(code));
  }, false);
};

// If this file is executed form a command line, startup Code City.
// Otherwise, if it is required as a library, do nothing.
if (require.main === module) {
  CodeCity.startup();

  // SIGTERM and SIGINT shut down server.
  process.once('SIGTERM', CodeCity.shutdown.bind(null, 'SIGTERM'));
  process.once('SIGINT', CodeCity.shutdown.bind(null, 'SIGINT'));

  // SIGHUP forces checkpoint.
  process.on('SIGHUP', CodeCity.checkpoint.bind(null, false));
}

module.exports = CodeCity;
<|endoftext|>


<commit_before>function bytesToSize(bytes) {
   var sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
   if (bytes == 0) return '0 B';
   var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
   return Math.round(bytes / Math.pow(1024, i), 2) + ' ' + sizes[i];
}

function formatFloat(f, eps) {
  if (!eps) eps = 2;
  eps = Math.pow(10, eps);
  return parseInt(f * eps) / eps;
}

function makeRatio(tx, rx) {
  var r = "0.0";
  if ( rx > 0 ) {
    if ( tx > 0 ) {
      r = "" + formatFloat(tx / rx);
    }
	} else if ( tx > 0 ) {
		r = "∞";
	}
  return r;
}

var Torrent = function(data) {
    this.Name = data.Name;
    this.State = data.State;
    this.Infohash = data.Infohash;
    this.Peers = function() { return data.Peers ? data.Peers.length : 0; };
    this.Speed = function() {
        var tx = 0, rx = 0;
        if (data.Peers)
            data.Peers.forEach(function(p){tx += p.TX; rx += p.RX;});
        return "↑ " + bytesToSize(tx) +"/s ↓ " + bytesToSize(rx) + "/s";
    };
    this.RX = function() {
      var rx = 0;
      if(data.Peers) data.Peers.forEach(function(p) { rx += p.RX; });
      return rx;
    };
    this.TX = function() {
      var tx = 0;
      if(data.Peers) data.Peers.forEach(function(p) { tx += p.TX; });
      return tx;
    };
    this.Data = function() {
      return data;
    };
    this.TotalSize = function() {
      var total_size = 0;
      if(data.Files) {
        data.Files.forEach(function(f){ total_size += f.FileInfo.Length });
      }
      return bytesToSize(total_size);
    };
    this.Progress = data.Progress * 100;
    this.Ratio = function() {
      return "("+makeRatio(data.TX, data.RX) + " ratio)";
    };

  this.changeTorrent = function(action)
  {
    viewModel._apicall({method: "XD.ChangeTorrent", action: action, infohash: this.Infohash, swarm: "0"}, function(data){
      console.log(data);
    });
  }.bind(this);

  this.remove = function()
  {
    this.changeTorrent("remove");
  }.bind(this);

  this.start = function()
  {
    this.changeTorrent("start");
  }.bind(this);

  this.stop = function()
  {
    this.changeTorrent("stop");
  }.bind(this);

  this.toggle = function()
  {
    if(this.Stopped())
      this.start();
    else
      this.stop();
  }.bind(this);

  this.Stopped = function()
  {
    return data.State == "stopped";
  };

  this.StatusButton = function()
  {
    if (this.Stopped())
    {
      return "▶";
    }
    else
    {
      return "⏸";
    }
  };
}

var viewModel = {
    _url: window.location.protocol + "//" + window.location.host + "/ecksdee/api",
    _apicall: function(call, cb)
    {
        $.ajax({
            type: "POST",
            url: this._url,
            contentType: "text/json; charset=UTF-8",
            data: JSON.stringify(call),
            success: function(j, text, xhr) {
                // console.log(call, j);
                cb(JSON.parse(j));
            }
        });
    },
    torrents: ko.observableArray(),
    torrentURL: ko.observable(),
    torrentFilter: ko.observable('all').bind(this),
    setFilter: function(state) {
        this.torrentFilter(state); main(); },
    addTorrent: function()
    {
        var _this = this;
        this._apicall({method: "XD.AddTorrent", swarm: "0", url: this.torrentURL()}, function(data){
            if (!data.error) _this.torrentURL("");
        });
    },
    torrentStates: ['all', 'downloading', 'seeding'],
    globalInfo: function()
    {
        var rx = 0;
        var tx = 0;
        var peers = 0;
        var rtx = 0;
        var rrx = 0;
        var count = 0;
        this.torrents().forEach(function(t) {
            rx += t.RX();
            tx += t.TX();
            rrx += t.Data().RX;
            rtx += t.Data().TX;
            peers += t.Peers();
            count ++;
        });
        return peers+" peers connected on "+ count+ " torrents (" + makeRatio(rtx, rrx) + " ratio) ↑ " + bytesToSize(tx) +"/s ↓ " + bytesToSize(rx) + "/s";
    }
};

function main()
{
    viewModel._apicall({method: "XD.SwarmStatus"}, function(data){
        viewModel.torrents.removeAll();
        for (var prop in data) {
            if (viewModel.torrentFilter() != 'all' & viewModel.torrentFilter() != data[prop].State)
                continue;
            viewModel.torrents.push(new Torrent(data[prop]));
        }
    });
}

window.onload = function()
{
    ko.applyBindings(viewModel);
    main(); setInterval(main, 1000);
}
<commit_msg>relative url<commit_after>function bytesToSize(bytes) {
   var sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
   if (bytes == 0) return '0 B';
   var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
   return Math.round(bytes / Math.pow(1024, i), 2) + ' ' + sizes[i];
}

function formatFloat(f, eps) {
  if (!eps) eps = 2;
  eps = Math.pow(10, eps);
  return parseInt(f * eps) / eps;
}

function makeRatio(tx, rx) {
  var r = "0.0";
  if ( rx > 0 ) {
    if ( tx > 0 ) {
      r = "" + formatFloat(tx / rx);
    }
	} else if ( tx > 0 ) {
		r = "∞";
	}
  return r;
}

var Torrent = function(data) {
    this.Name = data.Name;
    this.State = data.State;
    this.Infohash = data.Infohash;
    this.Peers = function() { return data.Peers ? data.Peers.length : 0; };
    this.Speed = function() {
        var tx = 0, rx = 0;
        if (data.Peers)
            data.Peers.forEach(function(p){tx += p.TX; rx += p.RX;});
        return "↑ " + bytesToSize(tx) +"/s ↓ " + bytesToSize(rx) + "/s";
    };
    this.RX = function() {
      var rx = 0;
      if(data.Peers) data.Peers.forEach(function(p) { rx += p.RX; });
      return rx;
    };
    this.TX = function() {
      var tx = 0;
      if(data.Peers) data.Peers.forEach(function(p) { tx += p.TX; });
      return tx;
    };
    this.Data = function() {
      return data;
    };
    this.TotalSize = function() {
      var total_size = 0;
      if(data.Files) {
        data.Files.forEach(function(f){ total_size += f.FileInfo.Length });
      }
      return bytesToSize(total_size);
    };
    this.Progress = data.Progress * 100;
    this.Ratio = function() {
      return "("+makeRatio(data.TX, data.RX) + " ratio)";
    };

  this.changeTorrent = function(action)
  {
    viewModel._apicall({method: "XD.ChangeTorrent", action: action, infohash: this.Infohash, swarm: "0"}, function(data){
      console.log(data);
    });
  }.bind(this);

  this.remove = function()
  {
    this.changeTorrent("remove");
  }.bind(this);

  this.start = function()
  {
    this.changeTorrent("start");
  }.bind(this);

  this.stop = function()
  {
    this.changeTorrent("stop");
  }.bind(this);

  this.toggle = function()
  {
    if(this.Stopped())
      this.start();
    else
      this.stop();
  }.bind(this);

  this.Stopped = function()
  {
    return data.State == "stopped";
  };

  this.StatusButton = function()
  {
    if (this.Stopped())
    {
      return "▶";
    }
    else
    {
      return "⏸";
    }
  };
}

var viewModel = {
    _url: "ecksdee/api",
    _apicall: function(call, cb)
    {
        $.ajax({
            type: "POST",
            url: this._url,
            contentType: "text/json; charset=UTF-8",
            data: JSON.stringify(call),
            success: function(j, text, xhr) {
                // console.log(call, j);
                cb(JSON.parse(j));
            }
        });
    },
    torrents: ko.observableArray(),
    torrentURL: ko.observable(),
    torrentFilter: ko.observable('all').bind(this),
    setFilter: function(state) {
        this.torrentFilter(state); main(); },
    addTorrent: function()
    {
        var _this = this;
        this._apicall({method: "XD.AddTorrent", swarm: "0", url: this.torrentURL()}, function(data){
            if (!data.error) _this.torrentURL("");
        });
    },
    torrentStates: ['all', 'downloading', 'seeding'],
    globalInfo: function()
    {
        var rx = 0;
        var tx = 0;
        var peers = 0;
        var rtx = 0;
        var rrx = 0;
        var count = 0;
        this.torrents().forEach(function(t) {
            rx += t.RX();
            tx += t.TX();
            rrx += t.Data().RX;
            rtx += t.Data().TX;
            peers += t.Peers();
            count ++;
        });
        return peers+" peers connected on "+ count+ " torrents (" + makeRatio(rtx, rrx) + " ratio) ↑ " + bytesToSize(tx) +"/s ↓ " + bytesToSize(rx) + "/s";
    }
};

function main()
{
    viewModel._apicall({method: "XD.SwarmStatus"}, function(data){
        viewModel.torrents.removeAll();
        for (var prop in data) {
            if (viewModel.torrentFilter() != 'all' & viewModel.torrentFilter() != data[prop].State)
                continue;
            viewModel.torrents.push(new Torrent(data[prop]));
        }
    });
}

window.onload = function()
{
    ko.applyBindings(viewModel);
    main(); setInterval(main, 1000);
}
<|endoftext|>


<commit_before>import Field from '../Field';
import React, { PropTypes } from 'react';
import { Button, FormField, FormInput, FormNote } from 'elemental';
import FileChangeMessage from '../../components/FileChangeMessage';
import HiddenFileInput from '../../components/HiddenFileInput';

const buildInitialState = () => ({
	action: null,
	removeExisting: false,
	userSelectedFile: null,
});

module.exports = Field.create({
	propTypes: {
		autoCleanup: PropTypes.bool,
		collapse: PropTypes.bool,
		label: PropTypes.string,
		note: PropTypes.string,
		path: PropTypes.string.isRequired,
		paths: PropTypes.shape({
			action: PropTypes.string.isRequired,
			upload: PropTypes.string.isRequired,
		}).isRequired,
		value: PropTypes.shape({
			filename: PropTypes.string,
			// TODO: these are present but not used in the UI,
			//       should we start using them?
			// filetype: PropTypes.string,
			// originalname: PropTypes.string,
			// path: PropTypes.string,
			// size: PropTypes.number,
		}),
	},
	getInitialState () {
		return buildInitialState();
	},
	shouldCollapse () {
		return this.props.collapse && !this.hasExisting();
	},

	// ==============================
	// HELPERS
	// ==============================

	hasFile () {
		return this.hasExisting() || !!this.state.userSelectedFile;
	},
	hasExisting () {
		return this.props.value && !!this.props.value.filename;
	},
	getFilename () {
		return this.state.userSelectedFile
			? this.state.userSelectedFile.name
			: this.props.value.filename;
	},

	// ==============================
	// METHODS
	// ==============================

	triggerFileBrowser () {
		this.refs.fileInput.clickDomNode();
	},
	handleFileChange (event) {
		const userSelectedFile = event.target.files[0];

		this.setState({
			userSelectedFile: userSelectedFile,
		});
	},
	handleRemove (e) {
		var state = {};

		if (this.state.userSelectedFile) {
			state.userSelectedFile = null;
		} else if (this.hasExisting()) {
			state.removeExisting = true;

			if (this.props.autoCleanup) {
				if (e.altKey) {
					state.action = 'reset';
				} else {
					state.action = 'delete';
				}
			} else {
				if (e.altKey) {
					state.action = 'delete';
				} else {
					state.action = 'reset';
				}
			}
		}

		this.setState(state);
	},
	undoRemove () {
		this.setState(buildInitialState());
	},

	// ==============================
	// RENDERERS
	// ==============================

	renderFileNameAndOptionalMessage (showChangeMessage = false) {
		return (

				{(this.hasFile() && !this.state.removeExisting) ? (
					<FileChangeMessage>
						{this.getFilename()}
					</FileChangeMessage>
				) : null}
				{showChangeMessage && this.renderChangeMessage()}

		);
	},
	renderChangeMessage () {
		if (this.state.userSelectedFile) {
			return (
				<FileChangeMessage type="success">
					File selected - save to upload
				</FileChangeMessage>
			);
		} else if (this.state.removeExisting) {
			return (
				<FileChangeMessage type="danger">
					File {this.props.autoCleanup ? 'deleted' : 'removed'} - save to confirm
				</FileChangeMessage>
			);
		} else {
			return null;
		}
	},
	renderClearButton () {
		if (this.state.removeExisting) {
			return (
				<Button type="link" onClick={this.undoRemove}>
					Undo Remove
				</Button>
			);
		} else {
			var clearText;
			if (this.state.userSelectedFile) {
				clearText = 'Cancel Upload';
			} else {
				clearText = (this.props.autoCleanup ? 'Delete File' : 'Remove File');
			}
			return (
				<Button type="link-cancel" onClick={this.handleRemove}>
					{clearText}
				</Button>
			);
		}
	},
	renderUI () {
		const buttons = (

				<Button onClick={this.triggerFileBrowser}>
					{this.hasFile() ? 'Change' : 'Upload'} File
				</Button>
				{this.hasFile() && this.renderClearButton()}

		);

		return (

				<FormField label={this.props.label} htmlFor={this.props.path}>
					{this.shouldRenderField() ? (

							{this.hasFile() && this.renderFileNameAndOptionalMessage(true)}
							{buttons}
							<HiddenFileInput
								name={this.props.paths.upload}
								onChange={this.handleFileChange}
								ref="fileInput"
							/>
							<input
								name={this.props.paths.action}
								type="hidden"
								value={this.state.action}
							/>

					) : (

							{this.hasFile()
								? this.renderFileNameAndOptionalMessage()
								: <FormInput noedit>no file</FormInput>}

					)}
					{!!this.props.note && <FormNote note={this.props.note} />}
				</FormField>

		);
	},

});
<commit_msg>Updating FileField component to match the spec<commit_after>import Field from '../Field';
import React, { PropTypes } from 'react';
import { Button, FormField, FormInput, FormNote } from 'elemental';
import FileChangeMessage from '../../components/FileChangeMessage';
import HiddenFileInput from '../../components/HiddenFileInput';

let uploadInc = 0;

const buildInitialState = (props) => ({
	action: null,
	removeExisting: false,
	uploadFieldPath: `${props.path}-${++uploadInc}`,
	userSelectedFile: null,
});

module.exports = Field.create({
	propTypes: {
		autoCleanup: PropTypes.bool,
		collapse: PropTypes.bool,
		label: PropTypes.string,
		note: PropTypes.string,
		path: PropTypes.string.isRequired,
		value: PropTypes.shape({
			filename: PropTypes.string,
			// TODO: these are present but not used in the UI,
			//       should we start using them?
			// filetype: PropTypes.string,
			// originalname: PropTypes.string,
			// path: PropTypes.string,
			// size: PropTypes.number,
		}),
	},
	getInitialState () {
		return buildInitialState(this.props);
	},
	shouldCollapse () {
		return this.props.collapse && !this.hasExisting();
	},

	// ==============================
	// HELPERS
	// ==============================

	hasFile () {
		return this.hasExisting() || !!this.state.userSelectedFile;
	},
	hasExisting () {
		return this.props.value && !!this.props.value.filename;
	},
	getFilename () {
		return this.state.userSelectedFile
			? this.state.userSelectedFile.name
			: this.props.value.filename;
	},

	// ==============================
	// METHODS
	// ==============================

	triggerFileBrowser () {
		this.refs.fileInput.clickDomNode();
	},
	handleFileChange (event) {
		const userSelectedFile = event.target.files[0];

		this.setState({
			userSelectedFile: userSelectedFile,
		});
	},
	handleRemove (e) {
		var state = {};

		if (this.state.userSelectedFile) {
			state.userSelectedFile = null;
		} else if (this.hasExisting()) {
			state.removeExisting = true;

			if (this.props.autoCleanup) {
				if (e.altKey) {
					state.action = 'reset';
				} else {
					state.action = 'delete';
				}
			} else {
				if (e.altKey) {
					state.action = 'delete';
				} else {
					state.action = 'reset';
				}
			}
		}

		this.setState(state);
	},
	undoRemove () {
		this.setState(buildInitialState(this.props));
	},

	// ==============================
	// RENDERERS
	// ==============================

	renderFileNameAndOptionalMessage (showChangeMessage = false) {
		return (

				{(this.hasFile() && !this.state.removeExisting) ? (
					<FileChangeMessage>
						{this.getFilename()}
					</FileChangeMessage>
				) : null}
				{showChangeMessage && this.renderChangeMessage()}

		);
	},
	renderChangeMessage () {
		if (this.state.userSelectedFile) {
			return (
				<FileChangeMessage type="success">
					File selected - save to upload
				</FileChangeMessage>
			);
		} else if (this.state.removeExisting) {
			return (
				<FileChangeMessage type="danger">
					File {this.props.autoCleanup ? 'deleted' : 'removed'} - save to confirm
				</FileChangeMessage>
			);
		} else {
			return null;
		}
	},
	renderClearButton () {
		if (this.state.removeExisting) {
			return (
				<Button type="link" onClick={this.undoRemove}>
					Undo Remove
				</Button>
			);
		} else {
			var clearText;
			if (this.state.userSelectedFile) {
				clearText = 'Cancel Upload';
			} else {
				clearText = (this.props.autoCleanup ? 'Delete File' : 'Remove File');
			}
			return (
				<Button type="link-cancel" onClick={this.handleRemove}>
					{clearText}
				</Button>
			);
		}
	},
	renderActionInput () {
		// If the user has selected a file for uploading, we need to point at
		// the upload field. If the file is being deleted, we submit that.
		if (this.state.userSelectedFile || this.state.action) {
			const value = this.state.userSelectedFile
				? `upload:${this.state.uploadFieldPath}`
				: (this.state.action === 'delete' ? 'remove' : '');
			return (
				<input
					name={this.props.path}
					type="hidden"
					value={value}
				/>
			);
		} else {
			return null;
		}
	},
	renderUI () {
		const buttons = (

				<Button onClick={this.triggerFileBrowser}>
					{this.hasFile() ? 'Change' : 'Upload'} File
				</Button>
				{this.hasFile() && this.renderClearButton()}

		);

		return (

				<FormField label={this.props.label} htmlFor={this.props.path}>
					{this.shouldRenderField() ? (

							{this.hasFile() && this.renderFileNameAndOptionalMessage(true)}
							{buttons}
							<HiddenFileInput
								name={this.state.uploadFieldPath}
								onChange={this.handleFileChange}
								ref="fileInput"
							/>
							{this.renderActionInput()}

					) : (

							{this.hasFile()
								? this.renderFileNameAndOptionalMessage()
								: <FormInput noedit>no file</FormInput>}

					)}
					{!!this.props.note && <FormNote note={this.props.note} />}
				</FormField>

		);
	},

});
<|endoftext|>


<commit_before>import { Entity } from 'draft-js'

const getSelectionEntities = (editorState, entityType) => {
  // Selection cursor
  const targetSelection = editorState.getSelection()
  const startOffset = targetSelection.getStartOffset()
  const endOffset = targetSelection.getEndOffset()

  const currentContent = editorState.getCurrentContent()

  const block = currentContent.getBlockForKey(targetSelection.getStartKey())

  const entitiesSelection = []

  block.findEntityRanges(character => {
    const entityKey = character.getEntity()
    return entityKey !== null && Entity.get(entityKey).getType() === entityType
  }, (start, end) => {
    if (start >= startOffset && end <= endOffset) {
      entitiesSelection.push({
        blockKey: block.getKey(),
        entityKey: block.getEntityAt(start),
        start,
        end
      })
    }
  })

  return entitiesSelection
}

export default getSelectionEntities
<commit_msg>Fix utils to get selection entities [ci skip]<commit_after>import { Entity } from 'draft-js'

const getSelectionEntities = (editorState, entityType) => {
  // Selection cursor
  const targetSelection = editorState.getSelection()
  const endOffset = targetSelection.getEndOffset()

  const currentContent = editorState.getCurrentContent()

  const block = currentContent.getBlockForKey(targetSelection.getStartKey())

  const entitiesSelection = []

  block.findEntityRanges(character => {
    const entityKey = character.getEntity()
    return entityKey !== null && Entity.get(entityKey).getType() === entityType
  }, (start, end) => {

    const isSelected = end >= endOffset && start <= endOffset

    if (isSelected) {
      entitiesSelection.push({
        blockKey: block.getKey(),
        entityKey: block.getEntityAt(start),
        start,
        end
      })
    }
  })

  return entitiesSelection
}

export default getSelectionEntities
<|endoftext|>