nakarte

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

App.js (14895B)


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