nakarte

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

App.js (14633B)


      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     const areHotkeysEnabled = !L.Browser.touch || !L.Browser.mobile;
    187     if (areHotkeysEnabled) {
    188         enableLayersControlHotkeys(layersControl);
    189     }
    190     enableLayersControlAdaptiveHeight(layersControl);
    191     enableLayersMinimize(layersControl);
    192     enableLayersConfig(layersControl, getLayers(), {withHotkeys: areHotkeysEnabled});
    193     layersControl.addTo(map);
    194     layersControl.enableHashState('l');
    195 
    196     /* controls bottom-left corner */
    197 
    198     const attribution = L.control.attribution({
    199         position: 'bottomleft',
    200         prefix: false,
    201     });
    202     map.on('resize', function() {
    203         if (map.getSize().y > 567) {
    204             map.addControl(attribution);
    205             // Hack to keep control at the bottom of the map
    206             const container = attribution._container;
    207             const parent = container.parentElement;
    208             parent.appendChild(container);
    209         } else {
    210             map.removeControl(attribution);
    211         }
    212     });
    213     if (map.getSize().y > 567) {
    214         map.addControl(attribution);
    215     }
    216 
    217     const printControl = new L.Control.PrintPages({position: 'bottomleft'})
    218         .addTo(map)
    219         .enableHashState('p');
    220     if (
    221         minimizeControls.print === minimizeStateMinimized ||
    222         (minimizeControls.print === minimizeStateAuto && !printControl.hasPages())
    223     ) {
    224         printControl.setMinimized();
    225     }
    226 
    227     const jnxControl = new L.Control.JNX(layersControl, {position: 'bottomleft'})
    228         .addTo(map)
    229         .enableHashState('j');
    230 
    231     /* controls bottom-right corner */
    232 
    233     tracklist.addTo(map);
    234     const tracksHashParams = tracklist.hashParams();
    235 
    236     let hasTrackParamsInHash = false;
    237     for (let param of tracksHashParams) {
    238         if (hashState.hasKey(param)) {
    239             hasTrackParamsInHash = true;
    240             break;
    241         }
    242     }
    243 
    244     if (sessionsControl) {
    245         (async() => {
    246             await sessionsControl.loadSession();
    247             await sessionsControl.consumeSessionFromHash();
    248             if (await sessionsControl.importOldSessions() && !hasTrackParamsInHash) {
    249                 notify(
    250                     'If some tracks disappeared from the tracks list, ' +
    251                     'you can find them in the new list of recent sessions in the upper left corner.'
    252                 );
    253             }
    254         })();
    255     }
    256 
    257     if (hashState.hasKey('autoprofile') && hasTrackParamsInHash) {
    258         tracklist.once('loadedTracksFromParam', () => {
    259             const track = tracklist.tracks()[0];
    260             if (track) {
    261                 tracklist.showElevationProfileForTrack(track);
    262             }
    263         });
    264     }
    265 
    266     // This is not quite correct: minimizeControls should have effect only during loading, but the way it is
    267     // implemented, it will affect expanding when loading track from hash param during session.
    268     // But as parameter is expected to be found only when site is embedded using iframe,
    269     // the latter scenario is not very probable.
    270     if (minimizeControls.tracks !== minimizeStateMinimized) {
    271         tracklist.on('loadedTracksFromParam', () => tracklist.setExpanded());
    272     }
    273 
    274     for (let param of tracksHashParams) {
    275         bindHashStateReadOnly(param, tracklist.loadTrackFromParam.bind(tracklist, param));
    276     }
    277 
    278     /* set map position */
    279 
    280     if (!validPositionInHash) {
    281         if (hasTrackParamsInHash) {
    282             tracklist.whenLoadDone(() => tracklist.setViewToAllTracks(true));
    283         } else {
    284             locateControl.moveMapToCurrentLocation(config.defaultZoom);
    285         }
    286     }
    287 
    288     /* adaptive layout */
    289 
    290     if (
    291         minimizeControls.layers === minimizeStateAuto && L.Browser.mobile ||
    292         minimizeControls.layers === minimizeStateMinimized
    293     ) {
    294         layersControl.setMinimized();
    295     }
    296 
    297     if (L.Browser.mobile) {
    298         map.on('mousedown dragstart', () => layersControl.setMinimized());
    299     }
    300 
    301     if (
    302         minimizeControls.tracks === minimizeStateAuto && L.Browser.mobile && !tracklist.hasTracks() ||
    303         minimizeControls.tracks === minimizeStateMinimized
    304     ) {
    305         tracklist.setMinimized();
    306     }
    307 
    308     raiseControlsOnFocus(map);
    309 
    310     /* track list and azimuth measure interaction */
    311 
    312     tracklist.on('startedit', () => azimuthControl.disableControl());
    313     tracklist.on('elevation-shown', () => azimuthControl.hideProfile());
    314     azimuthControl.on('enabled', () => {
    315         tracklist.stopEditLine();
    316     });
    317     azimuthControl.on('elevation-shown', () => tracklist.hideElevationProfile());
    318 
    319     /* setup events logging */
    320 
    321     function getLayerLoggingInfo(layer) {
    322         if (layer.meta) {
    323             return {title: layer.meta.title};
    324         } else if (layer.__customLayer) {
    325             return {custom: true, title: layer.__customLayer.title, url: layer._url};
    326         }
    327         return null;
    328     }
    329 
    330     function getLatLngBoundsLoggingInfo(latLngBounds) {
    331         return {
    332             west: latLngBounds.getWest(),
    333             south: latLngBounds.getSouth(),
    334             east: latLngBounds.getEast(),
    335             north: latLngBounds.getNorth(),
    336         };
    337     }
    338 
    339     function getErrorLoggingInfo(error) {
    340         return error
    341             ? {
    342                   name: error.name,
    343                   message: error.message,
    344                   stack: error.stack,
    345               }
    346             : null;
    347     }
    348 
    349     function logUsedMaps() {
    350         const layers = [];
    351         map.eachLayer((layer) => {
    352             const layerInfo = getLayerLoggingInfo(layer);
    353             if (layerInfo) {
    354                 layers.push(layerInfo);
    355             }
    356         });
    357         const bounds = map.getBounds();
    358         logging.logEvent('activeLayers', {
    359             layers,
    360             view: getLatLngBoundsLoggingInfo(bounds),
    361         });
    362     }
    363 
    364     L.DomEvent.on(document, 'mousemove click touchend', L.Util.throttle(logUsedMaps, 30000));
    365 
    366     printControl.on('mapRenderEnd', function(e) {
    367         logging.logEvent('mapRenderEnd', {
    368             eventId: e.eventId,
    369             success: e.success,
    370             error: getErrorLoggingInfo(e.error),
    371         });
    372     });
    373 
    374     printControl.on('mapRenderStart', function(e) {
    375         const layers = [];
    376         map.eachLayer((layer) => {
    377             const layerInfo = getLayerLoggingInfo(layer);
    378             if (layer.options?.print && layerInfo) {
    379                 layers.push({
    380                     ...getLayerLoggingInfo(layer),
    381                     scaleDependent: layer.options.scaleDependent
    382                 });
    383             }
    384         });
    385         logging.logEvent('mapRenderStart', {
    386             eventId: e.eventId,
    387             action: e.action,
    388             scale: e.scale,
    389             resolution: e.resolution,
    390             pages: e.pages.map((page) => getLatLngBoundsLoggingInfo(page.latLngBounds)),
    391             zooms: e.zooms,
    392             layers
    393         });
    394     });
    395 
    396     jnxControl.on('tileExportStart', function(e) {
    397         logging.logEvent('tileExportStart', {
    398             eventId: e.eventId,
    399             layer: getLayerLoggingInfo(e.layer),
    400             zoom: e.zoom,
    401             bounds: getLatLngBoundsLoggingInfo(e.bounds),
    402         });
    403     });
    404 
    405     jnxControl.on('tileExportEnd', function(e) {
    406         logging.logEvent('tileExportEnd', {
    407             eventId: e.eventId,
    408             success: e.success,
    409             error: getErrorLoggingInfo(e.error),
    410         });
    411     });
    412 
    413     searchControl.on('resultreceived', function(e) {
    414         logging.logEvent('SearchProviderSelected', {
    415             provider: e.provider,
    416             query: e.query,
    417         });
    418         if (e.provider === 'Links' && e.result.error) {
    419             logging.logEvent('SearchLinkError', {
    420                 query: e.query,
    421                 result: e.result,
    422             });
    423         }
    424         if (e.provider === 'Coordinates') {
    425             logging.logEvent('SearchCoordinates', {
    426                 query: e.query,
    427                 result: e.result,
    428             });
    429         }
    430     });
    431 
    432     logging.logEvent('start', startInfo);
    433     logUsedMaps();
    434 }
    435 
    436 export {setUp};