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:
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;
+}