nakarte

Source code of https://map.sikmir.ru (fork)
git clone git://git.sikmir.ru/nakarte
Log | Files | Refs | LICENSE

App.js (14481B)


      1 import './App.css';
      2 import './leaflet-fixes.css';
      3 import L from 'leaflet';
      4 import 'leaflet/dist/leaflet.css';
      5 import {MapWithSidebars} from '~/lib/leaflet.map.sidebars';
      6 import '~/lib/leaflet.control.printPages/control';
      7 import '~/lib/leaflet.control.caption';
      8 import config from './config';
      9 import '~/lib/leaflet.control.coordinates';
     10 import enableLayersControlHotKeys from '~/lib/leaflet.control.layers.hotkeys';
     11 import '~/lib/leaflet.hashState/Leaflet.Map';
     12 import '~/lib/leaflet.hashState/Leaflet.Control.Layers';
     13 import {fixAll} from '~/lib/leaflet.fixes';
     14 import './adaptive.css';
     15 import '~/lib/leaflet.control.panoramas';
     16 import '~/lib/leaflet.control.track-list/track-list';
     17 import '~/lib/leaflet.control.track-list/control-ruler';
     18 import '~/lib/leaflet.control.track-list/track-list.hash-state';
     19 import enableLayersControlAdaptiveHeight from '~/lib/leaflet.control.layers.adaptive-height';
     20 import enableLayersMinimize from '~/lib/leaflet.control.layers.minimize';
     21 import enableLayersConfig from '~/lib/leaflet.control.layers.configure';
     22 import raiseControlsOnFocus from '~/lib/leaflet.controls.raise-on-focus';
     23 import {getLayers} from './layers';
     24 import '~/lib/leaflet.control.layers.events';
     25 import '~/lib/leaflet.control.jnx';
     26 import '~/lib/leaflet.control.jnx/hash-state';
     27 import '~/lib/leaflet.control.azimuth';
     28 import {SessionsControl} from '~/lib/leaflet.control.sessions';
     29 import {hashState, bindHashStateReadOnly} from '~/lib/leaflet.hashState/hashState';
     30 import {LocateControl} from '~/lib/leaflet.control.locate';
     31 import {notify} from '~/lib/notifications';
     32 import ZoomDisplay from '~/lib/leaflet.control.zoom-display';
     33 import * as logging from '~/lib/logging';
     34 import safeLocalStorage from '~/lib/safe-localstorage';
     35 import {ExternalMaps} from '~/lib/leaflet.control.external-maps';
     36 import {SearchControl} from '~/lib/leaflet.control.search';
     37 import '~/lib/leaflet.placemark';
     38 import '~/vendored/mapbbcode/FunctionButton';
     39 import Contextmenu from '~/lib/contextmenu';
     40 import iconMenu from './images/menu.png';
     41 
     42 const locationErrorMessage = {
     43     0: 'Your browser does not support geolocation.',
     44     1: 'Geolocation is blocked for this site. Please, enable in browser setting.',
     45     2: 'Failed to acquire position for unknown reason.',
     46 };
     47 
     48 const minimizeStateAuto = 0;
     49 const minimizeStateMinimized = 1;
     50 const minimizeStateExpanded = 2;
     51 
     52 function isInIframe() {
     53     // Check if the window is not the top window
     54     return window.self !== window.top;
     55 }
     56 
     57 function setUp() { // eslint-disable-line complexity
     58     const startInfo = {
     59         href: window.location.href,
     60         localStorageKeys: Object.keys(safeLocalStorage),
     61         mobile: L.Browser.mobile,
     62     };
     63     fixAll();
     64 
     65     function validateMinimizeState(state) {
     66         state = Number(state);
     67         if (state === minimizeStateMinimized || state === minimizeStateExpanded) {
     68             return state;
     69         }
     70         return minimizeStateAuto;
     71     }
     72     const minimizeState = hashState.getState('min') ?? [];
     73     const minimizeControls = {
     74         tracks: validateMinimizeState(minimizeState[0]),
     75         layers: validateMinimizeState(minimizeState[1]),
     76         print: validateMinimizeState(minimizeState[2]),
     77         search: validateMinimizeState(minimizeState[3]),
     78     };
     79 
     80     const map = new MapWithSidebars('map', {
     81             zoomControl: false,
     82             fadeAnimation: false,
     83             attributionControl: false,
     84             inertiaMaxSpeed: 1500,
     85             worldCopyJump: true,
     86             maxZoom: 18
     87         }
     88     );
     89 
     90     const tracklist = new L.Control.TrackList({
     91         keysToExcludeOnCopyLink: ['q', 'r']
     92     });
     93 
     94     /* controls top-left corner */
     95 
     96     new L.Control.Caption(config.caption, {
     97             position: 'topleft'
     98         }
     99     ).addTo(map);
    100 
    101     new ZoomDisplay().addTo(map);
    102 
    103     const searchOptions = {
    104         position: 'topleft',
    105         stackHorizontally: true,
    106         maxMapWidthToMinimize: 620,
    107     };
    108     if (minimizeControls.search === minimizeStateMinimized) {
    109         searchOptions.maxMapHeightToMinimize = Infinity;
    110         searchOptions.maxMapWidthToMinimize = Infinity;
    111     } else if (minimizeControls.search === minimizeStateExpanded) {
    112         searchOptions.maxMapHeightToMinimize = 0;
    113         searchOptions.maxMapWidthToMinimize = 0;
    114     }
    115     const searchControl = new SearchControl(searchOptions)
    116         .addTo(map)
    117         .enableHashState('q');
    118     map.getPlacemarkHashStateInterface().enableHashState('r');
    119 
    120     new L.Control.Scale({
    121         imperial: false,
    122         position: 'topleft',
    123         stackHorizontally: true
    124     }).addTo(map);
    125 
    126     let sessionsControl;
    127     if (!isInIframe()) {
    128         sessionsControl = new SessionsControl(tracklist, {noButton: true}).addTo(map);
    129         const size = L.Browser.touch ? 30 : 26;
    130         const offset = L.Browser.touch ? 7 : 5;
    131         const menuButton = L.functionButtons(
    132             [
    133                 {
    134                     content: iconMenu,
    135                     title: 'Menu',
    136                     bgPos: [-offset, -offset],
    137                     imageSize: size,
    138                 },
    139             ],
    140             {position: 'topleft'}
    141         );
    142         menuButton.addTo(map);
    143         menuButton.on('clicked', (e) => {
    144             new Contextmenu([
    145                 {
    146                     text: 'Recent sessions',
    147                     callback: () => sessionsControl.showSessionListWindow(),
    148                 },
    149                 {
    150                     text: 'Copy share link',
    151                     callback: () => tracklist.copyAllTracksToClipboard(e, true),
    152                 },
    153             ]).show(e);
    154         });
    155     }
    156 
    157     new ExternalMaps({position: 'topleft'}).addTo(map);
    158 
    159     new L.Control.TrackList.Ruler(tracklist).addTo(map);
    160 
    161      const panoramas = new L.Control.Panoramas()
    162         .addTo(map)
    163         .enableHashState('n2');
    164     L.Control.Panoramas.hashStateUpgrader(panoramas).enableHashState('n');
    165 
    166     new L.Control.Coordinates(config.elevationTileUrl, {position: 'topleft'}).addTo(map);
    167 
    168     const azimuthControl = new L.Control.Azimuth({position: 'topleft'}).addTo(map);
    169 
    170     const locateControl = new LocateControl({
    171         position: 'topleft',
    172         showError: function({code, message}) {
    173             let customMessage = locationErrorMessage[code];
    174             if (!customMessage) {
    175                 customMessage = `Geolocation error: ${message}`;
    176             }
    177             notify(customMessage);
    178         }
    179     }).addTo(map);
    180     let {valid: validPositionInHash} = map.validateState(hashState.getState('m'));
    181     map.enableHashState('m', [config.defaultZoom, ...config.defaultLocation]);
    182 
    183     /* controls top-right corner */
    184 
    185     const layersControl = L.control.layers(null, null, {collapsed: false})
    186         .addTo(map);
    187     enableLayersControlHotKeys(layersControl);
    188     enableLayersControlAdaptiveHeight(layersControl);
    189     enableLayersMinimize(layersControl);
    190     enableLayersConfig(layersControl, getLayers());
    191     layersControl.enableHashState('l');
    192 
    193     /* controls bottom-left corner */
    194 
    195     const attribution = L.control.attribution({
    196         position: 'bottomleft',
    197         prefix: false,
    198     });
    199     map.on('resize', function() {
    200         if (map.getSize().y > 567) {
    201             map.addControl(attribution);
    202             // Hack to keep control at the bottom of the map
    203             const container = attribution._container;
    204             const parent = container.parentElement;
    205             parent.appendChild(container);
    206         } else {
    207             map.removeControl(attribution);
    208         }
    209     });
    210     if (map.getSize().y > 567) {
    211         map.addControl(attribution);
    212     }
    213 
    214     const printControl = new L.Control.PrintPages({position: 'bottomleft'})
    215         .addTo(map)
    216         .enableHashState('p');
    217     if (
    218         minimizeControls.print === minimizeStateMinimized ||
    219         (minimizeControls.print === minimizeStateAuto && !printControl.hasPages())
    220     ) {
    221         printControl.setMinimized();
    222     }
    223 
    224     const jnxControl = new L.Control.JNX(layersControl, {position: 'bottomleft'})
    225         .addTo(map)
    226         .enableHashState('j');
    227 
    228     /* controls bottom-right corner */
    229 
    230     tracklist.addTo(map);
    231     const tracksHashParams = tracklist.hashParams();
    232 
    233     let hasTrackParamsInHash = false;
    234     for (let param of tracksHashParams) {
    235         if (hashState.hasKey(param)) {
    236             hasTrackParamsInHash = true;
    237             break;
    238         }
    239     }
    240 
    241     if (sessionsControl) {
    242         (async() => {
    243             await sessionsControl.loadSession();
    244             await sessionsControl.consumeSessionFromHash();
    245             if (await sessionsControl.importOldSessions() && !hasTrackParamsInHash) {
    246                 notify(
    247                     'If some tracks disappeared from the tracks list, ' +
    248                     'you can find them in the new list of recent sessions in the upper left corner.'
    249                 );
    250             }
    251         })();
    252     }
    253 
    254     if (hashState.hasKey('autoprofile') && hasTrackParamsInHash) {
    255         tracklist.once('loadedTracksFromParam', () => {
    256             const track = tracklist.tracks()[0];
    257             if (track) {
    258                 tracklist.showElevationProfileForTrack(track);
    259             }
    260         });
    261     }
    262 
    263     // This is not quite correct: minimizeControls should have effect only during loading, but the way it is
    264     // implemented, it will affect expanding when loading track from hash param during session.
    265     // But as parameter is expected to be found only when site is embedded using iframe,
    266     // the latter scenario is not very probable.
    267     if (minimizeControls.tracks !== minimizeStateMinimized) {
    268         tracklist.on('loadedTracksFromParam', () => tracklist.setExpanded());
    269     }
    270 
    271     for (let param of tracksHashParams) {
    272         bindHashStateReadOnly(param, tracklist.loadTrackFromParam.bind(tracklist, param));
    273     }
    274 
    275     /* set map position */
    276 
    277     if (!validPositionInHash) {
    278         if (hasTrackParamsInHash) {
    279             tracklist.whenLoadDone(() => tracklist.setViewToAllTracks(true));
    280         } else {
    281             locateControl.moveMapToCurrentLocation(config.defaultZoom);
    282         }
    283     }
    284 
    285     /* adaptive layout */
    286 
    287     if (
    288         minimizeControls.layers === minimizeStateAuto && L.Browser.mobile ||
    289         minimizeControls.layers === minimizeStateMinimized
    290     ) {
    291         layersControl.setMinimized();
    292     }
    293 
    294     if (L.Browser.mobile) {
    295         map.on('mousedown dragstart', () => layersControl.setMinimized());
    296     }
    297 
    298     if (
    299         minimizeControls.tracks === minimizeStateAuto && L.Browser.mobile && !tracklist.hasTracks() ||
    300         minimizeControls.tracks === minimizeStateMinimized
    301     ) {
    302         tracklist.setMinimized();
    303     }
    304 
    305     raiseControlsOnFocus(map);
    306 
    307     /* track list and azimuth measure interaction */
    308 
    309     tracklist.on('startedit', () => azimuthControl.disableControl());
    310     tracklist.on('elevation-shown', () => azimuthControl.hideProfile());
    311     azimuthControl.on('enabled', () => {
    312         tracklist.stopEditLine();
    313     });
    314     azimuthControl.on('elevation-shown', () => tracklist.hideElevationProfile());
    315 
    316     /* setup events logging */
    317 
    318     function getLayerLoggingInfo(layer) {
    319         if (layer.meta) {
    320             return {title: layer.meta.title};
    321         } else if (layer.__customLayer) {
    322             return {custom: true, title: layer.__customLayer.title, url: layer._url};
    323         }
    324         return null;
    325     }
    326 
    327     function getLatLngBoundsLoggingInfo(latLngBounds) {
    328         return {
    329             west: latLngBounds.getWest(),
    330             south: latLngBounds.getSouth(),
    331             east: latLngBounds.getEast(),
    332             north: latLngBounds.getNorth(),
    333         };
    334     }
    335 
    336     function getErrorLoggingInfo(error) {
    337         return error
    338             ? {
    339                   name: error.name,
    340                   message: error.message,
    341                   stack: error.stack,
    342               }
    343             : null;
    344     }
    345 
    346     function logUsedMaps() {
    347         const layers = [];
    348         map.eachLayer((layer) => {
    349             const layerInfo = getLayerLoggingInfo(layer);
    350             if (layerInfo) {
    351                 layers.push(layerInfo);
    352             }
    353         });
    354         const bounds = map.getBounds();
    355         logging.logEvent('activeLayers', {
    356             layers,
    357             view: getLatLngBoundsLoggingInfo(bounds),
    358         });
    359     }
    360 
    361     L.DomEvent.on(document, 'mousemove click touchend', L.Util.throttle(logUsedMaps, 30000));
    362 
    363     printControl.on('mapRenderEnd', function(e) {
    364         logging.logEvent('mapRenderEnd', {
    365             eventId: e.eventId,
    366             success: e.success,
    367             error: getErrorLoggingInfo(e.error),
    368         });
    369     });
    370 
    371     printControl.on('mapRenderStart', function(e) {
    372         const layers = [];
    373         map.eachLayer((layer) => {
    374             const layerInfo = getLayerLoggingInfo(layer);
    375             if (layer.options?.print && layerInfo) {
    376                 layers.push({
    377                     ...getLayerLoggingInfo(layer),
    378                     scaleDependent: layer.options.scaleDependent
    379                 });
    380             }
    381         });
    382         logging.logEvent('mapRenderStart', {
    383             eventId: e.eventId,
    384             action: e.action,
    385             scale: e.scale,
    386             resolution: e.resolution,
    387             pages: e.pages.map((page) => getLatLngBoundsLoggingInfo(page.latLngBounds)),
    388             zooms: e.zooms,
    389             layers
    390         });
    391     });
    392 
    393     jnxControl.on('tileExportStart', function(e) {
    394         logging.logEvent('tileExportStart', {
    395             eventId: e.eventId,
    396             layer: getLayerLoggingInfo(e.layer),
    397             zoom: e.zoom,
    398             bounds: getLatLngBoundsLoggingInfo(e.bounds),
    399         });
    400     });
    401 
    402     jnxControl.on('tileExportEnd', function(e) {
    403         logging.logEvent('tileExportEnd', {
    404             eventId: e.eventId,
    405             success: e.success,
    406             error: getErrorLoggingInfo(e.error),
    407         });
    408     });
    409 
    410     searchControl.on('resultreceived', function(e) {
    411         logging.logEvent('SearchProviderSelected', {
    412             provider: e.provider,
    413             query: e.query,
    414         });
    415         if (e.provider === 'Links' && e.result.error) {
    416             logging.logEvent('SearchLinkError', {
    417                 query: e.query,
    418                 result: e.result,
    419             });
    420         }
    421         if (e.provider === 'Coordinates') {
    422             logging.logEvent('SearchCoordinates', {
    423                 query: e.query,
    424                 result: e.result,
    425             });
    426         }
    427     });
    428 
    429     logging.logEvent('start', startInfo);
    430     logUsedMaps();
    431 }
    432 
    433 export {setUp};