MapPage.tsx 29.3 KB
Newer Older
1
import * as React from 'react';
2
import { connect } from 'react-redux';
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
3
import { Link } from 'react-router-dom';
4 5
import { translate } from 'react-i18next';
import { withStyles } from '@material-ui/core/styles';
6
import { isMobile } from 'is-mobile';
7
import { bindActionCreators } from 'redux';
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
8
import {showSnackbar} from 'actions/snackbar';
9
import navigateTo from 'actions/navigation';
Oleksii Savran's avatar
Oleksii Savran committed
10 11 12 13 14 15 16
import {
  currentClimateRequest,
  geoJsonRequest,
  loadAccessionsMapInfo,
  addToMyMaps,
  toggleMyMap,
} from 'accessions/actions/public';
17
import AccessionFilter from 'model/accession/AccessionFilter';
18
import Loading from 'ui/common/Loading';
19
import AccessionMapInfo from 'model/accession/AccessionMapInfo';
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
20
import MapLayer from 'model/genesys/MapTileLayer';
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
21
import ContentHeader from 'ui/common/heading/ContentHeader';
22
import Button from '@material-ui/core/Button';
23
import Tabs, { Tab } from 'ui/common/Tabs';
24
import PrettyFilters from 'ui/common/filter/PrettyFilters';
Oleksii Savran's avatar
Oleksii Savran committed
25
import ButtonBar from 'ui/common/buttons/ButtonBar';
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
26 27
import ContentLayout from 'ui/layout/ContentLayout';
import MapConfigSection from './c/MapConfigSection';
Oleksii Savran's avatar
Oleksii Savran committed
28
import MapSavedSection from './c/MapSavedSection';
29

30
import AccessionsFilters from 'accessions/ui/c/Filters';
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
31 32
import BioClimateDisplay from 'accessions/ui/c/BioClimateDisplay';
import {Dialog, Drawer} from '@material-ui/core';
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
33
import PageTitle from 'ui/common/PageTitle';
Oleksii Savran's avatar
Oleksii Savran committed
34 35 36 37 38
import Popover from '@material-ui/core/Popover';
import Paper from '@material-ui/core/Paper';
import Tooltip from '@material-ui/core/Tooltip';
import LayersIcon from '@material-ui/icons/Layers';
import ClimateIcon from '@material-ui/icons/WbSunny';
39
import PositionIcon from '@material-ui/icons/SettingsOverscan';
Oleksii Savran's avatar
Oleksii Savran committed
40 41
import FilterIcon from '@material-ui/icons/PermDataSetting';
import CancelIcon from '@material-ui/icons/Cancel';
Matija Obreza's avatar
Matija Obreza committed
42
import ApiCall from 'model/ApiCall';
Oleksii Savran's avatar
Oleksii Savran committed
43
import SaveIcon from '@material-ui/icons/Save';
Oleksii Savran's avatar
Oleksii Savran committed
44

45

46
let MapComponent;
47
let TileLayer;
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
48
let Popup;
Oleksii Savran's avatar
Oleksii Savran committed
49
let Rectangle;
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
50
let Control;
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
51 52

const popupContentLimit = 11;
53

54 55 56 57
interface IMapPageProps {
    classes?: any;
    t?: any;

58
    apiUrl: string;
Matija Obreza's avatar
Matija Obreza committed
59
    mapInfo: ApiCall<AccessionMapInfo>;
60
    suggestions: any;
61 62
    currentTab: string;
    filterCode: string;
63
    loadAccessionsMapInfo: any;
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
64
    mapLayers: MapLayer[];
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
65
    showSnackbar: (message: string) => void;
66 67 68
    navigateTo: (location: string) => void;
    initialPosition: number[];
    initialZoom: number;
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
69 70 71
    loading: boolean;
    geoJsonRequest: (filter, limit: number) => Promise<any>;
    currentClimateRequest: (lat, lng) => Promise<any>;
Oleksii Savran's avatar
Oleksii Savran committed
72 73 74
    myMaps: any[];
    addToMyMaps: (map: any) => void;
    toggleMyMap: (filterCode: string, enabled: boolean) => void;
75 76 77
}

const styles = (theme) => ({
Oleksii Savran's avatar
Oleksii Savran committed
78
    /*tslint:disable*/
79 80 81
    leafletContainer: {
        width: '100%',
        minHeight: '500px',
Oleksii Savran's avatar
Oleksii Savran committed
82 83 84 85 86 87 88 89 90 91 92 93 94
        height: 'calc(100vh - 245px)',
        [theme.breakpoints.down('md')]: {
            height: 'calc(100vh - 211px)',
        },
        [theme.breakpoints.down('sm')]: {
          height: 'calc(100vh - 199px)',
        },
        '& a[aria-label="Zoom in"], & a[aria-label="Zoom out"],': {
            color: '#000',
            '&:hover': {
                color: '#88ba42',
            },
        },
95
    },
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
96 97 98 99 100 101 102 103 104
    crosshair: {
        '& > div': {
            cursor: 'crosshair !important' as 'crosshair !important',
        },
    },
    climateDialog: {
        marginBottom: 0,
        overflow: 'auto' as 'auto',
    },
Oleksii Savran's avatar
Oleksii Savran committed
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
    mapButton: {
        height: '34px',
        width: '34px',
        minWidth: 'auto',
        minHeight: 'initial',
        padding: '5px 8px',
        backgroundColor: '#fff',
        borderRadius: '4px',
        border: 'solid 2px #b7b7b7',
        transition: 'none',
        '& > span > svg': {
            color: '#000',
        },
        '&:hover': {
            backgroundColor: '#f4f4f4',
            '& > span > svg': {
                color: '#88ba42',
            },
        },
Oleksii Savran's avatar
Oleksii Savran committed
124 125 126
        'html[dir="rtl"] &' : {
            left: 0,
        },
Oleksii Savran's avatar
Oleksii Savran committed
127 128 129 130 131 132 133
    },
    mapIcon: {
        fontSize: '20px',
    },
    tooltip: {
        fontSize: '1rem',
    },
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
134 135
    filterAccessionsButton: {
        position: 'absolute' as 'absolute',
Oleksii Savran's avatar
Oleksii Savran committed
136
        top: '4px',
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
137 138 139
    },
    pickPositionButton: {
        position: 'absolute' as 'absolute',
Oleksii Savran's avatar
Oleksii Savran committed
140
        top: '42px',
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
141
    },
142 143 144 145
    pickRectangleButton: {
        position: 'absolute' as 'absolute',
        top: '80px',
    },
Oleksii Savran's avatar
Oleksii Savran committed
146
    controlsButton: {
Oleksii Savran's avatar
Oleksii Savran committed
147 148 149 150 151 152 153 154 155 156
        width: '50px',
        height: '50px',
        '&:hover > span > svg': {
            color: '#000',
        },
    },
    layersControl: {
        maxHeight: '55vh',
        overflow: 'auto',
    },
Oleksii Savran's avatar
Oleksii Savran committed
157 158 159 160 161 162 163 164 165
    savedButton: {
        position: 'relative' as 'relative',
        top: '20px',
    },
    savedMapControls: {
        overflowX: 'hidden' as 'hidden',
        maxHeight: '55vh',
        overflow: 'auto',
    }
Oleksii Savran's avatar
Oleksii Savran committed
166
    /*tslint:enable*/
167 168 169
});

class BrowsePage extends React.Component<IMapPageProps, any> {
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
170 171

    private clickTimeout;
172
    private mapRef = null;
Oleksii Savran's avatar
Oleksii Savran committed
173 174
    private anchorLayersRef = null;
    private anchorSavedRef = null;
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
175

176
    protected static needs = [
177 178
      ({ params: { filterCode, initialPosition, initialZoom } }) => {
        return loadAccessionsMapInfo(filterCode || '', {center: initialPosition, zoom: initialZoom});
179 180 181
      },
    ];

Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
182
    public state = {
Oleksii Savran's avatar
Oleksii Savran committed
183 184
      clickLocation: [],
      searchBox: null,
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
185 186
      geoData: [],
      otherCount: 0,
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
187 188
      sidebarOpened: false,
      trackClickPos: false,
189
      trackAreaSelect: false,
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
190 191
      dialogOpened: false,
      climateData: null,
Oleksii Savran's avatar
Oleksii Savran committed
192
      layersControlsIsOpen: false,
Oleksii Savran's avatar
Oleksii Savran committed
193 194
      savedControlsIsOpen: false,
      colorInputIsFocused: false,
195 196 197
      mouseDown: false,
      areaStartPos: [0, 0],
      areaEndPos: [0, 0],
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
198 199
    };

200 201 202
    constructor(props, context) {
        super(props, context);
        if (typeof window !== 'undefined') {
203
            MapComponent = require('react-leaflet').Map;
204
            TileLayer = require('react-leaflet').TileLayer;
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
205
            Popup = require('react-leaflet').Popup;
Oleksii Savran's avatar
Oleksii Savran committed
206
            Rectangle = require('react-leaflet').Rectangle;
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
207
            Control = require('react-leaflet-control').default;
208 209 210 211 212
        }
    }


    public componentWillMount() {
213
      const { mapInfo, filterCode, loadAccessionsMapInfo, initialPosition, initialZoom } = this.props;
214
      // console.log(`Filter code for map ${filterCode} ?== ${mapInfo && mapInfo.filterCode}`, filterCode, mapInfo ? mapInfo.filterCode : 'No mapInfo');
Matija Obreza's avatar
Matija Obreza committed
215
      if (mapInfo && !mapInfo.loading && mapInfo.data.filterCode !== filterCode) {
216
        // console.log(`mapInfo.filterCode !== filterCode. updatingRoute`);
217
        loadAccessionsMapInfo(filterCode || '', {center: initialPosition, zoom: initialZoom});
218
      } else if (!mapInfo) {
219
        loadAccessionsMapInfo(filterCode || '', {center: initialPosition, zoom: initialZoom});
220 221 222
      }
    }

Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
223 224 225 226 227 228 229 230
    private openSidebar = () => {
      return this.setState({sidebarOpened: true});
    }

    private closeSidebar = () => {
      return this.setState({sidebarOpened: false});
    }

Oleksii Savran's avatar
Oleksii Savran committed
231 232 233 234 235 236 237 238
    private openLayersControls = () => {
      return this.setState({layersControlsIsOpen: true});
    }

    private closeLayersControls = () => {
      return this.setState({layersControlsIsOpen: false});
    }

Oleksii Savran's avatar
Oleksii Savran committed
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
    private openSavedControls = () => {
      return this.setState({savedControlsIsOpen: true});
    }

    private closeSavedControls = () => {
      return this.setState({savedControlsIsOpen: false});
    }

    private toggleInputFocus = () => {
      return this.setState({colorInputIsFocused: !this.state.colorInputIsFocused});
    }

    private handleMouseLeave = () => {
      if (!this.state.colorInputIsFocused) {
        this.closeSavedControls();
      }
    }

Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
257 258 259
    private setPositionPick = (e) => {
      e.preventDefault();
      e.stopPropagation();
260 261 262 263 264 265 266 267 268 269 270 271 272
      this.setState({trackClickPos: !this.state.trackClickPos, trackAreaSelect: false});
      return false;
    }

    private setAreaTrackPick = (e) => {
      e.preventDefault();
      e.stopPropagation();

      if (typeof window !== 'undefined' && isMobile()) {
        return this.handleMobileAreaSelect();
      }

      this.setState({trackAreaSelect: !this.state.trackAreaSelect, trackClickPos: false, areaStartPos: [0, 0], areaEndPos: [0, 0]});
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
273 274 275 276
      return false;
    }

    private handleTrackPosition = (e) => {
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
277
      const {showSnackbar, t, currentClimateRequest } = this.props;
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
278 279 280 281
      const { lat, lng } = e.latlng;

      this.setState({climateData: null});

Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
282
      currentClimateRequest(lat, lng)
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
283 284 285 286 287 288 289 290 291 292 293 294 295 296
        .then((res) => {
          // @ts-ignore
          if (!res || res === '') {
            return showSnackbar(t('accessions.public.p.map.noClimateData'));
          }
          return this.setState({climateData: res, dialogOpened: true});
        })
        .catch((error) =>  {
          if (error.code === 404) {
            return showSnackbar(t('accessions.public.p.map.noClimateData'));
          }
        });
    }

297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323
    private handleMobileAreaSelect = () => {
      const mapBounds = this.mapRef.leafletElement.getBounds();

      const {mapInfo: { data: { filter } }} = this.props;

      const filterWithGeo = {
        ...filter,
        geo: {
          longitude: {
            ge: mapBounds._southWest.lng,
            le: mapBounds._northEast.lng,
          },
          latitude: {
            ge:  mapBounds._southWest.lat,
            le: mapBounds._northEast.lat,
          },
          climate: filter && filter.geo && filter.geo.climate,
        },
      };
      this.myApplyFilters(filterWithGeo);
    }

    private handleTrackAreaStart = (e) => {
      const { lat, lng } = e.latlng;
      this.setState({areaStartPos: [lat, lng], areaEndPos: [lat, lng]});
    }

Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
324 325 326
    private hideDialog = () => {
      this.setState({dialogOpened: false});
    }
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
327

328 329 330 331 332 333 334 335 336 337 338
    private handleMoveEnd = (e) => {
      const {navigateTo, filterCode} = this.props;
      if (this.mapRef) {
        const {viewport: {center, zoom}} = this.mapRef;
        // console.log('Updating URL center: ', center, ' Zoom: ', zoom);
        if (center && center[0] !== 0 || center[1] !== 0) {
          navigateTo(`/a/map${filterCode && `/${filterCode}`}/@${center[0]},${center[1]},${zoom}z`);
        }
      }
    }

339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392
    private handleMove = (e) => {

      if (this.state.trackAreaSelect && this.mapRef) {
       this.setState({areaEndPos: [e.latlng.lat,  e.latlng.lng]});
      }
    }

    private handleMouseUp = (e) => {
      const {areaStartPos, areaEndPos, trackAreaSelect} = this.state;

      if (trackAreaSelect && (areaStartPos[0] || areaStartPos[0] || areaEndPos[1] || areaEndPos[1])) {

        const currentZoom = e.target._zoom;
        if (areaStartPos[0] - areaEndPos[0] < 3 / Math.pow(2, currentZoom - 2) && areaStartPos[1] - areaEndPos[1] <  3 / Math.pow(2, currentZoom - 2)) {
          this.setState({trackAreaSelect: false, mouseDown: false});
          return;
        }

        const {mapInfo: { data: { filter } }} = this.props;

        const filterWithGeo = {
          ...filter,
          geo: {
            latitude: {
              ge: Math.min(areaStartPos[0], areaEndPos[0]),
              le: Math.max(areaStartPos[0], areaEndPos[0]),
            },
            longitude: {
              ge: Math.min(areaStartPos[1], areaEndPos[1]),
              le: Math.max(areaStartPos[1], areaEndPos[1]),
            },
            climate: filter && filter.geo && filter.geo.climate,
          },
        };
        this.myApplyFilters(filterWithGeo);
      }
      this.setState({trackAreaSelect: false, mouseDown: false});
    }

    private inFilterBounds = (searchBounds) => {
      const {mapInfo: { data: { filter } }} = this.props;

      // no filter = always in bounds
      if (!filter || !filter.geo) {
       return true;
      }
      // longitude check
      if (searchBounds[0][1] < filter.geo.longitude.ge || searchBounds[1][1] > filter.geo.longitude.le) {
        return false;
      }
      // latitude check
      return !(searchBounds[0][0] < filter.geo.latitude.ge || searchBounds[1][0] > filter.geo.latitude.le);
    }

Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
393
    private onMapClick = (e) => {
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
394 395 396 397

        if (e.originalEvent.target.className.indexOf('leaflet-touch') === -1) {
          return;
        }
398
        this.setState({mouseDown: true});
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
399 400 401 402 403

        if (this.state.trackClickPos) {
          return this.handleTrackPosition(e);
        }

404 405 406 407
        if (this.state.trackAreaSelect) {
          return this.handleTrackAreaStart(e);
        }

Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
408 409 410 411 412 413 414 415 416 417 418 419 420 421
        if (this.clickTimeout) {
            console.log('prevented');
            clearTimeout(this.clickTimeout);
            this.clickTimeout = null;
            return;
        }
        this.clickTimeout = setTimeout(() => {
            console.log('started');
            this.clickTimeout = null;
            const currentZoom = e.target._zoom;
            if (currentZoom < 4) {
                return;
            }

Matija Obreza's avatar
Matija Obreza committed
422
            const {mapInfo: { data: { filter } }, geoJsonRequest} = this.props;
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
423 424 425

            const correctLng = Math.abs(e.latlng.lng) > 180 ? (360 + e.latlng.lng) % 180 : e.latlng.lng;

Oleksii Savran's avatar
Oleksii Savran committed
426
            const width = Math.pow(2, currentZoom - 2);
427
            // console.log(`zoom=${currentZoom} width=${width} diff=${ 3 / width}`);
Oleksii Savran's avatar
Oleksii Savran committed
428 429 430 431
            const searchBounds: number[][] = [
                [ e.latlng.lat - (3 / width), correctLng - (3 / width) ],
                [ e.latlng.lat + (3 / width), correctLng + (3 / width) ],
            ];
432 433 434 435 436

            if (!this.inFilterBounds(searchBounds)) {
              return;
            }

Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
437 438 439 440
            const filterWithGeo = {
                ...filter,
                geo: {
                    longitude: {
Oleksii Savran's avatar
Oleksii Savran committed
441 442
                        ge: searchBounds[0][1],
                        le: searchBounds[1][1],
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
443 444
                    },
                    latitude: {
Oleksii Savran's avatar
Oleksii Savran committed
445 446
                        ge: searchBounds[0][0],
                        le: searchBounds[1][0],
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
447
                    },
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
448
                    climate: filter && filter.geo && filter.geo.climate,
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
449 450
                },
            };
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
451
            geoJsonRequest(filterWithGeo, popupContentLimit)
Oleksii Savran's avatar
Oleksii Savran committed
452
                .then((res) => this.setState({clickLocation: [ e.latlng.lat,  e.latlng.lng], searchBox: searchBounds, geoData: res.geoJson, otherCount: res.otherCount}));
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
453 454 455
        }, 200);
    }

456 457 458 459 460
    /// Wrap loadAccessionsMapInfo dispatch and fills the current sort selection
    protected myApplyFilters = (filters: AccessionFilter) => {
      const { loadAccessionsMapInfo } = this.props;

      loadAccessionsMapInfo(filters);
461 462 463
    }

    public render() {
464
        const { searchBox, geoData, otherCount, sidebarOpened, trackClickPos, dialogOpened, climateData, layersControlsIsOpen, savedControlsIsOpen, trackAreaSelect, mouseDown, areaStartPos, areaEndPos } = this.state;
Oleksii Savran's avatar
Oleksii Savran committed
465
        const { mapInfo, mapLayers, currentTab, classes, filterCode, loading, suggestions, t, loadAccessionsMapInfo, initialPosition, initialZoom, myMaps, addToMyMaps, toggleMyMap } = this.props;
466

Oleksii Savran's avatar
Oleksii Savran committed
467 468 469 470 471
        const position = initialPosition[0] && initialPosition[1] ? initialPosition : [5, 5];
        const initialBounds = [
          [-40, 60],
          [50, -50],
        ];
472

Matija Obreza's avatar
Matija Obreza committed
473
        if (! mapInfo || mapInfo.loading) {
474
          return <Loading />;
Matija Obreza's avatar
Matija Obreza committed
475 476
        } else if (mapInfo.error) {
          return <div>{ mapInfo.error }</div>;
477
        }
478

Matija Obreza's avatar
Matija Obreza committed
479 480 481 482
        if (mapInfo.data.bounds[0][0] !== null && mapInfo.data.bounds[1][0] !== null) {
          if (Math.abs(mapInfo.data.bounds[0][0] - mapInfo.data.bounds[1][0]) < Math.abs(initialBounds[0][0] - initialBounds[1][0])) {
            initialBounds[0][0] = mapInfo.data.bounds[0][0];
            initialBounds[1][0] = mapInfo.data.bounds[1][0];
483
          }
Matija Obreza's avatar
Matija Obreza committed
484 485 486
          if (Math.abs(mapInfo.data.bounds[0][1] - mapInfo.data.bounds[1][1]) < Math.abs(initialBounds[0][1] - initialBounds[1][1])) {
            initialBounds[0][1] = mapInfo.data.bounds[0][1];
            initialBounds[1][1] = mapInfo.data.bounds[1][1];
487
          }
Oleksii Savran's avatar
Oleksii Savran committed
488 489
        }

490 491 492 493 494 495 496 497 498 499
        const suggestionTerms = new Map();
        if (suggestions) {
            Object.keys(suggestions).forEach((key) => {
              const overviewEl = suggestions[key];
              const terms = new Map();
              overviewEl.terms.forEach((term) => terms.set(term.term, term.count));
              suggestionTerms.set(key, terms);
            });
        }

Matija Obreza's avatar
Matija Obreza committed
500
        // console.log(suggestionTerms);
501

502
        // const color = 'f00ba0';
503
        const layerUrl = `{s}/acn/tile/{z}/{x}/{y}?f=${filterCode ? filterCode : ''}`; // `&color=${color}`;
504 505

        return (
Oleksii Savran's avatar
Oleksii Savran committed
506
            <ContentLayout customHeaderHeight>
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
507 508
              <PageTitle title={ t('accessions.public.p.browse.title') } />
              <ContentHeader title={ t('accessions.public.p.browse.title') } subTitle={ t('accessions.public.p.browse.subTitle') } />
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
509 510

              <Drawer variant="temporary" open={ sidebarOpened } onClose={ this.closeSidebar }>
Maxym Borodenko's avatar
Maxym Borodenko committed
511 512 513 514 515 516 517
                <AccessionsFilters
                  filterCode={ filterCode || '' }
                  terms={ suggestionTerms }
                  onSubmit={ loadAccessionsMapInfo }
                  initialValues={ mapInfo.data.filter }
                />
              </Drawer>
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
518 519 520 521 522 523

              <Dialog open={ dialogOpened } onClose={ this.hideDialog } maxWidth="md" fullWidth>
                <BioClimateDisplay classes={ {section: classes.climateDialog} } climateData={ climateData }/>
              </Dialog>

                <Tabs
524 525
                    tab={ currentTab }
                    actions={
Oleksii Savran's avatar
Oleksii Savran committed
526
                      <ButtonBar>
527
                        <span>
Matija Obreza's avatar
Matija Obreza committed
528 529
                          <form method="post" action="/proxy/api/v1/acn/downloadKml">
                            <input type="hidden" name="f" value={ filterCode } />
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
530
                            <Button type="submit">{ `${t('common:action.download')} ${t('accessions.public.p.map.kml')}` }</Button>
Matija Obreza's avatar
Matija Obreza committed
531
                          </form>
532
                        </span>
Oleksii Savran's avatar
Oleksii Savran committed
533
                      </ButtonBar>
534 535
                    }
                >
Viacheslav Pavlov's avatar
i18n  
Viacheslav Pavlov committed
536
                  <Tab name="overview" to={ `/a/overview/${filterCode || ''}` }>{ t('accessions.tab.overview') }</Tab>
Oleksii Savran's avatar
Oleksii Savran committed
537
                  <Tab name="data" to={ `/a/${filterCode || ''}` }>{ t('accessions.tab.data') }</Tab>
Viacheslav Pavlov's avatar
i18n  
Viacheslav Pavlov committed
538
                  <Tab name="map" to={ `/a/map/${filterCode || '' }` }>{ t('accessions.tab.map') }</Tab>
539 540 541
                </Tabs>
                <PrettyFilters
                    prefix="accessions"
Matija Obreza's avatar
Matija Obreza committed
542
                    filterObj={ mapInfo.data.filter || {} }
543
                    onSubmit={ this.myApplyFilters }
Oleksii Savran's avatar
Oleksii Savran committed
544
                    displayName="accessions.common.modelName"
Matija Obreza's avatar
Matija Obreza committed
545
                    amount={ mapInfo.data.accessionCount }
546
                />
547
                <div className={ `${classes.leafletContainer} ${(trackClickPos || trackAreaSelect) && classes.crosshair}` }>
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
548 549
                  { loading && <Loading /> }
                  { mapInfo && typeof window !== 'undefined' &&
550
                    <MapComponent
551
                        onMoveend={ this.handleMoveEnd }
552 553 554
                        onMouseMove={ this.handleMove }
                        onMouseUp={ this.handleMouseUp }
                        onMouseDown={ this.onMapClick }
555
                        center={ position }
556
                        dragging={ !trackAreaSelect }
557
                        zoom={ initialZoom || 3 } minZoom={ 2 } maxZoom={ 14 }
Oleksii Savran's avatar
Oleksii Savran committed
558
                        zoomDelta={ 0.5 }
Matija Obreza's avatar
Matija Obreza committed
559
                        bounds={ !initialZoom && initialPosition[0] && !initialPosition[1] ? mapInfo.data.bounds : initialBounds }
560
                        ref={ (ref) => this.mapRef = ref }
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
561
                    >
Oleksii Savran's avatar
Oleksii Savran committed
562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588
                        <Control position="topleft">
                            <Tooltip
                              classes={ { tooltip: classes.tooltip } }
                              className={ `${classes.filterAccessionsButton} ${classes.mapButton}` }
                              title={ t(`accessions.public.p.map.filterAccessions`) }
                              placement="right"
                            >
                                <Button variant="outlined" onClick={ this.openSidebar }>
                                    <FilterIcon className={ classes.mapIcon }/>
                                </Button>
                            </Tooltip>
                        </Control>
                        <Control position="topleft">
                            <Tooltip
                              classes={ { tooltip: classes.tooltip } }
                              className={ `${classes.pickPositionButton} ${classes.mapButton}` }
                              title={ t(`accessions.public.p.map.${ trackClickPos ? 'stopPick' : 'pick'}`) }
                              placement="right"
                            >
                                <Button variant="outlined" onClick={ this.setPositionPick }>
                                    { trackClickPos ?
                                        <CancelIcon className={ classes.mapIcon }/> :
                                        <ClimateIcon className={ classes.mapIcon }/>
                                    }
                                </Button>
                            </Tooltip>
                        </Control>
589 590 591 592 593 594 595 596 597 598 599 600 601 602 603
                        <Control position="topleft">
                            <Tooltip
                              classes={ { tooltip: classes.tooltip } }
                              className={ `${classes.pickRectangleButton} ${classes.mapButton}` }
                              title={ t(`accessions.public.p.map.${ trackClickPos ? 'stopPick' : 'selectArea'}`) }
                              placement="right"
                            >
                                <Button variant="outlined" onClick={ this.setAreaTrackPick }>
                                    { trackAreaSelect ?
                                        <CancelIcon className={ classes.mapIcon }/> :
                                        <PositionIcon className={ classes.mapIcon }/>
                                    }
                                </Button>
                            </Tooltip>
                        </Control>
Oleksii Savran's avatar
Oleksii Savran committed
604 605 606
                        <Control position="topright">
                            <div
                              onMouseEnter={ this.openLayersControls }
Oleksii Savran's avatar
Oleksii Savran committed
607
                              ref={ (ref) => this.anchorLayersRef = ref }
Oleksii Savran's avatar
Oleksii Savran committed
608 609 610
                            >
                                <Button
                                  variant="contained"
Oleksii Savran's avatar
Oleksii Savran committed
611
                                  className={ `${classes.mapButton} ${classes.controlsButton}` }
Oleksii Savran's avatar
Oleksii Savran committed
612 613 614 615 616
                                  onClick={ this.openLayersControls }
                                >
                                    <LayersIcon/>
                                </Button>
                                <Popover open={ layersControlsIsOpen }
Oleksii Savran's avatar
Oleksii Savran committed
617
                                         anchorEl={ this.anchorLayersRef }
Oleksii Savran's avatar
Oleksii Savran committed
618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633
                                         onClose={ this.closeLayersControls }
                                         transformOrigin={ {
                                             vertical: 'top',
                                             horizontal: 'right',
                                         } }
                                         anchorOrigin={ {
                                             vertical: 'top',
                                             horizontal: 'right',
                                         } }
                                >
                                    <Paper className={ classes.layersControl } onMouseLeave={ this.closeLayersControls }>
                                        <MapConfigSection/>
                                    </Paper>
                                </Popover>
                            </div>
                        </Control>
Oleksii Savran's avatar
Oleksii Savran committed
634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670
                        <Control position="topright">
                          <div
                            onMouseEnter={ this.openSavedControls }
                            ref={ (ref) => this.anchorSavedRef = ref }
                            className={ classes.savedButton }
                          >
                            <Button
                              variant="contained"
                              className={ `${classes.mapButton} ${classes.controlsButton}` }
                              onClick={ this.openSavedControls }
                            >
                              <SaveIcon/>
                            </Button>
                            <Popover open={ savedControlsIsOpen }
                                     anchorEl={ this.anchorSavedRef }
                                     onClose={ this.closeSavedControls }
                                     transformOrigin={ {
                                       vertical: 'top',
                                       horizontal: 'right',
                                     } }
                                     anchorOrigin={ {
                                       vertical: 'top',
                                       horizontal: 'right',
                                     } }
                            >
                              <Paper className={ classes.savedMapControls } onMouseLeave={ this.handleMouseLeave }>
                                <MapSavedSection
                                  filterCode={ filterCode }
                                  toggleInputFocus={ this.toggleInputFocus }
                                  addToMyMaps={ addToMyMaps }
                                  myMaps={ myMaps }
                                  toggleMyMap={ toggleMyMap }
                                />
                              </Paper>
                            </Popover>
                          </div>
                        </Control>
671 672 673 674 675
                        { trackAreaSelect && mouseDown &&
                            <Rectangle bounds={ [areaStartPos, areaEndPos] }>
                                <div style={ {backgroundColor: 'grey', opacity: 0.4} }/>
                            </Rectangle>
                        }
Oleksii Savran's avatar
Oleksii Savran committed
676
                        <TileLayer
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
677
                            zIndex={ 0 }
678
                            opacity={ 0.50 }
Matija Obreza's avatar
Matija Obreza committed
679 680
                            attribution={ 'Tiles &copy; Esri &mdash; Esri, DeLorme, NAVTEQ' }
                            url={ 'https://server.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Base/MapServer/tile/{z}/{y}/{x}' }
681
                        />
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
682 683

                        { mapLayers && mapLayers.filter((layer) => layer.enabled).map((layer, index) => <TileLayer zIndex={ index + 1 } key={ layer.name } { ...layer }/>) }
Oleksii Savran's avatar
Oleksii Savran committed
684 685 686 687 688 689 690 691 692 693 694 695 696
                        { myMaps && myMaps.filter((layer) => layer.enabled).map((layer, index) => {
                            return (
                              <TileLayer
                                updateInterval={ 1000 }
                                updateWhenZooming={ false }
                                zIndex={ mapLayers.length + 1 + index }
                                key={ layer.name }
                                url={ `{s}/acn/tile/{z}/{x}/{y}?f=${layer.filterCode}&color=${layer.color}` }
                                subdomains={ mapInfo.data.tileServers }
                              />
                            );
                          })
                        }
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
697

Matija Obreza's avatar
Matija Obreza committed
698
                        <TileLayer
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
699
                            zIndex={ mapLayers.length + 1 }
700 701 702 703
                            updateInterval={ 1000 }
                            updateWhenZooming={ false }
                            attribution="&amp;copy Accession localities from <a href=&quot;/&quot;>Genesys PGR</a>"
                            url={ layerUrl }
Matija Obreza's avatar
Matija Obreza committed
704
                            subdomains={ mapInfo.data.tileServers }
705
                        />
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
706
                        { geoData && geoData.length > 0 &&
Oleksii Savran's avatar
Oleksii Savran committed
707 708 709 710
                            <Rectangle
                              bounds={ searchBox }
                              ref={ (marker) => marker && marker.leafletElement.openPopup() }
                            >
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
711 712
                                <Popup open>
                                    <div>
713
                                        { geoData.map((feature, idx) => (<div key={ idx }><Link to={ `/a/${feature.properties.uuid}` }>{ `${feature.properties.accessionNumber} ${feature.properties.instCode}` }</Link></div>)) }
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
714 715 716
                                        { otherCount > 0 && <div>{ t('accessions.public.p.map.andMore', {otherMore: otherCount}) }</div> }
                                    </div>
                                </Popup>
Oleksii Savran's avatar
Oleksii Savran committed
717
                            </Rectangle>
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
718
                        }
719
                    </MapComponent>
720 721
                }
                </div>
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
722
            </ContentLayout>
723 724 725 726 727
        );
    }
}

const mapStateToProps = (state, ownProps) => ({
728
    suggestions: state.accessions.public.suggestions,
Matija Obreza's avatar
Matija Obreza committed
729
    mapInfo: state.accessions.public.mapInfo,
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
730
    mapLayers: state.accessions.public.mapLayers,
731
    filterCode: ownProps.match.params.filterCode || (state.accessions.public.mapInfo ? state.accessions.public.mapInfo.data && state.accessions.public.mapInfo.data.filterCode : ''),
732
    currentTab: ownProps.match.params.tab || 'map', // current tab, or ownProps.location.pathname
733 734
    initialPosition: isNaN(Number(ownProps.match.params.lat)) || isNaN(Number(ownProps.match.params.lng)) ? [] : [Number(ownProps.match.params.lat), Number(ownProps.match.params.lng)],
    initialZoom: isNaN(Number(ownProps.match.params.zoom)) ? null : Number(ownProps.match.params.zoom),
Oleksii Savran's avatar
Oleksii Savran committed
735
    myMaps: state.accessions.public.myMaps,
736 737 738
});

const mapDispatchToProps = (dispatch) => bindActionCreators({
739
    loadAccessionsMapInfo,
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
740 741
    geoJsonRequest,
    currentClimateRequest,
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
742
    showSnackbar,
743
    navigateTo,
Oleksii Savran's avatar
Oleksii Savran committed
744 745
    addToMyMaps,
    toggleMyMap,
746 747
}, dispatch);

748
export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(translate()(BrowsePage)));