<commit_before>import React from 'react';
import { Map, TileLayer, Marker, LayersControl } from 'react-leaflet';
import FileSaver from 'file-saver';
import Navbar from 'react-bootstrap/lib/Navbar';
import Nav from 'react-bootstrap/lib/Nav';
import NavItem from 'react-bootstrap/lib/NavItem';
import FormGroup from 'react-bootstrap/lib/FormGroup';
import ControlLabel from 'react-bootstrap/lib/ControlLabel';
import FormControl from 'react-bootstrap/lib/FormControl';
import NavDropdown from 'react-bootstrap/lib/NavDropdown';
import MenuItem from 'react-bootstrap/lib/MenuItem';
import Checkbox from 'react-bootstrap/lib/Checkbox';
import Glyphicon from 'react-bootstrap/lib/Glyphicon';
import Panel from 'react-bootstrap/lib/Panel';
import ButtonGroup from 'react-bootstrap/lib/ButtonGroup';
import Button from 'react-bootstrap/lib/Button';

import Toposcope from './toposcope.jsx';
import Help from './help.jsx';
import Settings from './settings.jsx';
import Hourglass from './hourglass.jsx';
import createMarker from './markers.js';
import loadPois from './poiLoader.js';
import { languages, getBrowserLanguage, readMessages } from './i18n.js';
import mapDefinitions from './mapDefinitions';

const poiIcon = createMarker('#ddf');
const observerIcon = createMarker('#f88');
const activeObserverIcon = createMarker('#f00');
const activePoiIcon = createMarker('#66f');

const localStorageName = 'toposcope1';

const cleanState = {
  pois: [],
  activePoiId: null,
  inscriptions: [ '', '{a}', '', '' ]
};

export default class Main extends React.Component {

  constructor(props) {
    super(props);

    let toposcope;
    try {
      toposcope = JSON.parse(localStorage.getItem(localStorageName));
    } catch (e) {
      toposcope = null;
    }
    if (!toposcope || typeof toposcope !== 'object') {
      toposcope = {};
    }
    const language = getBrowserLanguage(toposcope && toposcope.language);
    delete toposcope.language;

    this.state = Object.assign({}, cleanState, {
      map: 'OpenStreetMap Mapnik',
      center: L.latLng(0, 0),
      zoom: 1,
      mode: '',
      fetching: false,
      language,
      messages: readMessages(language),
      showHelp: false,
      showSettings: false,
      loadPoiMaxDistance: 1000,
      onlyNearest: true,
      innerCircleRadius: 25,
      addLineBreaks: false,
      fontSize: 3.5
    }, toposcope || {});

    this.nextId = this.state.pois.reduce((a, { id }) => Math.min(a, id), 0) - 1;
  }

  componentDidUpdate() {
    const toSave = {};
    [ 'pois', 'activePoiId', 'inscriptions', 'map', 'center', 'zoom', 'mode', 'language', 'loadPoiMaxDistance', 'onlyNearest',
      'preventUpturnedText', 'addLineBreaks', 'innerCircleRadius', 'fontSize' ]
      .forEach(prop => toSave[prop] = this.state[prop]);
    localStorage.setItem(localStorageName, JSON.stringify(toSave));
  }

  handleMapMove(e) {
    this.setState({ center: e.target.getCenter() });
  }

  handleMapZoom(e) {
    this.setState({ zoom: e.target.getZoom() });
  }

  handlePoiClick(poiId) {
    this.setState(this.state.mode === 'deletePoi' ?
      { activePoiId: null, pois: [ ...this.state.pois.filter(({ id }) => id !== poiId) ] }
      : { activePoiId: poiId });
  }

  handlePoiClick2(poiId) {
    this.handlePoiClick(poiId);
    const poi = this.state.pois.find(({ id }) => id === poiId);
    this.refs.map.leafletElement.panTo([ poi.lat, poi.lng ], { animate: false }); // TODO animation doesn't work for some reason
  }

  handleMapClick(e) {
    if (this.state.mode === 'setObserver') {
      const observer = this.state.pois.find(({ observer }) => observer);
      this.setState({
        activePoiId: observer ? observer.id : this.nextId,
        pois: [
          ...this.state.pois.filter(poi => poi !== observer),
          observer ? Object.assign({}, observer, { lat: e.latlng.lat, lng: e.latlng.lng })
            : { lat: e.latlng.lat, lng: e.latlng.lng, text: '{lat}\n{lon}', id: this.nextId, observer: true, flipText: false }
        ]
      });
      this.nextId--;
    } else if (this.state.mode === 'addPoi') {
      this.setState({
        activePoiId: this.nextId,
        pois: [ ...this.state.pois, { lat: e.latlng.lat, lng: e.latlng.lng, text: '{d} km', id: this.nextId, observer: false, flipText: false } ]
      });
      this.nextId--;
    } else if (this.state.mode === 'loadPois') {
      const { loadPoiMaxDistance, language, addLineBreaks, onlyNearest } = this.state;
      const { lat, lng } = e.latlng;
      const radius = loadPoiMaxDistance > 0 && loadPoiMaxDistance <= 20000 ? loadPoiMaxDistance : 1000;
      this.setState({ fetching: true });
      loadPois(lat, lng, radius, language, addLineBreaks, onlyNearest).then(pois => {
        this.setState({ activePoiId: null,
          pois: [ ...this.state.pois.filter(({ id1 }) => pois.find(({ id2 }) => id1 !== id2) !== -1), ...pois ] });
      }).catch().then(() => this.setState({ fetching: false }));
    } else {
      this.setState({ activePoiId: null, });
    }
  }

  handleTextChange(e) {
    const activePoi = this.state.pois.find(({ id }) => id === this.state.activePoiId);<commit_msg>fixed duplicate POI loading<commit_after>import React from 'react';
import { Map, TileLayer, Marker, LayersControl } from 'react-leaflet';
import FileSaver from 'file-saver';
import Navbar from 'react-bootstrap/lib/Navbar';
import Nav from 'react-bootstrap/lib/Nav';
import NavItem from 'react-bootstrap/lib/NavItem';
import FormGroup from 'react-bootstrap/lib/FormGroup';
import ControlLabel from 'react-bootstrap/lib/ControlLabel';
import FormControl from 'react-bootstrap/lib/FormControl';
import NavDropdown from 'react-bootstrap/lib/NavDropdown';
import MenuItem from 'react-bootstrap/lib/MenuItem';
import Checkbox from 'react-bootstrap/lib/Checkbox';
import Glyphicon from 'react-bootstrap/lib/Glyphicon';
import Panel from 'react-bootstrap/lib/Panel';
import ButtonGroup from 'react-bootstrap/lib/ButtonGroup';
import Button from 'react-bootstrap/lib/Button';

import Toposcope from './toposcope.jsx';
import Help from './help.jsx';
import Settings from './settings.jsx';
import Hourglass from './hourglass.jsx';
import createMarker from './markers.js';
import loadPois from './poiLoader.js';
import { languages, getBrowserLanguage, readMessages } from './i18n.js';
import mapDefinitions from './mapDefinitions';

const poiIcon = createMarker('#ddf');
const observerIcon = createMarker('#f88');
const activeObserverIcon = createMarker('#f00');
const activePoiIcon = createMarker('#66f');

const localStorageName = 'toposcope1';

const cleanState = {
  pois: [],
  activePoiId: null,
  inscriptions: [ '', '{a}', '', '' ]
};

export default class Main extends React.Component {

  constructor(props) {
    super(props);

    let toposcope;
    try {
      toposcope = JSON.parse(localStorage.getItem(localStorageName));
    } catch (e) {
      toposcope = null;
    }
    if (!toposcope || typeof toposcope !== 'object') {
      toposcope = {};
    }
    const language = getBrowserLanguage(toposcope && toposcope.language);
    delete toposcope.language;

    this.state = Object.assign({}, cleanState, {
      map: 'OpenStreetMap Mapnik',
      center: L.latLng(0, 0),
      zoom: 1,
      mode: '',
      fetching: false,
      language,
      messages: readMessages(language),
      showHelp: false,
      showSettings: false,
      loadPoiMaxDistance: 1000,
      onlyNearest: true,
      innerCircleRadius: 25,
      addLineBreaks: false,
      fontSize: 3.5
    }, toposcope || {});

    this.nextId = this.state.pois.reduce((a, { id }) => Math.min(a, id), 0) - 1;
  }

  componentDidUpdate() {
    const toSave = {};
    [ 'pois', 'activePoiId', 'inscriptions', 'map', 'center', 'zoom', 'mode', 'language', 'loadPoiMaxDistance', 'onlyNearest',
      'preventUpturnedText', 'addLineBreaks', 'innerCircleRadius', 'fontSize' ]
      .forEach(prop => toSave[prop] = this.state[prop]);
    localStorage.setItem(localStorageName, JSON.stringify(toSave));
  }

  handleMapMove(e) {
    this.setState({ center: e.target.getCenter() });
  }

  handleMapZoom(e) {
    this.setState({ zoom: e.target.getZoom() });
  }

  handlePoiClick(poiId) {
    this.setState(this.state.mode === 'deletePoi' ?
      { activePoiId: null, pois: [ ...this.state.pois.filter(({ id }) => id !== poiId) ] }
      : { activePoiId: poiId });
  }

  handlePoiClick2(poiId) {
    this.handlePoiClick(poiId);
    const poi = this.state.pois.find(({ id }) => id === poiId);
    this.refs.map.leafletElement.panTo([ poi.lat, poi.lng ], { animate: false }); // TODO animation doesn't work for some reason
  }

  handleMapClick(e) {
    if (this.state.mode === 'setObserver') {
      const observer = this.state.pois.find(({ observer }) => observer);
      this.setState({
        activePoiId: observer ? observer.id : this.nextId,
        pois: [
          ...this.state.pois.filter(poi => poi !== observer),
          observer ? Object.assign({}, observer, { lat: e.latlng.lat, lng: e.latlng.lng })
            : { lat: e.latlng.lat, lng: e.latlng.lng, text: '{lat}\n{lon}', id: this.nextId, observer: true, flipText: false }
        ]
      });
      this.nextId--;
    } else if (this.state.mode === 'addPoi') {
      this.setState({
        activePoiId: this.nextId,
        pois: [ ...this.state.pois, { lat: e.latlng.lat, lng: e.latlng.lng, text: '{d} km', id: this.nextId, observer: false, flipText: false } ]
      });
      this.nextId--;
    } else if (this.state.mode === 'loadPois') {
      const { loadPoiMaxDistance, language, addLineBreaks, onlyNearest } = this.state;
      const { lat, lng } = e.latlng;
      const radius = loadPoiMaxDistance > 0 && loadPoiMaxDistance <= 20000 ? loadPoiMaxDistance : 1000;
      this.setState({ fetching: true });
      loadPois(lat, lng, radius, language, addLineBreaks, onlyNearest).then(pois => {
        this.setState({ activePoiId: null,
          pois: [ ...this.state.pois.filter(p1 => pois.length === 0 || pois.some(p2 => p1.id !== p2.id)), ...pois ] });
      }).catch().then(() => this.setState({ fetching: false }));
    } else {
      this.setState({ activePoiId: null, });
    }
  }

  handleTextChange(e) {
    const activePoi = this.state.pois.find(({ id }) => id === this.state.activePoiId);
{'fraction_non_alphanumeric': 0.16091281451141018, 'fraction_numerical': 0.007411741759313439, 'mean_word_length': 3.4012875536480687, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 27, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '290583', 'n_tokens_mistral': 3425, 'n_tokens_neox': 3373, 'n_words': 745}