nakarte

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

commit 707dd92447b601a88c07e0256cc0cc5a608199b7
parent ef27dfade22ea6f096f47640785a73646fa27e8a
Author: Sergey Orlov <wladimirych@gmail.com>
Date:   Tue,  5 May 2020 21:44:11 +0200

panoramas: enable to split horizontally and adjust viewer size

Related to #16

Diffstat:
Msrc/App.css | 14+-------------
Msrc/App.js | 5+++--
Msrc/index.html | 1-
Asrc/lib/anyElementResizeEvent/index.js | 20++++++++++++++++++++
Msrc/lib/leaflet.control.panoramas/index.js | 106+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Asrc/lib/leaflet.control.panoramas/slider-button.svg | 13+++++++++++++
Msrc/lib/leaflet.control.panoramas/style.css | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/leaflet.map.sidebars/index.js | 34++++++++++++++++++++++++++++++++++
Asrc/lib/leaflet.map.sidebars/style.css | 33+++++++++++++++++++++++++++++++++
9 files changed, 257 insertions(+), 23 deletions(-)

diff --git a/src/App.css b/src/App.css @@ -2,17 +2,6 @@ height: 100%; } -#street-view { - width: 50%; - height: 100%; - float: left; - display: none; -} - -#street-view.enabled { - display: block; -} - *:focus { outline: 0; -} -\ No newline at end of file +} diff --git a/src/App.js b/src/App.js @@ -2,6 +2,7 @@ import './App.css'; import './leaflet-fixes.css'; import L from 'leaflet'; import 'leaflet/dist/leaflet.css'; +import {MapWithSidebars} from '~/lib/leaflet.map.sidebars'; import '~/lib/leaflet.control.printPages/control'; import '~/lib/leaflet.control.caption'; import config from './config'; @@ -46,7 +47,7 @@ function setUp() { }; fixAll(); - const map = L.map('map', { + const map = new MapWithSidebars('map', { zoomControl: false, fadeAnimation: false, attributionControl: false, @@ -75,7 +76,7 @@ function setUp() { new L.Control.TrackList.Ruler(tracklist).addTo(map); - const panoramas = new L.Control.Panoramas(document.getElementById('street-view')) + const panoramas = new L.Control.Panoramas() .addTo(map) .enableHashState('n2'); L.Control.Panoramas.hashStateUpgrader(panoramas).enableHashState('n'); diff --git a/src/index.html b/src/index.html @@ -8,7 +8,6 @@ <base target="_blank"> </head> <body> - <div id="street-view"></div> <div id="map"></div> </body> </html> diff --git a/src/lib/anyElementResizeEvent/index.js b/src/lib/anyElementResizeEvent/index.js @@ -0,0 +1,20 @@ +function onElementResize(element, cb) { + if (window.ResizeObserver) { + const observer = new ResizeObserver(() => cb()); + observer.observe(element); + } else { + let width = element.offsetWidth; + let height = element.offsetHeight; + setInterval(function() { + const newWidth = element.offsetWidth; + const newHeight = element.offsetHeight; + if (newWidth !== width || newHeight !== height) { + width = newWidth; + height = newHeight; + cb(); + } + }, 200); + } +} + +export {onElementResize}; diff --git a/src/lib/leaflet.control.panoramas/index.js b/src/lib/leaflet.control.panoramas/index.js @@ -7,6 +7,9 @@ import '~/lib/controls-styles/controls-styles.css'; import {makeButtonWithBar} from '~/lib/leaflet.control.commons'; import mapillaryProvider from './lib/mapillary'; import wikimediaProvider from './lib/wikimedia'; +import {DragEvents} from '~/lib/leaflet.events.drag'; +import {onElementResize} from '~/lib/anyElementResizeEvent'; +import safeLocalStorage from '~/lib/safe-localstorage'; function fireRefreshEventOnWindow() { const evt = document.createEvent("HTMLEvents"); @@ -52,7 +55,10 @@ L.Control.Panoramas = L.Control.extend({ includes: L.Mixin.Events, options: { - position: 'topleft' + position: 'topleft', + splitVerically: true, + splitSizeFraction: 0.5, + minViewerSize: 30, }, getProviders: function() { @@ -87,19 +93,48 @@ L.Control.Panoramas = L.Control.extend({ ]; }, - initialize: function(panoramaContainer, options) { + initialize: function(options) { L.Control.prototype.initialize.call(this, options); - this._panoramaContainer = panoramaContainer; + this.loadSettings(); + this._panoramasContainer = L.DomUtil.create('div', 'panoramas-container'); + onElementResize(this._panoramasContainer, fireRefreshEventOnWindow); this.providers = this.getProviders(); for (let provider of this.providers) { provider.selected.subscribe(this.updateCoverageVisibility, this); - provider.container = L.DomUtil.create('div', 'panorama-container', panoramaContainer); + provider.container = L.DomUtil.create('div', 'panorama-container', this._panoramasContainer); } this.nearbyPoints = []; }, + loadSettings: function() { + let storedSettings; + try { + storedSettings = JSON.parse(safeLocalStorage.panoramaSettings); + } catch { + // ignore + } + this._splitVerically = storedSettings?.spitVertically ?? this.options.splitVerically; + const fraction = storedSettings?.splitSizeFraction; + this._splitSizeFraction = isNaN(fraction) ? this.options.splitSizeFraction : fraction; + }, + + saveSetting: function() { + safeLocalStorage.panoramaSettings = JSON.stringify({ + spitVertically: this._splitVerically, + splitSizeFraction: this._splitSizeFraction + }); + }, + onAdd: function(map) { this._map = map; + + const splitter = L.DomUtil.create('div', 'panorama-splitter', this._panoramasContainer); + L.DomUtil.create('div', 'button', splitter); + new DragEvents(splitter, null, {trackOutsideElement: true}).on({ + drag: this.onSplitterDrag, + click: this.onSplitterClick + }, this); + this.setupViewerLayout(); const {container, link, barContainer} = makeButtonWithBar( 'leaflet-contol-panoramas', 'Show panoramas (Alt-P)', 'icon-panoramas'); this._container = container; @@ -120,6 +155,28 @@ L.Control.Panoramas = L.Control.extend({ return container; }, + onSplitterDrag: function(e) { + const minSize = this.options.minViewerSize; + const container = this._panoramasContainer; + const oldSize = container[this._splitVerically ? 'offsetWidth' : 'offsetHeight']; + let newSize = oldSize + e.dragMovement[this._splitVerically ? 'x' : 'y']; + const mapSize = this._map._container[this._splitVerically ? 'offsetWidth' : 'offsetHeight']; + if (newSize < minSize) { + newSize = this.options.minViewerSize; + } + const maxSize = oldSize + mapSize - minSize; + if (newSize > maxSize) { + newSize = maxSize; + } + this.setContainerSizePixels(newSize); + }, + + onSplitterClick: function() { + this._splitVerically = !this._splitVerically; + this.saveSetting(); + this.setupViewerLayout(); + }, + onButtonClick: function() { this.switchControl(); }, @@ -184,12 +241,12 @@ L.Control.Panoramas = L.Control.extend({ }, showPanoramaContainer: function() { - L.DomUtil.addClass(this._panoramaContainer, 'enabled'); + L.DomUtil.addClass(this._panoramasContainer, 'enabled'); fireRefreshEventOnWindow(); }, panoramaVisible: function() { - if (L.DomUtil.hasClass(this._panoramaContainer, 'enabled')) { + if (L.DomUtil.hasClass(this._panoramasContainer, 'enabled')) { for (let provider of this.providers) { if (L.DomUtil.hasClass(provider.container, 'enabled')) { return provider; @@ -252,7 +309,7 @@ L.Control.Panoramas = L.Control.extend({ for (let provider of this.providers) { this.hidePano(provider); } - L.DomUtil.removeClass(this._panoramaContainer, 'enabled'); + L.DomUtil.removeClass(this._panoramasContainer, 'enabled'); this.hideMarker(); fireRefreshEventOnWindow(); this.notifyChanged(); @@ -315,6 +372,41 @@ L.Control.Panoramas = L.Control.extend({ return; } } + }, + + setupViewerLayout: function() { + let sidebar; + if (this._splitVerically) { + L.DomUtil.addClass(this._panoramasContainer, 'split-vertical'); + L.DomUtil.removeClass(this._panoramasContainer, 'split-horizontal'); + sidebar = 'left'; + this._panoramasContainer.style.height = '100%'; + } else { + L.DomUtil.addClass(this._panoramasContainer, 'split-horizontal'); + L.DomUtil.removeClass(this._panoramasContainer, 'split-vertical'); + sidebar = 'top'; + this._panoramasContainer.style.width = '100%'; + } + this._map.addElementToSidebar(sidebar, this._panoramasContainer); + this.updateContainerSize(); + }, + + setContainerSizePixels: function(size) { + this._panoramasContainer.style[this._splitVerically ? 'width' : 'height'] = `${size}px`; + setTimeout(() => { // map size has not updated yet + const mapSize = this._map._container[this._splitVerically ? 'offsetWidth' : 'offsetHeight']; + this._splitSizeFraction = size / (mapSize + size); + this.saveSetting(); + }, 0); + }, + + updateContainerSize: function() { + const fraction = this._splitSizeFraction; + const container = this._panoramasContainer; + const containerSize = container[this._splitVerically ? 'offsetWidth' : 'offsetHeight']; + const mapSize = this._map._container[this._splitVerically ? 'offsetWidth' : 'offsetHeight']; + const newSize = fraction * (mapSize + containerSize); + container.style[this._splitVerically ? 'width' : 'height'] = `${newSize}px`; } }, ); diff --git a/src/lib/leaflet.control.panoramas/slider-button.svg b/src/lib/leaflet.control.panoramas/slider-button.svg @@ -0,0 +1,12 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="51" height="21"> +<g style="stroke: #222222; stroke-width: 1"> + <rect stroke="#888" fill="#efefef" x="0.5" y="0.5" width="50" height="20" rx="7" ry="7"/> + + <path d="M7,7.5L23,7.5"/> + <path d="M7,10.5L23,10.5"/> + <path d="M7,13.5L23,13.5"/> + + <rect fill="#8ae9e9" x="30.5" y="4.5" width="6" height="11"/> + <rect fill="#abecab" x="36.5" y="4.5" width="6" height="11"/> +</g> +</svg> +\ No newline at end of file diff --git a/src/lib/leaflet.control.panoramas/style.css b/src/lib/leaflet.control.panoramas/style.css @@ -1,3 +1,57 @@ +.panoramas-container { + display: none; +} + +.panoramas-container.enabled { + display: flex; +} + +.panoramas-container.split-horizontal { + flex-direction: column; +} + +.panoramas-container.split-vertical { + flex-direction: row; +} + +.panorama-splitter { + background-color: black; + display: flex; + justify-content: center; +} + +.split-vertical .panorama-splitter { + flex-direction: column; + width: 3px; + cursor: col-resize; +} + +.split-horizontal .panorama-splitter { + flex-direction: row; + height: 3px; + cursor: row-resize; +} + +.panorama-splitter .button { + width: 51px; + height: 21px; + position: absolute; + z-index: 20000; + background-image: url("slider-button.svg"); + background-size: 51px 21px; + user-select: none; + cursor: pointer; +} + +.split-vertical .panorama-splitter .button { + margin-left: 13px; + transform: rotate(90deg); + transform-origin: 0 0; +} +.split-horizontal .panorama-splitter .button { + margin-top: -9px; +} + .icon-panoramas { background-image: url('panoramas-off.png'); background-size: 17px 17px !important; diff --git a/src/lib/leaflet.map.sidebars/index.js b/src/lib/leaflet.map.sidebars/index.js @@ -0,0 +1,34 @@ +import L from 'leaflet'; +import './style.css'; +import {onElementResize} from '~/lib/anyElementResizeEvent'; + +const MapWithSidebars = L.Map.extend({ + initialize: function(id, options) { + this._sidebarsContainer = L.DomUtil.get(id); + this.setupSidebarsLayout(this._sidebarsContainer); + L.Map.prototype.initialize.call(this, this._mapContainer, options); + onElementResize(this._mapContainer, this.invalidateSize.bind(this)); + }, + + setupSidebarsLayout: function(container) { + const sidebars = this._sidebarContainers = {}; + L.DomUtil.addClass(container, 'leaflet-map-sidebars-container'); + sidebars['left'] = L.DomUtil.create('div', 'leaflet-map-sidebar-left', this._sidebarsContainer); + const midColumn = L.DomUtil.create('div', 'leaflet-map-sidebar-mid-column', this._sidebarsContainer); + sidebars['right'] = L.DomUtil.create('div', 'leaflet-map-sidebar-right', this._sidebarsContainer); + sidebars['top'] = L.DomUtil.create('div', 'leaflet-map-sidebar-top', midColumn); + this._mapContainer = L.DomUtil.create('div', 'leaflet-map-container-with-sidebars', midColumn); + sidebars['bottom'] = L.DomUtil.create('div', 'leaflet-map-sidebar-bottom', midColumn); + }, + + addElementToSidebar: function(name, element) { + this._sidebarContainers[name].appendChild(element); + }, + + removeElementFromSidebar: function(name, element) { + this._sidebarContainers[name].removeChild(element); + } + +}); + +export {MapWithSidebars}; diff --git a/src/lib/leaflet.map.sidebars/style.css b/src/lib/leaflet.map.sidebars/style.css @@ -0,0 +1,33 @@ +.leaflet-map-sidebars-container { + display: flex; + flex-direction: row; +} + +.leaflet-map-sidebar-left, +.leaflet-map-sidebar-right, +.leaflet-map-sidebar-top, +.leaflet-map-sidebar-bottom { + display: flex; +} + +.leaflet-map-sidebar-mid-column { + display: flex; + flex-direction: column; + flex-basis: 100%; +} + +.leaflet-map-container-with-sidebars { + flex-basis: 100%; +} + +.leaflet-map-sidebar-left { + flex-direction: row-reverse; +} + +.leaflet-map-sidebar-right { + flex-direction: row; +} + +.leaflet-map-sidebar-top { + flex-direction: column-reverse; +}