<commit_before>import React, {Component} from 'react';
import styled from 'styled-components';

import icons from '../img/icons';
import {colors, sizes, zIndex} from '../constants';

const Wrapper = styled.div`
  width: 100%;
  height: 100%;
  background-color: ${colors.black};
  color: ${colors.white};
  z-index: ${zIndex.list};
  flex-basis: ${sizes.list.height};
  flex-grow: 0;
  display: flex;
  flex-direction: column;
`;

const Container = styled.div`
  overflow: hidden;
  display: flex;
  width: 100%;
  height: 100%;
  flex-direction: ${props => props.direction};
`;

const Item = styled.div`
  flex-grow: 1;
  max-width: 50vw;
  display: flex;
  flex-direction: column;

  &:nth-of-type(even) {
    background-color: ${colors.lightGrey};
  }

  &:nth-of-type(odd) {
    background-color: ${colors.darkGrey};
  }
`;

const Heading = styled.div`
  text-transform: uppercase;
  text-align: right;
  font-size: .8em;
  padding: .4rem;
  color: ${colors.veryLightGrey};
`;

const Info = styled.div`
  padding: .4rem;
  flex-grow: 1;
  display: flex;
  align-items: center;
`;

const Text = styled.span`
  margin: .4rem;
`;

const InfoItem = styled.div`
  background-color: ${colors.red};
  color: ${colors.white};
  display: flex;
  justify-content: center;
  align-items: center;
`;

export default class List extends Component {
  renderInbound() {
    const {inbound} = this.props.queries;

    return inbound.map((query,index) => (
      <Item key={index}>
        <Heading>{query.destination.name}</Heading>
        <Info>

          <Text>{query.origin.name}</Text>
        </Info>
      </Item>
    ));
  }

  renderOutbound() {
    const {outbound} = this.props.queries;

    return outbound.map((query,index) => (
      <Item key={index}>
        <Heading>{query.origin.name}</Heading>
        <Info>

          <Text>{query.destination.name}</Text>
        </Info>
      </Item>
    ));
  }

  render() {
    return (
      <Wrapper>
        <Container direction="row">
          <InfoItem>

          </InfoItem>
          {this.renderOutbound()}
        </Container>
        <Container direction="row-reverse">
          <InfoItem>

          </InfoItem>
          {this.renderInbound()}
        </Container>
      </Wrapper>
    );
  }
}
<commit_msg>adjust to use icons<commit_after>import React, {Component} from 'react';
import styled from 'styled-components';

import icons from '../img/icons';
import {colors, sizes, zIndex} from '../constants';

const Wrapper = styled.div`
  width: 100%;
  height: 100%;
  background-color: ${colors.black};
  color: ${colors.white};
  z-index: ${zIndex.list};
  flex-basis: ${sizes.list.height};
  flex-grow: 0;
  display: flex;
  flex-direction: column;
`;

const Container = styled.div`
  overflow: hidden;
  display: flex;
  flex-direction: ${props => props.direction};
  width: 100%;
  height: 100%;
`;

const Item = styled.div`
  flex-grow: 1;
  max-width: 50vw;
  display: flex;
  flex-direction: column;

  &:nth-of-type(even) {
    background-color: ${colors.lightGrey};
  }

  &:nth-of-type(odd) {
    background-color: ${colors.darkGrey};
  }
`;

const Heading = styled.div`
  text-transform: uppercase;
  text-align: right;
  font-size: .8em;
  padding: .4rem;
  color: ${colors.veryLightGrey};
`;

const Info = styled.div`
  padding: .4rem;
  flex-grow: 1;
  display: flex;
  align-items: center;
`;

const Text = styled.span`
  margin: .4rem;
`;

const InfoItem = styled.div`
  padding: .4rem;
  width: ${sizes.list.info.width};
  background-color: ${colors.red};
  color: ${colors.white};
  display: flex;
  justify-content: center;
  align-items: center;
`;

export default class List extends Component {
  renderInbound() {
    const {inbound} = this.props.queries;

    return inbound.map((query,index) => (
      <Item key={index}>
        <Heading>{query.destination.name}</Heading>
        <Info>

          <Text>{query.origin.name}</Text>
        </Info>
      </Item>
    ));
  }

  renderOutbound() {
    const {outbound} = this.props.queries;

    return outbound.map((query,index) => (
      <Item key={index}>
        <Heading>{query.origin.name}</Heading>
        <Info>

          <Text>{query.destination.name}</Text>
        </Info>
      </Item>
    ));
  }

  render() {
    return (
      <Wrapper>
        <Container direction="row">
          <InfoItem>

          </InfoItem>
          {this.renderOutbound()}
        </Container>
        <Container direction="row-reverse">
          <InfoItem>

          </InfoItem>
          {this.renderInbound()}
        </Container>
      </Wrapper>
    );
  }
}
<|endoftext|>


<commit_before>var App = App || {};
App.classes = App.classes || {};

App.classes.IndexView = Backbone.View.extend({
	el: '#content',
	template: App.templates.index,
	// This is a totally static page, so lets render here, no need for a seperate render function.
	// Data will be empty but this is just an example :)
	initialize: function(data) {
		'use strict';
		this.$el.html(this.template(data));
	}
});

// The about view is basically the same as the index view.
App.classes.AboutView = App.classes.IndexView.extend({
	template: App.templates.about
});<commit_msg>Now inherits initialize and render methods from superclass<commit_after>var App = App || {};
App.classes = App.classes || {};

App.classes.IndexView = App.classes.BaseView.extend({
	el: '#content',
	template: App.templates.index,
	// This is a totally static page, so lets render here, no need for a seperate render function.
	// Data will be empty but this is just an example :)
	initialize: function(data) {
		'use strict';
		this.render(data);
	}
});<|endoftext|>


<commit_before>const http = require('http');

var urlWhitelist = new Set([
    'https://straylight.jp/one/00001',
    'https://straylight.jp/one/00002',
    'https://straylight.jp/one/vk2g7',
    'https://straylight.jp/one/b4cz6',
    'https://straylight.jp/one/6ej7n',
    'https://straylight.jp/one/u36bx',
    'https://straylight.jp/one/9y2tk',
    'https://straylight.jp/one/33fxm',
    'https://straylight.jp/one/ujv3w',
    'https://straylight.jp/one/zz6n7',
]);
var macAddressWhitelist = new Set([
    'F0:2A;63:5C;E3:E5',  // SLBeacon00001
    'EB:B4:73:21:AC:3C',  // SLBeacon00002
    'D7:AF:DA:DF:43:85',  // SLBeacon00003
]);

function send(action) {
  var url = 'http://192.168.0.3:8080/' + action;
  console.info('GET ' + url);
  http.get(url).on('error', function(e) {
    console.error('ERROR GET ' + url + ' ' + e.message);
  });
}

function throttle(seconds, callback) {
  var lastExecutionTime;
  return function() {
    if (lastExecutionTime === undefined ||
        new Date().getTime() >= lastExecutionTime.getTime() + seconds) {
      callback();
      lastExecutionTime = new Date();
    }
  };
}

var unlock = throttle(2000, function() {
  send('unlock');
});

exports.onMessage = function(message) {
  var data = JSON.parse(message);
  console.info('RECEIVED: ' + message);
  if (message.type == 'nfc') {
    authorizeNfc(message.url);
  } else if (message.type == 'ble') {
    authorizeBle(message.macAddress, message.rssi);
  } else {
    console.error('Unknown message type: ' + message.type);
  }
}

function authorizeNfc(url) {
  if (urlWhitelist.has(url)) {
    unlock();
  }
}

function authorizeBle(macAddress, rssi) {
  if (macAddressWhitelist.has(macAddress) && rssi > -90) {
    unlock();
  }
}

<commit_msg>Fix typo.<commit_after>const http = require('http');

var urlWhitelist = new Set([
    'https://straylight.jp/one/00001',
    'https://straylight.jp/one/00002',
    'https://straylight.jp/one/vk2g7',
    'https://straylight.jp/one/b4cz6',
    'https://straylight.jp/one/6ej7n',
    'https://straylight.jp/one/u36bx',
    'https://straylight.jp/one/9y2tk',
    'https://straylight.jp/one/33fxm',
    'https://straylight.jp/one/ujv3w',
    'https://straylight.jp/one/zz6n7',
]);
var macAddressWhitelist = new Set([
    'F0:2A;63:5C;E3:E5',  // SLBeacon00001
    'EB:B4:73:21:AC:3C',  // SLBeacon00002
    'D7:AF:DA:DF:43:85',  // SLBeacon00003
]);

function send(action) {
  var url = 'http://192.168.0.3:8080/' + action;
  console.info('GET ' + url);
  http.get(url).on('error', function(e) {
    console.error('ERROR GET ' + url + ' ' + e.message);
  });
}

function throttle(seconds, callback) {
  var lastExecutionTime;
  return function() {
    if (lastExecutionTime === undefined ||
        new Date().getTime() >= lastExecutionTime.getTime() + seconds) {
      callback();
      lastExecutionTime = new Date();
    }
  };
}

var unlock = throttle(2000, function() {
  send('unlock');
});

exports.onMessage = function(message) {
  console.info('RECEIVED: ' + message);
  var data = JSON.parse(message);
  if (data.type == 'nfc') {
    authorizeNfc(data.url);
  } else if (data.type == 'ble') {
    authorizeBle(data.macAddress, data.rssi);
  } else {
    console.error('Unknown message type: ' + data.type);
  }
}

function authorizeNfc(url) {
  if (urlWhitelist.has(url)) {
    unlock();
  }
}

function authorizeBle(macAddress, rssi) {
  if (macAddressWhitelist.has(macAddress) && rssi > -90) {
    unlock();
  }
}

<|endoftext|>


<commit_before>const Metrics = require('./Metrics')

const ONE_MB = 1024 * 1024

function emitPdfStats(stats, timings) {
  if (timings['compute-pdf-caching']) {
    emitPdfCachingStats(stats, timings)
  } else {
    // How much bandwidth will the pdf incur when downloaded in full?
    Metrics.summary('pdf-bandwidth', stats['pdf-size'])
  }
}

function emitPdfCachingStats(stats, timings) {
  if (!stats['pdf-size']) return // double check

  // How large is the overhead of hashing up-front?
  const fraction =
    timings.compileE2E - timings['compute-pdf-caching'] !== 0
      ? timings.compileE2E /
        (timings.compileE2E - timings['compute-pdf-caching'])
      : 1
  Metrics.summary('overhead-compute-pdf-ranges', fraction * 100 - 100)

  // How does the hashing scale to pdf size in MB?
  Metrics.timing(
    'compute-pdf-caching-relative-to-pdf-size',
    timings['compute-pdf-caching'] / (stats['pdf-size'] / ONE_MB)
  )
  if (stats['pdf-caching-total-ranges-size']) {
    // How does the hashing scale to total ranges size in MB?
    Metrics.timing(
      'compute-pdf-caching-relative-to-total-ranges-size',
      timings['compute-pdf-caching'] /
        (stats['pdf-caching-total-ranges-size'] / ONE_MB)
    )
    // How fast is the hashing per range on average?
    Metrics.timing(
      'compute-pdf-caching-relative-to-ranges-count',
      timings['compute-pdf-caching'] / stats['pdf-caching-n-ranges']
    )

    // How many ranges are new?
    Metrics.summary(
      'new-pdf-ranges-relative-to-total-ranges',
      (stats['pdf-caching-n-new-ranges'] / stats['pdf-caching-n-ranges']) * 100
    )
  }

  // How much content is cacheable?
  Metrics.summary(
    'cacheable-ranges-to-pdf-size',
    (stats['pdf-caching-total-ranges-size'] / stats['pdf-size']) * 100
  )

  const sizeWhenDownloadedInFull =
    // All of the pdf
    stats['pdf-size'] -
    // These ranges are potentially cached.
    stats['pdf-caching-total-ranges-size'] +
    // These ranges are not cached.
    stats['pdf-caching-new-ranges-size']

  // How much bandwidth can we save when downloading the pdf in full?
  Metrics.summary(
    'pdf-bandwidth-savings',
    100 - (sizeWhenDownloadedInFull / stats['pdf-size']) * 100
  )

  // How much bandwidth will the pdf incur when downloaded in full?
  Metrics.summary('pdf-bandwidth', sizeWhenDownloadedInFull)

  // How much space do the ranges use?
  // This will accumulate the ranges size over time, skipping already written ranges.
  Metrics.summary(
    'pdf-ranges-disk-size',
    stats['pdf-caching-new-ranges-size'] - stats['pdf-caching-reclaimed-space']
  )
}

module.exports = {
  emitPdfStats
}
<commit_msg>[misc] ContentCacheMetrics: log slow pdf caching performance<commit_after>const logger = require('logger-sharelatex')
const Metrics = require('./Metrics')
const os = require('os')

let CACHED_LOAD = {
  expires: -1,
  load: [0, 0, 0]
}
function getSystemLoad() {
  if (CACHED_LOAD.expires < Date.now()) {
    CACHED_LOAD = {
      expires: Date.now() + 10 * 1000,
      load: os.loadavg()
    }
  }
  return CACHED_LOAD.load
}

const ONE_MB = 1024 * 1024

function emitPdfStats(stats, timings) {
  if (timings['compute-pdf-caching']) {
    emitPdfCachingStats(stats, timings)
  } else {
    // How much bandwidth will the pdf incur when downloaded in full?
    Metrics.summary('pdf-bandwidth', stats['pdf-size'])
  }
}

function emitPdfCachingStats(stats, timings) {
  if (!stats['pdf-size']) return // double check

  // How large is the overhead of hashing up-front?
  const fraction =
    timings.compileE2E - timings['compute-pdf-caching'] !== 0
      ? timings.compileE2E /
        (timings.compileE2E - timings['compute-pdf-caching'])
      : 1
  if (fraction > 1.5) {
    logger.warn(
      {
        stats,
        timings,
        load: getSystemLoad()
      },
      'slow pdf caching'
    )
  }
  Metrics.summary('overhead-compute-pdf-ranges', fraction * 100 - 100)

  // How does the hashing scale to pdf size in MB?
  Metrics.timing(
    'compute-pdf-caching-relative-to-pdf-size',
    timings['compute-pdf-caching'] / (stats['pdf-size'] / ONE_MB)
  )
  if (stats['pdf-caching-total-ranges-size']) {
    // How does the hashing scale to total ranges size in MB?
    Metrics.timing(
      'compute-pdf-caching-relative-to-total-ranges-size',
      timings['compute-pdf-caching'] /
        (stats['pdf-caching-total-ranges-size'] / ONE_MB)
    )
    // How fast is the hashing per range on average?
    Metrics.timing(
      'compute-pdf-caching-relative-to-ranges-count',
      timings['compute-pdf-caching'] / stats['pdf-caching-n-ranges']
    )

    // How many ranges are new?
    Metrics.summary(
      'new-pdf-ranges-relative-to-total-ranges',
      (stats['pdf-caching-n-new-ranges'] / stats['pdf-caching-n-ranges']) * 100
    )
  }

  // How much content is cacheable?
  Metrics.summary(
    'cacheable-ranges-to-pdf-size',
    (stats['pdf-caching-total-ranges-size'] / stats['pdf-size']) * 100
  )

  const sizeWhenDownloadedInFull =
    // All of the pdf
    stats['pdf-size'] -
    // These ranges are potentially cached.
    stats['pdf-caching-total-ranges-size'] +
    // These ranges are not cached.
    stats['pdf-caching-new-ranges-size']

  // How much bandwidth can we save when downloading the pdf in full?
  Metrics.summary(
    'pdf-bandwidth-savings',
    100 - (sizeWhenDownloadedInFull / stats['pdf-size']) * 100
  )

  // How much bandwidth will the pdf incur when downloaded in full?
  Metrics.summary('pdf-bandwidth', sizeWhenDownloadedInFull)

  // How much space do the ranges use?
  // This will accumulate the ranges size over time, skipping already written ranges.
  Metrics.summary(
    'pdf-ranges-disk-size',
    stats['pdf-caching-new-ranges-size'] - stats['pdf-caching-reclaimed-space']
  )
}

module.exports = {
  emitPdfStats
}
<|endoftext|>


<commit_before>import React from 'react';
import { Map, Marker, Popup } from 'react-leaflet';
import { connect } from 'react-redux';

import Navbar from 'react-bootstrap/lib/Navbar';
import Row from 'react-bootstrap/lib/Row';
import Nav from 'react-bootstrap/lib/Nav';
import NavItem from 'react-bootstrap/lib/NavItem';

import { toHtml } from 'fm3/poiTypes';
import Search from 'fm3/components/Search';
import SearchResults from 'fm3/components/SearchResults';
import ObjectsModal from 'fm3/components/ObjectsModal';
import Layers from 'fm3/components/Layers';
import Measurement from 'fm3/components/Measurement';
import ElevationMeasurement from 'fm3/components/ElevationMeasurement';
import RoutePlanner from 'fm3/components/RoutePlanner';
import RoutePlannerResults from 'fm3/components/RoutePlannerResults';
import { setTool, setMapCenter, setMapZoom, setMapType, setMapOverlays } from 'fm3/actions/mapActions';

class Main extends React.Component {

  constructor(props) {
    super(props);

    this.state = {
      poiSearchResults: [],
      selectedSearchResult: null,
      highlightedSearchSuggestion: null
    };
  }

  // TODO parse URL and dispatch changes, otherwise programmatich URL change will not update the map state
  // componentWillReceiveProps(newProps) {
  //   this.setState(toMapState(newProps.params));
  // }

  handleMapMoveend(e) {
    const { lat, lng: lon } = e.target.getCenter();
    this.props.onMapCenterChange({ lat, lon });
  }

  handleMapZoom(e) {
    this.props.onMapZoomChange(e.target.getZoom());
  }

  handleMapTypeChange(mapType) {
    this.props.onMapTypeChange(mapType);
  }

  handleOverlayChange(overlays) {
    this.props.onMapOverlaysChange(overlays);
  }

  showObjectsModal(objectsModalShown) {
    this.setState({ objectsModalShown });
  }

  showObjects(filter) {
    this.setState({ objectsModalShown: false });

    if (!filter || !filter.length) {
      return;
    }

    const b = this.map.leafletElement.getBounds();

    const bbox = `(${b.getSouth()},${b.getWest()},${b.getNorth()},${b.getEast()})`;
    const query = `[out:json][timeout:60]; (${filter.map(f => `${f}${bbox};`).join('')}); out qt;`;

    return fetch('https://overpass-api.de/api/interpreter', {
      method: 'POST',
      body: `data=${encodeURIComponent(query)}`
    }).then(res => res.json()).then(data => {
      this.setState({
        poiSearchResults: data.elements.map((d, id) => ({ id, lat: d.lat, lon: d.lon, tags: d.tags })),
        selectedSearchResult: null
      });
    });
  }

  handleMapClick({ latlng: { lat, lng: lon }}) {
    if (this.measurement) {
      this.measurement.getWrappedInstance().handlePointAdded({ lat, lon });
    }

    if (this.elevationMeasurement) {
      this.elevationMeasurement.getWrappedInstance().handlePointAdded({ lat, lon });
    }

    if (this.routePlanner) {
      this.routePlanner.getWrappedInstance().handlePointAdded({ lat, lon });
    }
  }

  onSearchSuggestionHighlightChange(highlightedSearchSuggestion) {
    this.setState({ highlightedSearchSuggestion });
  }

  onSelectSearchResult(selectedSearchResult) {
    this.setState({ selectedSearchResult, highlightedSearchSuggestion: null, poiSearchResults: [] });
  }

  refocusMap(lat, lon, zoom) {
    this.setState({ lat, lon, zoom });
  }

  render() {
    const { tool, onSetTool, center: { lat, lon }, zoom, mapType, overlays } = this.props;

    const { objectsModalShown, poiSearchResults, selectedSearchResult, highlightedSearchSuggestion } = this.state;

    const b = (fn, ...args) => fn.bind(this, ...args);

    return (

        {objectsModalShown && <ObjectsModal onClose={b(this.showObjects)}/>}

        <Row>
          <Navbar fluid style={{ marginBottom: 0 }}>
            <Navbar.Header>
              <Navbar.Brand>Freemap</Navbar.Brand>
              <Navbar.Toggle/>
            </Navbar.Header>

            <Navbar.Collapse>
              {tool !== 'route-planner' &&

                  <Search
                    onSearchSuggestionHighlightChange={b(this.onSearchSuggestionHighlightChange)}
                    onSelectSearchResult={b(this.onSelectSearchResult)}
                    lat={lat}
                    lon={lon}
                    zoom={zoom}
                  />

                    <NavItem onClick={b(this.showObjectsModal, true)} disabled={zoom < 12}>Objekty</NavItem>
                    <NavItem onClick={b(onSetTool, 'measure')} active={tool === 'measure'}>Meranie</NavItem>
                    <NavItem onClick={b(onSetTool, 'route-planner')} active={tool === 'route-planner'}>Plánovač trasy</NavItem>
                    <NavItem onClick={b(onSetTool, 'measure-ele')} active={tool === 'measure-ele'}>Výškomer</NavItem>

              }
              {tool === 'route-planner' && <RoutePlanner/>}
            </Navbar.Collapse>
          </Navbar>
        </Row>
        <Row>
          <Map
              ref={map => this.map = map}
              className={`tool-${tool || 'none'}`}
              center={L.latLng(lat, lon)}
              zoom={zoom}
              onMoveend={b(this.handleMapMoveend)}
              onZoom={b(this.handleMapZoom)}
              onClick={b(this.handleMapClick)}>

            <Layers
              mapType={mapType} onMapChange={b(this.handleMapTypeChange)}
              overlays={overlays} onOverlaysChange={b(this.handleOverlayChange)}/>

            <SearchResults
              highlightedSearchSuggestion={highlightedSearchSuggestion}
              selectedSearchResult={selectedSearchResult}
              doMapRefocus={b(this.refocusMap)}/>

            {poiSearchResults.map(({ id, lat, lon, tags }) => {
              const __html = toHtml(tags);

              return (
                <Marker key={id} position={L.latLng(lat, lon)}>
                  {__html && <Popup autoPan={false}></Popup>}
                </Marker>
              );
            })}

            {tool === 'route-planner' && <RoutePlannerResults ref={e => this.routePlanner = e}/>}

            {tool === 'measure' && <Measurement ref={e => this.measurement = e}/>}

            {tool === 'measure-ele' && <ElevationMeasurement ref={e => this.elevationMeasurement = e}/>}
          </Map>
        </Row>

    );
  }
}

Main.propTypes = {
  params: React.PropTypes.object,
  tool: React.PropTypes.string,
  onSetTool: React.PropTypes.func.isRequired,
  onMapCenterChange: React.PropTypes.func.isRequired,
  onMapZoomChange: React.PropTypes.func.isRequired,
  onMapTypeChange: React.PropTypes.func.isRequired,
  onMapOverlaysChange: React.PropTypes.func.isRequired,
  center: React.PropTypes.object.isRequired,
  zoom: React.PropTypes.number.isRequired,
  mapType: React.PropTypes.string.isRequired, // TODO enum
  overlays: React.PropTypes.array.isRequired // TODO enums
};

export default connect(function (state) {
  return {
    tool: state.map.tool,
    center: state.map.center,
    zoom: state.map.zoom,
    mapType: state.map.mapType,
    overlays: state.map.overlays
  };
},
function (dispatch) {
  return {
    onSetTool: function(tool) {
      dispatch(setTool(tool));
    },
    onMapCenterChange: function({ lat, lon }) {
      dispatch(setMapCenter({ lat, lon }));
    },
    onMapZoomChange: function(zoom) {
      dispatch(setMapZoom(zoom));
    },
    onMapTypeChange: function(mapType) {
      dispatch(setMapType(mapType));
    },
    onMapOverlaysChange: function(overlays) {
      dispatch(setMapOverlays(overlays));
    }
  };
})(Main);

function toMapState({ zoom, lat, lon, mapType }) {
  return {
    mapType: mapType && mapType.charAt(0) || 'T',
    lat: parseFloat(lat) || 48.70714,
    lon: parseFloat(lon) || 19.4995,
    zoom: parseInt(zoom) || 8,
    overlays: mapType && mapType.substring(1).split('') || []
  };
}
<commit_msg>fixed map refocusing on search<commit_after>import React from 'react';
import { Map, Marker, Popup } from 'react-leaflet';
import { connect } from 'react-redux';

import Navbar from 'react-bootstrap/lib/Navbar';
import Row from 'react-bootstrap/lib/Row';
import Nav from 'react-bootstrap/lib/Nav';
import NavItem from 'react-bootstrap/lib/NavItem';

import { toHtml } from 'fm3/poiTypes';
import Search from 'fm3/components/Search';
import SearchResults from 'fm3/components/SearchResults';
import ObjectsModal from 'fm3/components/ObjectsModal';
import Layers from 'fm3/components/Layers';
import Measurement from 'fm3/components/Measurement';
import ElevationMeasurement from 'fm3/components/ElevationMeasurement';
import RoutePlanner from 'fm3/components/RoutePlanner';
import RoutePlannerResults from 'fm3/components/RoutePlannerResults';
import { setTool, setMapCenter, setMapZoom, setMapType, setMapOverlays } from 'fm3/actions/mapActions';

class Main extends React.Component {

  constructor(props) {
    super(props);

    this.state = {
      poiSearchResults: [],
      selectedSearchResult: null,
      highlightedSearchSuggestion: null
    };
  }

  // TODO parse URL and dispatch changes, otherwise programmatich URL change will not update the map state
  // componentWillReceiveProps(newProps) {
  //   this.setState(toMapState(newProps.params));
  // }

  handleMapMoveend(e) {
    const { lat, lng: lon } = e.target.getCenter();
    this.props.onMapCenterChange({ lat, lon });
  }

  handleMapZoom(e) {
    this.props.onMapZoomChange(e.target.getZoom());
  }

  handleMapTypeChange(mapType) {
    this.props.onMapTypeChange(mapType);
  }

  handleOverlayChange(overlays) {
    this.props.onMapOverlaysChange(overlays);
  }

  showObjectsModal(objectsModalShown) {
    this.setState({ objectsModalShown });
  }

  showObjects(filter) {
    this.setState({ objectsModalShown: false });

    if (!filter || !filter.length) {
      return;
    }

    const b = this.map.leafletElement.getBounds();

    const bbox = `(${b.getSouth()},${b.getWest()},${b.getNorth()},${b.getEast()})`;
    const query = `[out:json][timeout:60]; (${filter.map(f => `${f}${bbox};`).join('')}); out qt;`;

    return fetch('https://overpass-api.de/api/interpreter', {
      method: 'POST',
      body: `data=${encodeURIComponent(query)}`
    }).then(res => res.json()).then(data => {
      this.setState({
        poiSearchResults: data.elements.map((d, id) => ({ id, lat: d.lat, lon: d.lon, tags: d.tags })),
        selectedSearchResult: null
      });
    });
  }

  handleMapClick({ latlng: { lat, lng: lon }}) {
    if (this.measurement) {
      this.measurement.getWrappedInstance().handlePointAdded({ lat, lon });
    }

    if (this.elevationMeasurement) {
      this.elevationMeasurement.getWrappedInstance().handlePointAdded({ lat, lon });
    }

    if (this.routePlanner) {
      this.routePlanner.getWrappedInstance().handlePointAdded({ lat, lon });
    }
  }

  onSearchSuggestionHighlightChange(highlightedSearchSuggestion) {
    this.setState({ highlightedSearchSuggestion });
  }

  onSelectSearchResult(selectedSearchResult) {
    this.setState({ selectedSearchResult, highlightedSearchSuggestion: null, poiSearchResults: [] });
  }

  // TODO move to SearchResults
  refocusMap(lat, lon, zoom) {
    this.props.onMapCenterChange({ lat, lon });
    this.props.onMapZoomChange(zoom);
  }

  render() {
    const { tool, onSetTool, center: { lat, lon }, zoom, mapType, overlays } = this.props;

    const { objectsModalShown, poiSearchResults, selectedSearchResult, highlightedSearchSuggestion } = this.state;

    const b = (fn, ...args) => fn.bind(this, ...args);

    return (

        {objectsModalShown && <ObjectsModal onClose={b(this.showObjects)}/>}

        <Row>
          <Navbar fluid style={{ marginBottom: 0 }}>
            <Navbar.Header>
              <Navbar.Brand>Freemap</Navbar.Brand>
              <Navbar.Toggle/>
            </Navbar.Header>

            <Navbar.Collapse>
              {tool !== 'route-planner' &&

                  <Search
                    onSearchSuggestionHighlightChange={b(this.onSearchSuggestionHighlightChange)}
                    onSelectSearchResult={b(this.onSelectSearchResult)}
                    lat={lat}
                    lon={lon}
                    zoom={zoom}
                  />

                    <NavItem onClick={b(this.showObjectsModal, true)} disabled={zoom < 12}>Objekty</NavItem>
                    <NavItem onClick={b(onSetTool, 'measure')} active={tool === 'measure'}>Meranie</NavItem>
                    <NavItem onClick={b(onSetTool, 'route-planner')} active={tool === 'route-planner'}>Plánovač trasy</NavItem>
                    <NavItem onClick={b(onSetTool, 'measure-ele')} active={tool === 'measure-ele'}>Výškomer</NavItem>

              }
              {tool === 'route-planner' && <RoutePlanner/>}
            </Navbar.Collapse>
          </Navbar>
        </Row>
        <Row>
          <Map
              ref={map => this.map = map}
              className={`tool-${tool || 'none'}`}
              center={L.latLng(lat, lon)}
              zoom={zoom}
              onMoveend={b(this.handleMapMoveend)}
              onZoom={b(this.handleMapZoom)}
              onClick={b(this.handleMapClick)}>

            <Layers
              mapType={mapType} onMapChange={b(this.handleMapTypeChange)}
              overlays={overlays} onOverlaysChange={b(this.handleOverlayChange)}/>

            <SearchResults
              highlightedSearchSuggestion={highlightedSearchSuggestion}
              selectedSearchResult={selectedSearchResult}
              doMapRefocus={b(this.refocusMap)}
              map={this.map}/>

            {poiSearchResults.map(({ id, lat, lon, tags }) => {
              const __html = toHtml(tags);

              return (
                <Marker key={id} position={L.latLng(lat, lon)}>
                  {__html && <Popup autoPan={false}></Popup>}
                </Marker>
              );
            })}

            {tool === 'route-planner' && <RoutePlannerResults ref={e => this.routePlanner = e}/>}

            {tool === 'measure' && <Measurement ref={e => this.measurement = e}/>}

            {tool === 'measure-ele' && <ElevationMeasurement ref={e => this.elevationMeasurement = e}/>}
          </Map>
        </Row>

    );
  }
}

Main.propTypes = {
  params: React.PropTypes.object,
  tool: React.PropTypes.string,
  onSetTool: React.PropTypes.func.isRequired,
  onMapCenterChange: React.PropTypes.func.isRequired,
  onMapZoomChange: React.PropTypes.func.isRequired,
  onMapTypeChange: React.PropTypes.func.isRequired,
  onMapOverlaysChange: React.PropTypes.func.isRequired,
  center: React.PropTypes.object.isRequired,
  zoom: React.PropTypes.number.isRequired,
  mapType: React.PropTypes.string.isRequired, // TODO enum
  overlays: React.PropTypes.array.isRequired // TODO enums
};

export default connect(function (state) {
  return {
    tool: state.map.tool,
    center: state.map.center,
    zoom: state.map.zoom,
    mapType: state.map.mapType,
    overlays: state.map.overlays
  };
},
function (dispatch) {
  return {
    onSetTool: function(tool) {
      dispatch(setTool(tool));
    },
    onMapCenterChange: function({ lat, lon }) {
      dispatch(setMapCenter({ lat, lon }));
    },
    onMapZoomChange: function(zoom) {
      dispatch(setMapZoom(zoom));
    },
    onMapTypeChange: function(mapType) {
      dispatch(setMapType(mapType));
    },
    onMapOverlaysChange: function(overlays) {
      dispatch(setMapOverlays(overlays));
    }
  };
})(Main);

function toMapState({ zoom, lat, lon, mapType }) {
  return {
    mapType: mapType && mapType.charAt(0) || 'T',
    lat: parseFloat(lat) || 48.70714,
    lon: parseFloat(lon) || 19.4995,
    zoom: parseInt(zoom) || 8,
    overlays: mapType && mapType.substring(1).split('') || []
  };
}
<|endoftext|>


<commit_before>var jpgp        = require('../lib/jpgp');
var async       = require('async');
var request     = require('request');
var mongoose    = require('mongoose');
var _           = require('underscore');
var THTEntry    = mongoose.model('THTEntry');
var Amendment   = mongoose.model('Amendment');
var PublicKey   = mongoose.model('PublicKey');
var Membership  = mongoose.model('Membership');
var Merkle      = mongoose.model('Merkle');
var Vote        = mongoose.model('Vote');
var Peer        = mongoose.model('Peer');
var Key         = mongoose.model('Key');
var Forward     = mongoose.model('Forward');
var Status      = require('../models/statusMessage');
var log4js      = require('log4js');
var logger      = log4js.getLogger('peering');

module.exports.get = function (pgp, currency, conf) {

  var ParametersService = require('./ParametersService');

  this.createNext = function (am, done) {
    var amNext = new Amendment();
    var membersMerklePrev = null;
    var votersMerklePrev = null;
    async.waterfall([
      function(next){
        amNext.selfGenerated = true;
        ["version", "currency", "membersRoot", "membersCount", "votersRoot", "votersCount"].forEach(function(property){
          amNext[property] = am[property];
        });
        amNext.number = am.number + 1;
        amNext.previousHash = am.hash;
        amNext.generated = am.generated + conf.sync.votingFrequence;
        amNext.membersChanges = [];
        amNext.membersRoot = am.membersRoot;
        amNext.membersCount = am.membersCount;
        amNext.votersChanges = [];
        amNext.votersRoot = am.votersRoot;
        amNext.votersCount = am.votersCount;
        amNext.monetaryMass = am.monetaryMass
        // Time for Universal Dividend
        if (amNext.generated % conf.sync.UDFrequence == 0) {
          var monetaryMassDelta = am.monetaryMass * conf.sync.UDPercent;
          var dividendPerMember = monetaryMassDelta / am.membersCount;
          amNext.dividend = Math.max(conf.sync.UDMin, Math.floor(dividendPerMember));
          amNext.monetaryMass += am.dividend * am.membersCount;
        }
        amNext.nextVotes = Math.ceil((am.votersCount || 0) * conf.sync.VotesPercent);
        // Computes changes due to too old JOIN/ACTUALIZE
        var exclusionDate = getExclusionDate(amNext);
        Membership.getCurrentJoinOrActuOlderThan(exclusionDate, next);
      },
      function (membershipsToExclude, next){
        var changes = [];
        membershipsToExclude.forEach(function(ms){
          changes.push("-" + ms.issuer);
        });
        changes.sort();
        amNext.membersChanges = changes;
        Merkle.membersWrittenForAmendment(am.number, am.hash, next);
      },
      function (merkle, next){
        membersMerklePrev = merkle;
        Merkle.membersWrittenForProposedAmendment(amNext.number, next);
      },
      function (merkle, next){
        var leaves = membersMerklePrev.leaves();
        // Remove from Merkle members not actualized
        amNext.membersChanges.forEach(function(change){
          var issuer = change.substring(1);
          var index = leaves.indexOf(issuer);
          if (~index) {
            leaves.splice(index, 1);
          }
        });
        merkle.initialize(leaves);
        amNext.membersRoot = merkle.root();
        amNext.membersCount = leaves.length;
        merkle.save(function (err) {
          next(err);
        });
      },
      function (next){
        Merkle.votersWrittenForAmendment(am.number, am.hash, next);
      },
      function (merkle, next){
        votersMerklePrev = merkle;
        Merkle.votersWrittenForProposedAmendment(amNext.number, next);
      },
      function (merkle, next){
        merkle.initialize(votersMerklePrev.leaves());
        merkle.save(function (err) {
          next(err);
        });
      },
      function (next){
        // Finally save proposed amendment
        amNext.save(function (err) {
          next(err);
        });
      },
    ], done);
  };

  this.submit = function (signedEntry, done) {

    async.waterfall([

      function (callback) {
        if(signedEntry.indexOf('-----BEGIN') == -1){
          callback('Signature not found in given THT entry');
          return;
        }
        callback();
      },

      // Check signature's key ID
      function(callback){
        var sig = signedEntry.substring(signedEntry.indexOf('-----BEGIN'));
        var keyID = jpgp().signature(sig).issuer();
        if(!(keyID && keyID.length == 16)){
          callback('Cannot identify signature issuer`s keyID');
          return;
        }
        callback(null, keyID);
      },

      // Looking for corresponding public key
      function(keyID, callback){
        PublicKey.getTheOne(keyID, function (err, pubkey) {
          callback(err, pubkey);
        });
      },

      // Verify signature
      function(pubkey, callback){
        var entry = new Membership();
        var current = null;
        var nowIsIgnored = false;
        async.waterfall([
          function (next){
            entry.parse(signedEntry, next);
          },
          function (entry, next){
            entry.verify(currency, next);
          },
          function (valid, next){
            entry.verifySignature(pubkey.raw, next);
          },
          function (verified, next){
            if(!verified){
              next('Bad signature');
              return;
            }
            if(pubkey.fingerprint != entry.issuer){
              next('Fingerprint in Membership (' + entry.issuer + ') does not match signatory (' + pubkey.fingerprint + ')');
              return;
            }
            next();
          },
          function (next){
            Membership.find({ issuer: pubkey.fingerprint, hash: entry.hash }, next);
          },
          function (entries, next){
            if (entries.length > 0) {
              next('Already received membership');
              return;
            }
            next();
          },
          function (next){
            // var timestampInSeconds = parseInt(entry.sigDate.getTime()/1000, 10);
            // Amendment.findPromotedPreceding(timestampInSeconds, next);
            Amendment.current(next);
          },
          function (am, next){
            var entryTimestamp = parseInt(entry.sigDate.getTime()/1000, 10);
            if (am.generated > entryTimestamp) {
              next('Too late for this membership. Retry.');
              return;
            }
            entry.amNumber = am.number;
            Membership.getCurrent(next);
          },
          function (currentlyRecorded, next){
            current = currentlyRecorded;
            // Case new is JOIN
            if (entry.membership == 'JOIN') {
              if (current && (current.membership == 'JOIN' || current.membership == 'ACTUALIZE')) {
                next('Already joined');
                return;
              }
            }
            else if (entry.membership == 'ACTUALIZE' || entry.membership == 'LEAVE') {
              if (!current || current.membership == 'LEAVE') {
                next('Not a member currently');
                return;
              }
            }
            next();
          },
          function (next){
            // Get already existing Membership for same amendment
            Membership.getForAmendmentAndIssuer(entry.amNumber, entry.issuer, next);
          },
          function (entries, next){
            if (entries.length > 1) {
              next('Refused: already received more than one membership for next amendment.');
              return;
            } else if(entries.length > 0){
              // Already existing membership for this AM : this membership and the previous for this AM
              // are no more to be considered
              entry.eligible = false;
              entries[0].eligible = false;
              entries[0].save(function (err) {
                nowIsIgnored = true;
                next(err);
              });
            } else {
              next();
            }
          },
          function (next){
            // Saves entry
            entry.propagated = false;
            entry.save(function (err) {
              next(err);
            });
          },
          function (next){
            Amendment.getTheOneToBeVoted(entry.amNumber + 1, next);
          },
          function (amNext, next){
            var isJoining = false;
            var isLeaving = false;
            // Impacts on changes
            if (!nowIsIgnored) {
              if (entry.membership == 'JOIN') {
                isJoining = true;
              }
              else if (entry.membership == 'ACTUALIZE') {
                var index = amNext.membersChanges.indexOf('-' + entry.issuer);
                if (~index) {
                  amNext.membersChanges.splice(index, 1);
                }
              } else {
                isLeaving = true;
              }
            } else {
              // Remove what was present for members changes
              var index = amNext.membersChanges.indexOf('-' + entry.issuer);
              if (~index) {
                amNext.membersChanges.splice(index, 1);
              }
              index = amNext.membersChanges.indexOf('+' + entry.issuer);
              if (~index) {
                amNext.membersChanges.splice(index, 1);
              }
              // Computes regarding what is current
              var exclusionDate = getExclusionDate(amNext);
              if (current && current.membership != 'LEAVE' && current.sigDate < exclusionDate) {
                // If too old actualization
                isLeaving = true;
              }
            }
            async.parallel({
              joining: function(callback){
                if (isJoining) {
                  amNext.membersChanges.push('+' + entry.issuer);
                  amNext.membersChanges.sort();
                  async.waterfall([
                    function (next){
                      Merkle.membersWrittenForProposedAmendment(amNext.number, next);
                    },
                    function (merkle, next){
                      merkle.push(entry.issuer);
                      amNext.membersRoot = merkle.root();
                      amNext.membersCount = merkle.leaves().length;
                      merkle.save(function (err) {
                        next(err);
                      });
                    },
                    function (next){
                      amNext.save(function (err) {
                        next(err);
                      });
                    },
                  ], callback);
                } else callback();
              },
              leaving: function(callback){
                if (isLeaving) {
                  amNext.votersChanges.push('-' + entry.issuer);
                  amNext.votersChanges.sort();
                  async.waterfall([
                    function (next){
                      Merkle.votersWrittenForProposedAmendment(amNext.number, next);
                    },
                    function (merkle, next){
                      merkle.remove(entry.issuer);
                      amNext.votersRoot = merkle.root();
                      amNext.votersCount = merkle.leaves().length;
                      merkle.save(function (err) {
                        next(err);
                      });
                    },
                    function (next){
                      amNext.save(function (err) {
                        next(err);
                      });
                    },
                  ], callback);
                } else callback();
              }
            }, function(err) {
              // Impacts on reason
              // Impacts on tree
              //nowIsIgnored && "Already received membership: all received membership for this key will be ignored for next amendment"
              next(err, entry);
            });
          },
        ], callback);
      }
    ], done);
  }

  function getExclusionDate (amNext) {
    var nextTimestamp = amNext.generated;
    var exclusionDate = new Date();
    exclusionDate.setTime(nextTimestamp*1000 - conf.sync.ActualizeFrequence*1000);
    return exclusionDate;
  }

  return this;
}
<commit_msg>Memberships: corrtecly handled JOIN<commit_after>var jpgp        = require('../lib/jpgp');
var async       = require('async');
var request     = require('request');
var mongoose    = require('mongoose');
var _           = require('underscore');
var THTEntry    = mongoose.model('THTEntry');
var Amendment   = mongoose.model('Amendment');
var PublicKey   = mongoose.model('PublicKey');
var Membership  = mongoose.model('Membership');
var Merkle      = mongoose.model('Merkle');
var Vote        = mongoose.model('Vote');
var Peer        = mongoose.model('Peer');
var Key         = mongoose.model('Key');
var Forward     = mongoose.model('Forward');
var Status      = require('../models/statusMessage');
var log4js      = require('log4js');
var logger      = log4js.getLogger('peering');

module.exports.get = function (pgp, currency, conf) {

  var ParametersService = require('./ParametersService');

  this.createNext = function (am, done) {
    var amNext = new Amendment();
    var membersMerklePrev = null;
    var votersMerklePrev = null;
    async.waterfall([
      function(next){
        amNext.selfGenerated = true;
        ["version", "currency", "membersRoot", "membersCount", "votersRoot", "votersCount"].forEach(function(property){
          amNext[property] = am[property];
        });
        amNext.number = am.number + 1;
        amNext.previousHash = am.hash;
        amNext.generated = am.generated + conf.sync.votingFrequence;
        amNext.membersChanges = [];
        amNext.membersRoot = am.membersRoot;
        amNext.membersCount = am.membersCount;
        amNext.votersChanges = [];
        amNext.votersRoot = am.votersRoot;
        amNext.votersCount = am.votersCount;
        amNext.monetaryMass = am.monetaryMass
        // Time for Universal Dividend
        if (amNext.generated % conf.sync.UDFrequence == 0) {
          var monetaryMassDelta = am.monetaryMass * conf.sync.UDPercent;
          var dividendPerMember = monetaryMassDelta / am.membersCount;
          amNext.dividend = Math.max(conf.sync.UDMin, Math.floor(dividendPerMember));
          amNext.monetaryMass += am.dividend * am.membersCount;
        }
        amNext.nextVotes = Math.ceil((am.votersCount || 0) * conf.sync.VotesPercent);
        // Computes changes due to too old JOIN/ACTUALIZE
        var exclusionDate = getExclusionDate(amNext);
        Membership.getCurrentJoinOrActuOlderThan(exclusionDate, next);
      },
      function (membershipsToExclude, next){
        var changes = [];
        membershipsToExclude.forEach(function(ms){
          changes.push("-" + ms.issuer);
        });
        changes.sort();
        amNext.membersChanges = changes;
        Merkle.membersWrittenForAmendment(am.number, am.hash, next);
      },
      function (merkle, next){
        membersMerklePrev = merkle;
        Merkle.membersWrittenForProposedAmendment(amNext.number, next);
      },
      function (merkle, next){
        var leaves = membersMerklePrev.leaves();
        // Remove from Merkle members not actualized
        amNext.membersChanges.forEach(function(change){
          var issuer = change.substring(1);
          var index = leaves.indexOf(issuer);
          if (~index) {
            leaves.splice(index, 1);
          }
        });
        merkle.initialize(leaves);
        amNext.membersRoot = merkle.root();
        amNext.membersCount = leaves.length;
        merkle.save(function (err) {
          next(err);
        });
      },
      function (next){
        Merkle.votersWrittenForAmendment(am.number, am.hash, next);
      },
      function (merkle, next){
        votersMerklePrev = merkle;
        Merkle.votersWrittenForProposedAmendment(amNext.number, next);
      },
      function (merkle, next){
        merkle.initialize(votersMerklePrev.leaves());
        merkle.save(function (err) {
          next(err);
        });
      },
      function (next){
        // Finally save proposed amendment
        amNext.save(function (err) {
          next(err);
        });
      },
    ], done);
  };

  this.submit = function (signedEntry, done) {

    async.waterfall([

      function (callback) {
        if(signedEntry.indexOf('-----BEGIN') == -1){
          callback('Signature not found in given THT entry');
          return;
        }
        callback();
      },

      // Check signature's key ID
      function(callback){
        var sig = signedEntry.substring(signedEntry.indexOf('-----BEGIN'));
        var keyID = jpgp().signature(sig).issuer();
        if(!(keyID && keyID.length == 16)){
          callback('Cannot identify signature issuer`s keyID');
          return;
        }
        callback(null, keyID);
      },

      // Looking for corresponding public key
      function(keyID, callback){
        PublicKey.getTheOne(keyID, function (err, pubkey) {
          callback(err, pubkey);
        });
      },

      // Verify signature
      function(pubkey, callback){
        var entry = new Membership();
        var current = null;
        var nowIsIgnored = false;
        async.waterfall([
          function (next){
            entry.parse(signedEntry, next);
          },
          function (entry, next){
            entry.verify(currency, next);
          },
          function (valid, next){
            entry.verifySignature(pubkey.raw, next);
          },
          function (verified, next){
            if(!verified){
              next('Bad signature');
              return;
            }
            if(pubkey.fingerprint != entry.issuer){
              next('Fingerprint in Membership (' + entry.issuer + ') does not match signatory (' + pubkey.fingerprint + ')');
              return;
            }
            next();
          },
          function (next){
            Membership.find({ issuer: pubkey.fingerprint, hash: entry.hash }, next);
          },
          function (entries, next){
            if (entries.length > 0) {
              next('Already received membership');
              return;
            }
            next();
          },
          function (next){
            // var timestampInSeconds = parseInt(entry.sigDate.getTime()/1000, 10);
            // Amendment.findPromotedPreceding(timestampInSeconds, next);
            Amendment.current(next);
          },
          function (am, next){
            var entryTimestamp = parseInt(entry.sigDate.getTime()/1000, 10);
            if (am.generated > entryTimestamp) {
              next('Too late for this membership. Retry.');
              return;
            }
            entry.amNumber = am.number;
            Membership.getCurrent(next);
          },
          function (currentlyRecorded, next){
            current = currentlyRecorded;
            // Case new is JOIN
            if (entry.membership == 'JOIN') {
              if (current && (current.membership == 'JOIN' || current.membership == 'ACTUALIZE')) {
                next('Already joined');
                return;
              }
            }
            else if (entry.membership == 'ACTUALIZE' || entry.membership == 'LEAVE') {
              if (!current || current.membership == 'LEAVE') {
                next('Not a member currently');
                return;
              }
            }
            next();
          },
          function (next){
            // Get already existing Membership for same amendment
            Membership.getForAmendmentAndIssuer(entry.amNumber, entry.issuer, next);
          },
          function (entries, next){
            if (entries.length > 1) {
              next('Refused: already received more than one membership for next amendment.');
              return;
            } else if(entries.length > 0){
              // Already existing membership for this AM : this membership and the previous for this AM
              // are no more to be considered
              entry.eligible = false;
              entries[0].eligible = false;
              entries[0].save(function (err) {
                nowIsIgnored = true;
                next(err);
              });
            } else {
              next();
            }
          },
          function (next){
            // Saves entry
            entry.propagated = false;
            entry.save(function (err) {
              next(err);
            });
          },
          function (next){
            Amendment.getTheOneToBeVoted(entry.amNumber + 1, next);
          },
          function (amNext, next){
            var isJoining = false;
            var isLeaving = false;
            var isCancelled = false;
            // Impacts on changes
            if (!nowIsIgnored) {
              if (entry.membership == 'JOIN') {
                isJoining = true;
              }
              else if (entry.membership == 'ACTUALIZE') {
                var index = amNext.membersChanges.indexOf('-' + entry.issuer);
                if (~index) {
                  amNext.membersChanges.splice(index, 1);
                }
              } else {
                isLeaving = true;
              }
            } else {
              // Remove what was present for members changes
              var index = amNext.membersChanges.indexOf('-' + entry.issuer);
              if (~index) {
                amNext.membersChanges.splice(index, 1);
              }
              index = amNext.membersChanges.indexOf('+' + entry.issuer);
              if (~index) {
                amNext.membersChanges.splice(index, 1);
                isCancelled = true;
              }
              // Computes regarding what is current
              var exclusionDate = getExclusionDate(amNext);
              if (current && current.membership != 'LEAVE' && current.sigDate < exclusionDate) {
                // If too old actualization
                isLeaving = true;
              }
            }
            async.waterfall([
              function(next){
                if (isCancelled) {
                  async.waterfall([
                    function (next){
                      Merkle.membersWrittenForProposedAmendment(amNext.number, next);
                    },
                    function (merkle, next){
                      merkle.remove(entry.issuer);
                      amNext.membersRoot = merkle.root();
                      amNext.membersCount = merkle.leaves().length;
                      merkle.save(function (err) {
                        next(err);
                      });
                    },
                  ], next);
                }
                else next();
              },
              function (next){
                async.parallel({
                  joining: function(callback){
                    if (isJoining) {
                      amNext.membersChanges.push('+' + entry.issuer);
                      amNext.membersChanges.sort();
                      async.waterfall([
                        function (next){
                          Merkle.membersWrittenForProposedAmendment(amNext.number, next);
                        },
                        function (merkle, next){
                          merkle.push(entry.issuer);
                          amNext.membersRoot = merkle.root();
                          amNext.membersCount = merkle.leaves().length;
                          merkle.save(function (err) {
                            next(err);
                          });
                        },
                      ], callback);
                    } else callback();
                  },
                  leaving: function(callback){
                    if (isLeaving) {
                      amNext.membersChanges.push('-' + entry.issuer);
                      amNext.membersChanges.sort();
                      async.waterfall([
                        function (next){
                          Merkle.membersWrittenForProposedAmendment(amNext.number, next);
                        },
                        function (merkle, next){
                          merkle.remove(entry.issuer);
                          amNext.membersRoot = merkle.root();
                          amNext.membersCount = merkle.leaves().length;
                          merkle.save(function (err) {
                            next(err);
                          });
                        },
                      ], callback);
                    } else callback();
                  }
                }, function(err) {
                  // Impacts on reason
                  // Impacts on tree
                  //nowIsIgnored && "Already received membership: all received membership for this key will be ignored for next amendment"
                  amNext.save(function (err) {
                    next(err, entry);
                  });
                });
              },
            ], next);
          },
        ], callback);
      }
    ], done);
  }

  function getExclusionDate (amNext) {
    var nextTimestamp = amNext.generated;
    var exclusionDate = new Date();
    exclusionDate.setTime(nextTimestamp*1000 - conf.sync.ActualizeFrequence*1000);
    return exclusionDate;
  }

  return this;
}
<|endoftext|>


<commit_before>'use strict'

const Joi = require('@hapi/joi')
const { BaseJsonService } = require('..')
const { renderVersionBadge } = require('../version')

const schema = Joi.object({
  version: Joi.string().required(),
  status: Joi.string()
    .valid('ok')
    .required(),
}).required()

module.exports = class JitPackVersion extends BaseJsonService {
  static get category() {
    return 'version'
  }

  static get route() {
    return {
      base: 'jitpack/v',
      pattern: ':vcs(github|bitbucket|gitlab)/:user/:repo',
    }
  }

  static get examples() {
    return [
      {
        title: 'JitPack',
        namedParams: {
          vcs: 'github',
          user: 'jitpack',
          repo: 'maven-simple',
        },
        staticPreview: renderVersionBadge({ version: 'v1.1' }),
        keywords: ['java', 'maven'],
      },
    ]
  }

  static get defaultBadgeData() {
    return { label: 'jitpack' }
  }

  async fetch({ vcs, user, repo }) {
    const url = `https://jitpack.io/api/builds/com.${vcs}.${user}/${repo}/latest`

    return this._requestJson({
      schema,
      url,
      errorMessages: { 401: 'project not found or private' },
    })
  }

  async handle({ vcs, user, repo }) {
    const { version } = await this.fetch({ vcs, user, repo })
    return renderVersionBadge({ version })
  }
}
<commit_msg>add gitee (#3531)<commit_after>'use strict'

const Joi = require('@hapi/joi')
const { BaseJsonService } = require('..')
const { renderVersionBadge } = require('../version')

const schema = Joi.object({
  version: Joi.string().required(),
  status: Joi.string()
    .valid('ok')
    .required(),
}).required()

module.exports = class JitPackVersion extends BaseJsonService {
  static get category() {
    return 'version'
  }

  static get route() {
    return {
      base: 'jitpack/v',
      pattern: ':vcs(github|bitbucket|gitlab|gitee)/:user/:repo',
    }
  }

  static get examples() {
    return [
      {
        title: 'JitPack',
        namedParams: {
          vcs: 'github',
          user: 'jitpack',
          repo: 'maven-simple',
        },
        staticPreview: renderVersionBadge({ version: 'v1.1' }),
        keywords: ['java', 'maven'],
      },
    ]
  }

  static get defaultBadgeData() {
    return { label: 'jitpack' }
  }

  async fetch({ vcs, user, repo }) {
    const url = `https://jitpack.io/api/builds/com.${vcs}.${user}/${repo}/latest`

    return this._requestJson({
      schema,
      url,
      errorMessages: { 401: 'project not found or private' },
    })
  }

  async handle({ vcs, user, repo }) {
    const { version } = await this.fetch({ vcs, user, repo })
    return renderVersionBadge({ version })
  }
}
<|endoftext|>


<commit_before>angular.module("storageExplorer").factory("appContext", function ($q, $rootScope) {
    var appDeferred = $q.defer();
    var appInfo = appDeferred;

    var port = chrome.runtime.connect();

    port.onMessage.addListener(function (result) {
        if (result.name) {
            appDeferred.resolve(result);
            appInfo = result;
            port.disconnect();
        }
    });

    var inspectedWindow = chrome.devtools.inspectedWindow;
    inspectedWindow.eval('chrome.runtime.id', function (id, isError) {
        if (id && !isError) {
            port.postMessage({appId: id});
        } else {
        }
    });

    return function () {
        var deferred = $q.defer();
        $q.when(appInfo).then(function (result) {
            deferred.resolve(result);
        });
        return deferred.promise;
    }
});<commit_msg>bug - was appinfo were not retrieved correctly if invoked right away<commit_after>angular.module("storageExplorer").factory("appContext", function ($q, $rootScope) {
    var appDeferred = $q.defer();
    var appInfo = appDeferred.promise;

    var port = chrome.runtime.connect();

    port.onMessage.addListener(function (result) {
        if (result.name) {
            appDeferred.resolve(result);
            appDeferred = null;
            appInfo = result;
            port.disconnect();
        }
    });

    var inspectedWindow = chrome.devtools.inspectedWindow;
    inspectedWindow.eval('chrome.runtime.id', function (id, isError) {
        if (id && !isError) {
            port.postMessage({appId: id});
        } else {
        }
    });

    return function () {
        var deferred = $q.defer();
        if(appInfo) {
            $q.when(appInfo).then(function (result) {
                deferred.resolve(result);
            });
        }
        return deferred.promise;
    }
});<|endoftext|>


<commit_before>import React from 'react'
import AbstractForm from './abstract_form.js'

/*
 * A JSX-compatible React DOM Component.
 * Form should be used as the top level component above any other frig
 * components.
 */
export default class Form extends AbstractForm {
  displayName = 'Form'

  componentWillMount() {
    if (!this.props.data) {
      throw new Error(`<Form data={} /> must always be defined, got: ${this.props.data}`)
    }
    if (!this.props.onChange) {
      throw new Error(`<Form onChange={} /> must always be defined, got: ${this.props.onChange}`)
    }
  }

  static propTypes = {
    data: React.PropTypes.object.isRequired,
    onChange: React.PropTypes.func.isRequired,
    errors: React.PropTypes.object.isRequired,
    saved: React.PropTypes.object.isRequired,
    theme: React.PropTypes.object.isRequired,
    typeMapping: React.PropTypes.objectOf(React.PropTypes.string),
    layout: React.PropTypes.string.isRequired,
    align: React.PropTypes.string.isRequired,
    // Callbacks
    onSubmit: React.PropTypes.func,
  }

  static defaultProps = {
    errors: {},
    saved: {},
    theme: undefined,
    typeMapping: {},
    layout: 'vertical',
    align: 'left',
    onSubmit() {},
  }

  static childContextTypes = AbstractForm.childContextTypes

  render() {
    const ThemedForm = this.props.theme.Form
    return (
      <ThemedForm {...this._themedFormProps()}
        ref="form"
      >
        {this.props.children}
      </ThemedForm>
    )
  }

}
<commit_msg>react/jsx-first-prop-new-line: Fix Form<commit_after>import React from 'react'
import AbstractForm from './abstract_form.js'

/*
 * A JSX-compatible React DOM Component.
 * Form should be used as the top level component above any other frig
 * components.
 */
export default class Form extends AbstractForm {
  displayName = 'Form'

  componentWillMount() {
    if (!this.props.data) {
      throw new Error(`<Form data={} /> must always be defined, got: ${this.props.data}`)
    }
    if (!this.props.onChange) {
      throw new Error(`<Form onChange={} /> must always be defined, got: ${this.props.onChange}`)
    }
  }

  static propTypes = {
    data: React.PropTypes.object.isRequired,
    onChange: React.PropTypes.func.isRequired,
    errors: React.PropTypes.object.isRequired,
    saved: React.PropTypes.object.isRequired,
    theme: React.PropTypes.object.isRequired,
    typeMapping: React.PropTypes.objectOf(React.PropTypes.string),
    layout: React.PropTypes.string.isRequired,
    align: React.PropTypes.string.isRequired,
    // Callbacks
    onSubmit: React.PropTypes.func,
  }

  static defaultProps = {
    errors: {},
    saved: {},
    theme: undefined,
    typeMapping: {},
    layout: 'vertical',
    align: 'left',
    onSubmit() {},
  }

  static childContextTypes = AbstractForm.childContextTypes

  render() {
    const ThemedForm = this.props.theme.Form
    return (
      <ThemedForm
        {...this._themedFormProps()}
        ref="form"
      >
        {this.props.children}
      </ThemedForm>
    )
  }

}
<|endoftext|>


<commit_before>/* global BackboneWizard, Marionette, Backbone, $ */

window.BackboneWizard = {
    Models: {},
    Collections: {},
    Views: {},
    Controllers: {},
    Routers: {}
};

var app = new Marionette.Application();

app.addRegions({
    wizard: '#wizard'
});

app.addInitializer(function () {
    BackboneWizard.itemList = new BackboneWizard.Collections.ItemList();
    BackboneWizard.transaction = new BackboneWizard.Models.Transaction();
    BackboneWizard.state = 'index';

    $.get('./appData.json').done(function (data) {
        BackboneWizard.transaction.set(data);
    });

    $.get('./itemData.json').done(function (data) {
        BackboneWizard.itemList.add(data, { merge: true });
    });

    BackboneWizard.wizardController = new BackboneWizard.Controllers.WizardController();

    BackboneWizard.wizardRouter = new BackboneWizard.Routers.WizardRouter({
        controller: BackboneWizard.wizardController
    });

    Backbone.history.start();
});
<commit_msg>Utilize new Marionette modules.<commit_after>/* global BackboneWizard, Marionette, Backbone, $ */

window.BackboneWizard = {};

var app = new Marionette.Application();

app.addRegions({
    wizard: '#wizard'
});

app.addInitializer(function () {
    BackboneWizard.itemList = new app.Collections.ItemList();
    BackboneWizard.transaction = new app.Models.Transaction();
    BackboneWizard.state = 'index';

    $.get('./appData.json').done(function (data) {
        BackboneWizard.transaction.set(data);
    });

    $.get('./itemData.json').done(function (data) {
        BackboneWizard.itemList.add(data, { merge: true });
    });

    BackboneWizard.wizardController = new app.Controllers.WizardController();

    BackboneWizard.wizardRouter = new app.Routers.WizardRouter({
        controller: BackboneWizard.wizardController
    });

    Backbone.history.start();
});
<|endoftext|>


<commit_before>
import Immutable from 'immutable'
import Base64 from '../serializers/base-64'
import Debug from 'debug'
import React from 'react'
import ReactDOM from 'react-dom'
import TYPES from '../constants/types'
import Leaf from './leaf'
import Void from './void'
import scrollTo from '../utils/scroll-to'
import warning from '../utils/warning'
import isDev from '../utils/is-dev'

/**
 * Debug.
 *
 * @type {Function}
 */

const debug = Debug('slate:node')

/**
 * Node.
 *
 * @type {Component}
 */

class Node extends React.Component {

  /**
   * Property types.
   *
   * @type {Object}
   */

  static propTypes = {
    editor: React.PropTypes.object.isRequired,
    node: React.PropTypes.object.isRequired,
    parent: React.PropTypes.object.isRequired,
    schema: React.PropTypes.object.isRequired,
    state: React.PropTypes.object.isRequired
  }

  /**
   * Constructor.
   *
   * @param {Object} props
   */

  constructor(props) {
    super(props)
    const { node, schema } = props
    this.state = {}
    this.state.Component = node.kind == 'text' ? null : node.getComponent(schema)
  }

  /**
   * Debug.
   *
   * @param {String} message
   * @param {Mixed} ...args
   */

  debug = (message, ...args) => {
    const { node } = this.props
    const { key, kind, type } = node
    let id = kind == 'text' ? `${key} (${kind})` : `${key} (${type})`
    debug(message, `${id}`, ...args)
  }

  /**
   * On receiving new props, update the `Component` renderer.
   *
   * @param {Object} props
   */

  componentWillReceiveProps = (props) => {
    if (props.node.kind == 'text') return
    if (props.node == this.props.node) return
    const Component = props.node.getComponent(props.schema)
    this.setState({ Component })
  }

  /**
   * Should the node update?
   *
   * @param {Object} props
   * @param {Object} state
   * @return {Boolean}
   */

  shouldComponentUpdate = (props) => {
    const { Component } = this.state

    // If the node is rendered with a `Component` that has enabled suppression
    // of update checking, always return true so that it can deal with update
    // checking itself.
    if (Component && Component.suppressShouldComponentUpdate) {
      return true
    }

    // If the node has changed, update.
    if (props.node != this.props.node) {
      if (!isDev() || !Immutable.is(props.node, this.props.node)) {
        return true
      } else {
        warning('Encountered different references for identical node values in "shouldComponentUpdate". Check that you are preserving references for the following node:\n', props.node)
      }
    }

    // If the selection is focused and is inside the node, we need to update so
    // that the selection will be set by one of the <Leaf> components.
    if (
      props.state.isFocused &&
      props.state.selection.hasEdgeIn(props.node)
    ) {
      return true
    }

    // If the selection is blurred but was previously focused inside the node,
    // we need to update to ensure the selection gets updated by re-rendering.
    if (
      props.state.isBlurred &&
      this.props.state.isFocused &&
      this.props.state.selection.hasEdgeIn(props.node)
    ) {
      return true
    }

    // For block and inline nodes, which can have custom renderers, we need to
    // include another check for whether the previous selection had an edge in
    // the node, to allow for intuitive selection-based rendering.
    if (
      this.props.node.kind != 'text' &&
      (
          props.state.isFocused != this.props.state.isFocused ||
          this.props.state.selection.hasEdgeIn(this.props.node) != props.state.selection.hasEdgeIn(props.node)
      )
    ) {
      return true
    }

    // For text nodes, which can have custom decorations, we need to check to
    // see if the block has changed, which has caused the decorations to change.
    if (props.node.kind == 'text') {
      const { node, schema, state } = props
      const { document } = state
      const decorators = document.getDescendantDecorators(node.key, schema)
      const ranges = node.getRanges(decorators)

      const prevNode = this.props.node
      const prevSchema = this.props.schema
      const prevDocument = this.props.state.document
      const prevDecorators = prevDocument.getDescendantDecorators(prevNode.key, prevSchema)
      const prevRanges = prevNode.getRanges(prevDecorators)

      if (!ranges.equals(prevRanges)) return true
    }

    // Otherwise, don't update.
    return false
  }

  /**
   * On mount, update the scroll position.
   */

  componentDidMount = () => {
    this.updateScroll()
  }

  /**
   * After update, update the scroll position if the node's content changed.
   *
   * @param {Object} prevProps
   * @param {Object} prevState
   */

  componentDidUpdate = (prevProps, prevState) => {
    if (this.props.node != prevProps.node) this.updateScroll()
  }

  /**
   * Update the scroll position after a change as occured if this is a leaf
   * block and it has the selection's ending edge. This ensures that scrolling
   * matches native `contenteditable` behavior even for cases where the edit is
   * not applied natively, like when enter is pressed.
   */

  updateScroll = () => {
    const { node, state } = this.props
    const { selection } = state

    // If this isn't a block, or it's a wrapping block, abort.
    if (node.kind != 'block') return
    if (node.nodes.first().kind == 'block') return

    // If the selection is blurred, or this block doesn't contain it, abort.
    if (selection.isBlurred) return
    if (!selection.hasEndIn(node)) return

    const el = ReactDOM.findDOMNode(this)
    scrollTo(el)

    this.debug('updateScroll', el)
  }

  /**
   * On drag start, add a serialized representation of the node to the data.
   *
   * @param {Event} e
   */

  onDragStart = (e) => {
    const { node } = this.props
    const encoded = Base64.serializeNode(node)
    const data = e.nativeEvent.dataTransfer
    data.setData(TYPES.NODE, encoded)

    this.debug('onDragStart', e)
  }

  /**
   * Render.
   *
   * @return {Element} element
   */

  render = () => {
    this.debug('render')

    const { node } = this.props
    return node.kind == 'text'
      ? this.renderText()
      : this.renderElement()
  }

  /**
   * Render a `child` node.
   *
   * @param {Node} child
   * @return {Element} element
   */

  renderNode = (child) => {
    return (
      <Node
        key={child.key}
        node={child}
        parent={this.props.node}
        editor={this.props.editor}
        schema={this.props.schema}
        state={this.props.state}
      />
    )
  }

  /**
   * Render an element `node`.
   *
   * @return {Element} element
   */

  renderElement = () => {
    const { editor, node, parent, state } = this.props
    const { Component } = this.state
    const children = node.nodes
      .map(child => this.renderNode(child))
      .toArray()

    // Attributes that the developer must to mix into the element in their
    // custom node renderer component.
    const attributes = {
      'data-key': node.key,
      'onDragStart': this.onDragStart
    }

    // If it's a block node with inline children, add the proper `dir` attribute
    // for text direction.
    if (node.kind == 'block' && node.nodes.first().kind != 'block') {
      const direction = node.getTextDirection()
      if (direction == 'rtl') attributes.dir = 'rtl'
    }

    const element = (
      <Component
        attributes={attributes}
        key={node.key}
        editor={editor}
        parent={parent}
        node={node}
        state={state}
      >
        {children}
      </Component>
    )

    return node.isVoid
      ? <Void {...this.props}>{element}</Void>
      : element
  }

  /**
   * Render a text node.
   *
   * @return {Element} element
   */

  renderText = () => {
    const { node, schema, state } = this.props
    const { document } = state
    const decorators = document.getDescendantDecorators(node.key, schema)
    const ranges = node.getRanges(decorators)
    let offset = 0

    const leaves = ranges.map((range, i) => {
      const leaf = this.renderLeaf(ranges, range, i, offset)
      offset += range.text.length
      return leaf
    })

    return (

        {leaves}

    )
  }

  /**
   * Render a single leaf node given a `range` and `offset`.
   *
   * @param {List} ranges
   * @param {Range} range
   * @param {Number} index
   * @param {Number} offset
   * @return {Element} leaf
   */

  renderLeaf = (ranges, range, index, offset) => {
    const { node, parent, schema, state } = this.props
    const text = range.text
    const marks = range.marks

    return (
      <Leaf
        key={`${node.key}-${index}`}
        index={index}
        marks={marks}
        node={node}
        parent={parent}
        ranges={ranges}
        schema={schema}
        state={state}
        text={text}
      />
    )
  }

}

/**
 * Export.
 *
 * @type {Component}
 */

export default Node
<commit_msg>Fix error :bomb: in shouldComponentUpdate<commit_after>
import Immutable from 'immutable'
import Base64 from '../serializers/base-64'
import Debug from 'debug'
import React from 'react'
import ReactDOM from 'react-dom'
import TYPES from '../constants/types'
import Leaf from './leaf'
import Void from './void'
import scrollTo from '../utils/scroll-to'
import warning from '../utils/warning'
import isDev from '../utils/is-dev'

/**
 * Debug.
 *
 * @type {Function}
 */

const debug = Debug('slate:node')

/**
 * Node.
 *
 * @type {Component}
 */

class Node extends React.Component {

  /**
   * Property types.
   *
   * @type {Object}
   */

  static propTypes = {
    editor: React.PropTypes.object.isRequired,
    node: React.PropTypes.object.isRequired,
    parent: React.PropTypes.object.isRequired,
    schema: React.PropTypes.object.isRequired,
    state: React.PropTypes.object.isRequired
  }

  /**
   * Constructor.
   *
   * @param {Object} props
   */

  constructor(props) {
    super(props)
    const { node, schema } = props
    this.state = {}
    this.state.Component = node.kind == 'text' ? null : node.getComponent(schema)
  }

  /**
   * Debug.
   *
   * @param {String} message
   * @param {Mixed} ...args
   */

  debug = (message, ...args) => {
    const { node } = this.props
    const { key, kind, type } = node
    let id = kind == 'text' ? `${key} (${kind})` : `${key} (${type})`
    debug(message, `${id}`, ...args)
  }

  /**
   * On receiving new props, update the `Component` renderer.
   *
   * @param {Object} props
   */

  componentWillReceiveProps = (props) => {
    if (props.node.kind == 'text') return
    if (props.node == this.props.node) return
    const Component = props.node.getComponent(props.schema)
    this.setState({ Component })
  }

  /**
   * Should the node update?
   *
   * @param {Object} nextProps
   * @param {Object} state
   * @return {Boolean}
   */

  shouldComponentUpdate = (nextProps) => {
    const { Component } = this.state

    // If the node is rendered with a `Component` that has enabled suppression
    // of update checking, always return true so that it can deal with update
    // checking itself.
    if (Component && Component.suppressShouldComponentUpdate) {
      return true
    }

    // If the node has changed, update.
    if (nextProps.node != this.props.node) {
      if (!isDev() || !Immutable.is(nextProps.node, this.props.node)) {
        return true
      } else {
        warning('Encountered different references for identical node values in "shouldComponentUpdate". Check that you are preserving references for the following node:\n', nextProps.node)
      }
    }

    const nextHasEdgeIn = nextProps.state.selection.hasEdgeIn(nextProps.node)

    // If the selection is focused and is inside the node, we need to update so
    // that the selection will be set by one of the <Leaf> components.
    if (
      nextProps.state.isFocused &&
      nextHasEdgeIn
    ) {
      return true
    }

    const hasEdgeIn = this.props.state.selection.hasEdgeIn(nextProps.node)
    // If the selection is blurred but was previously focused (or vice versa) inside the node,
    // we need to update to ensure the selection gets updated by re-rendering.
    if (
      this.props.state.isFocused != nextProps.state.isFocused &&
      (
          hasEdgeIn || nextHasEdgeIn
      )
    ) {
      return true
    }

    // For block and inline nodes, which can have custom renderers, we need to
    // include another check for whether the previous selection had an edge in
    // the node, to allow for intuitive selection-based rendering.
    if (
      this.props.node.kind != 'text' &&
      hasEdgeIn != nextHasEdgeIn
    ) {
      return true
    }

    // For text nodes, which can have custom decorations, we need to check to
    // see if the block has changed, which has caused the decorations to change.
    if (nextProps.node.kind == 'text') {
      const { node, schema, state } = nextProps
      const { document } = state
      const decorators = document.getDescendantDecorators(node.key, schema)
      const ranges = node.getRanges(decorators)

      const prevNode = this.props.node
      const prevSchema = this.props.schema
      const prevDocument = this.props.state.document
      const prevDecorators = prevDocument.getDescendantDecorators(prevNode.key, prevSchema)
      const prevRanges = prevNode.getRanges(prevDecorators)

      if (!ranges.equals(prevRanges)) return true
    }

    // Otherwise, don't update.
    return false
  }

  /**
   * On mount, update the scroll position.
   */

  componentDidMount = () => {
    this.updateScroll()
  }

  /**
   * After update, update the scroll position if the node's content changed.
   *
   * @param {Object} prevProps
   * @param {Object} prevState
   */

  componentDidUpdate = (prevProps, prevState) => {
    if (this.props.node != prevProps.node) this.updateScroll()
  }

  /**
   * Update the scroll position after a change as occured if this is a leaf
   * block and it has the selection's ending edge. This ensures that scrolling
   * matches native `contenteditable` behavior even for cases where the edit is
   * not applied natively, like when enter is pressed.
   */

  updateScroll = () => {
    const { node, state } = this.props
    const { selection } = state

    // If this isn't a block, or it's a wrapping block, abort.
    if (node.kind != 'block') return
    if (node.nodes.first().kind == 'block') return

    // If the selection is blurred, or this block doesn't contain it, abort.
    if (selection.isBlurred) return
    if (!selection.hasEndIn(node)) return

    const el = ReactDOM.findDOMNode(this)
    scrollTo(el)

    this.debug('updateScroll', el)
  }

  /**
   * On drag start, add a serialized representation of the node to the data.
   *
   * @param {Event} e
   */

  onDragStart = (e) => {
    const { node } = this.props
    const encoded = Base64.serializeNode(node)
    const data = e.nativeEvent.dataTransfer
    data.setData(TYPES.NODE, encoded)

    this.debug('onDragStart', e)
  }

  /**
   * Render.
   *
   * @return {Element} element
   */

  render = () => {
    this.debug('render')

    const { node } = this.props
    return node.kind == 'text'
      ? this.renderText()
      : this.renderElement()
  }

  /**
   * Render a `child` node.
   *
   * @param {Node} child
   * @return {Element} element
   */

  renderNode = (child) => {
    return (
      <Node
        key={child.key}
        node={child}
        parent={this.props.node}
        editor={this.props.editor}
        schema={this.props.schema}
        state={this.props.state}
      />
    )
  }

  /**
   * Render an element `node`.
   *
   * @return {Element} element
   */

  renderElement = () => {
    const { editor, node, parent, state } = this.props
    const { Component } = this.state
    const children = node.nodes
      .map(child => this.renderNode(child))
      .toArray()

    // Attributes that the developer must to mix into the element in their
    // custom node renderer component.
    const attributes = {
      'data-key': node.key,
      'onDragStart': this.onDragStart
    }

    // If it's a block node with inline children, add the proper `dir` attribute
    // for text direction.
    if (node.kind == 'block' && node.nodes.first().kind != 'block') {
      const direction = node.getTextDirection()
      if (direction == 'rtl') attributes.dir = 'rtl'
    }

    const element = (
      <Component
        attributes={attributes}
        key={node.key}
        editor={editor}
        parent={parent}
        node={node}
        state={state}
      >
        {children}
      </Component>
    )

    return node.isVoid
      ? <Void {...this.props}>{element}</Void>
      : element
  }

  /**
   * Render a text node.
   *
   * @return {Element} element
   */

  renderText = () => {
    const { node, schema, state } = this.props
    const { document } = state
    const decorators = document.getDescendantDecorators(node.key, schema)
    const ranges = node.getRanges(decorators)
    let offset = 0

    const leaves = ranges.map((range, i) => {
      const leaf = this.renderLeaf(ranges, range, i, offset)
      offset += range.text.length
      return leaf
    })

    return (

        {leaves}

    )
  }

  /**
   * Render a single leaf node given a `range` and `offset`.
   *
   * @param {List} ranges
   * @param {Range} range
   * @param {Number} index
   * @param {Number} offset
   * @return {Element} leaf
   */

  renderLeaf = (ranges, range, index, offset) => {
    const { node, parent, schema, state } = this.props
    const text = range.text
    const marks = range.marks

    return (
      <Leaf
        key={`${node.key}-${index}`}
        index={index}
        marks={marks}
        node={node}
        parent={parent}
        ranges={ranges}
        schema={schema}
        state={state}
        text={text}
      />
    )
  }

}

/**
 * Export.
 *
 * @type {Component}
 */

export default Node
<|endoftext|>


<commit_before>black_book.controllers = black_book.controllers || angular.module('BlackBook.controllers', []);

black_book.controllers.controller(
    'selected_contact', function($scope, contacts_service) {

        $scope.selected = null;

        $scope.edit = function (href) {

        }

        $scope.delete = function (href) {
            contacts_service.delete(href).then(
                function () {
                    $scope.selected = null;
                }
            );
        }

        $scope.$on('select', function (event, href) {
            contacts_service.read(href).success(
                function (data) {
                    var collection = new Collection(data);
                    $scope.selected = collection.items[0];
                }
            );
        });
    }
);

black_book.controllers.controller(
    'contact_index', function($scope, contacts_service) {

        var navigation = {
            links: null,
            per_page: 5
        }

        $scope.index = null;
        $scope.navigation = navigation;

        $scope.get_contacts = function (href) {
            contacts_service.read(href).success(
                function (data) {
                    var collection = new Collection(data);
                    $scope.index = collection.items;
                    $scope.navigation.links = collection.links;
                }
            );
        }

        $scope.get_detail = function (href) {
            $scope.$broadcast('select', href);
        }

        $scope.refresh_index = function (href) {
            var uri;
            if ($scope.navigation.per_page !== 5) {
                uri = '/api/entry/?per_page=' + $scope.navigation.per_page;
            } else {
                uri = '/api/entry/';
            }
            $scope.get_contacts(uri);
        }

        $scope.refresh_index();
    }
);

// TODO: rewrite this using promises and expect()?
black_book.controllers.controller(
    'tests_controller', function ($scope, contacts_service) {

        var status = {
            FAIL: { cssclass: 'label-danger', text: 'Failed!' },
            RUNNING: { cssclass: 'label-warning', text: 'Running...' },
            PASS: { cssclass: 'label-success', text: 'Passed!' }
        }

        $scope.tests = []
        $scope.current_test = null;

        $scope.test_basic = function () {

            $scope.current_test = {
                title: 'Basic GET',
                result: {text: 'Testing basic HTTP GET to /api/', status: status.RUNNING}
            }

            contacts_service.read('/api/').then(
                function (response) {
                    if (
                        response.status === 200 &&
                        response.config.headers['Accept'] === 'application/vnd.collection+json' &&
                        response.data.hasOwnProperty('collection') &&
                        response.data.collection.hasOwnProperty('href') &&
                        response.data.collection.hasOwnProperty('version')
                        ) {

                        $scope.current_test.result.status = status.PASS;
                        $scope.current_test.result.text = 'HTTP GET /api/ successfully returned a Collection+JSON document'
                        $scope.tests.push($scope.current_test);
                        $scope.current_test = null;
                    }
                }
            )
        }

        $scope.test_basic();
    }
);<commit_msg>+ added semicolons where necessary + added ability to supply href to contact_index.refresh_index<commit_after>black_book.controllers = black_book.controllers || angular.module('BlackBook.controllers', []);

black_book.controllers.controller(
    'selected_contact', function($scope, contacts_service) {

        $scope.selected = null;

        $scope.edit = function (href) {

        };

        $scope.delete = function (href) {
            contacts_service.delete(href).then(
                function () {
                    $scope.selected = null;
                }
            );
        };

        $scope.$on('select', function (event, href) {
            contacts_service.read(href).success(
                function (data) {
                    var collection = new Collection(data);
                    $scope.selected = collection.items[0];
                }
            );
        });
    }
);

black_book.controllers.controller(
    'contact_index', function($scope, contacts_service) {

        var navigation = {
            links: null,
            per_page: 5
        };

        $scope.index = null;
        $scope.navigation = navigation;

        $scope.get_contacts = function (href) {
            contacts_service.read(href).success(
                function (data) {
                    var collection = new Collection(data);
                    $scope.index = collection.items;
                    $scope.navigation.links = collection.links;
                }
            );
        };

        $scope.get_detail = function (href) {
            $scope.$broadcast('select', href);
        };

        $scope.refresh_index = function (href) {
            var uri;
            if (href) {
                uri = href;
            } else if ($scope.navigation.per_page !== 5) {
                uri = '/api/entry/?per_page=' + $scope.navigation.per_page;
            } else {
                uri = '/api/entry/';
            }
            $scope.get_contacts(uri);
        };

        $scope.refresh_index();
    }
);

// TODO: rewrite this using promises and expect()?
black_book.controllers.controller(
    'tests_controller', function ($scope, contacts_service) {

        var status = {
            FAIL: { cssclass: 'label-danger', text: 'Failed!' },
            RUNNING: { cssclass: 'label-warning', text: 'Running...' },
            PASS: { cssclass: 'label-success', text: 'Passed!' }
        };

        $scope.tests = [];
        $scope.current_test = null;

        $scope.test_basic = function () {

            $scope.current_test = {
                title: 'Basic GET',
                result: {text: 'Testing basic HTTP GET to /api/', status: status.RUNNING}
            };

            contacts_service.read('/api/').then(
                function (response) {
                    if (
                        response.status === 200 &&
                        response.config.headers['Accept'] === 'application/vnd.collection+json' &&
                        response.data.hasOwnProperty('collection') &&
                        response.data.collection.hasOwnProperty('href') &&
                        response.data.collection.hasOwnProperty('version')
                        ) {

                        $scope.current_test.result.status = status.PASS;
                        $scope.current_test.result.text = 'HTTP GET /api/ successfully returned a Collection+JSON document';
                        $scope.tests.push($scope.current_test);
                        $scope.current_test = null;
                    }
                }
            )
        };

        $scope.test_basic();
    }
);<|endoftext|>