Skip to content

Commit 192c71b

Browse files
committed
experimenting with toggleCompare componment
1 parent aebe446 commit 192c71b

File tree

8 files changed

+298
-24
lines changed

8 files changed

+298
-24
lines changed

web/components.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,6 @@ declare module 'vue' {
3232
RecursiveTable: typeof import('./src/components/RecursiveTable.vue')['default']
3333
SideBars: typeof import('./src/components/sidebars/SideBars.vue')['default']
3434
SliderNumericInput: typeof import('./src/components/SliderNumericInput.vue')['default']
35+
ToggleCompareMap: typeof import('./src/components/map/ToggleCompareMap.vue')['default']
3536
}
3637
}

web/package-lock.json

Lines changed: 41 additions & 21 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

web/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
"sortablejs": "^1.15.0",
3030
"vue": "^3.2.13",
3131
"vue-chartjs": "^5.2.0",
32-
"vue-maplibre-compare": "^1.0.13",
32+
"vue-maplibre-compare": "file:../../vue-maplibre-compare",
3333
"vuedraggable": "^4.1.0",
3434
"vuetify": "^3.8.0"
3535
},

web/src/App.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import { watch, onMounted, computed } from "vue";
33
import { oauthClient } from "./api/auth";
44
import MapWrapper from "./components/map/MapWrapper.vue";
5+
import ToggleCompareMap from "./components/map/ToggleCompareMap.vue";
56
import SideBars from "./components/sidebars/SideBars.vue";
67
import ControlsBar from "./components/ControlsBar.vue";
78
@@ -71,7 +72,7 @@ watch(() => appStore.currentUser, onReady);
7172
@mousemove="panelStore.dragPanel"
7273
@mouseup="panelStore.stopDrag"
7374
>
74-
<MapWrapper />
75+
<ToggleCompareMap />
7576
<SideBars />
7677
<ControlsBar />
7778
</div>

web/src/components/map/MapWrapper.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import { useTheme } from 'vuetify';
1414
const mapStore = useMapStore();
1515
const compareStore = useMapCompareStore();
1616
const appStore = useAppStore();
17-
const layerStore = useLayerStore();
1817
1918
2019
const transformRequest = (url: string, _resourceType?: ResourceType) => {
Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
<!-- eslint-disable vue/multi-word-component-names -->
2+
<script setup lang="ts">
3+
import { useAppStore, useLayerStore, useMapStore } from "@/store";
4+
import { useMapCompareStore } from "@/store/compare";
5+
import { computed, onMounted, Ref, ref, watch } from "vue";
6+
import { ToggleCompare } from "vue-maplibre-compare";
7+
import { oauthClient } from "@/api/auth";
8+
import 'vue-maplibre-compare/dist/vue-maplibre-compare.css'
9+
import { addProtocol, AttributionControl, Popup } from "maplibre-gl";
10+
import type { StyleSpecification, Map, ResourceType } from "maplibre-gl";
11+
import { baseURL } from "@/api/auth";
12+
import { useTheme } from 'vuetify';
13+
import { Protocol } from "pmtiles";
14+
import { THEMES } from "@/themes";
15+
16+
const ATTRIBUTION = [
17+
"<a target='_blank' href='https://maplibre.org/'>© MapLibre</a>",
18+
"<span> | </span>",
19+
"<a target='_blank' href='https://www.openstreetmap.org/copyright'>© OpenStreetMap</a>",
20+
];
21+
22+
addProtocol("pmtiles", new Protocol().tile);
23+
24+
const appStore = useAppStore();
25+
const mapStore = useMapStore();
26+
const compareStore = useMapCompareStore();
27+
const layerStore = useLayerStore();
28+
29+
// MapLibre refs
30+
const tooltip = ref<HTMLElement>();
31+
const attributionControl = new AttributionControl({
32+
compact: true,
33+
customAttribution: ATTRIBUTION,
34+
});
35+
attributionControl.onAdd = (map: Map): HTMLElement => {
36+
attributionControl._map = map;
37+
const container = document.createElement("div");
38+
container.innerHTML = ATTRIBUTION.join("");
39+
attributionControl._container = container;
40+
setAttributionControlStyle();
41+
return container;
42+
};
43+
44+
function setAttributionControlStyle() {
45+
const container = attributionControl._container;
46+
container.style.padding = "3px 8px";
47+
container.style.marginRight = "5px";
48+
container.style.borderRadius = "15px";
49+
container.style.position = "relative";
50+
container.style.right = appStore.openSidebars.includes("right")
51+
? "360px"
52+
: "0px";
53+
container.style.background = appStore.theme === "light" ? "white" : "black";
54+
container.childNodes.forEach((child) => {
55+
const childElement = child as HTMLElement;
56+
childElement.style.color = appStore.theme === "light" ? "black" : "white";
57+
});
58+
}
59+
60+
const handleMapReady = (newMap: Map) => {
61+
console.log("Compare maps ready");
62+
newMap.addControl(attributionControl);
63+
newMap.on('error', (response) => {
64+
// AbortErrors are raised when updating style of raster layers; ignore these
65+
if (response.error.message !== 'AbortError') console.error(response.error)
66+
});
67+
68+
/**
69+
* This is called on every click, and technically hides the tooltip on every click.
70+
* However, if a feature layer is clicked, that event is fired after this one, and the
71+
* tooltip is re-enabled and rendered with the desired contents. The net result is that
72+
* this only has a real effect when the base map is clicked, as that means that no other
73+
* feature layer can "catch" the event, and the tooltip stays hidden.
74+
*/
75+
newMap.on("click", () => {mapStore.clickedFeature = undefined});
76+
mapStore.map = newMap;
77+
mapStore.setMapCenter(undefined, true);
78+
createMapControls();
79+
newMap.once('idle', () => {
80+
//layerStore.updateLayersShown();
81+
});
82+
83+
}
84+
85+
86+
function createMapControls() {
87+
if (!mapStore.map || !tooltip.value) {
88+
throw new Error("Map or refs not initialized!");
89+
}
90+
91+
// Add tooltip overlay
92+
const popup = new Popup({
93+
anchor: "bottom-left",
94+
closeOnClick: false,
95+
maxWidth: "none",
96+
closeButton: true,
97+
});
98+
99+
// Link overlay ref to dom, allowing for modification elsewhere
100+
popup.setDOMContent(tooltip.value);
101+
102+
// Set store value
103+
mapStore.tooltipOverlay = popup;
104+
}
105+
106+
107+
watch(() => appStore.theme, () => {
108+
if (!mapStore.map) return;
109+
const map = mapStore.getMap();
110+
map.setStyle(THEMES[appStore.theme].mapStyle);
111+
setAttributionControlStyle();
112+
//layerStore.updateLayersShown();
113+
});
114+
115+
watch(() => appStore.openSidebars, () => {
116+
setAttributionControlStyle();
117+
});
118+
119+
const transformRequest = (url: string, _resourceType?: ResourceType) => {
120+
// Only add auth headers to our own tile requests
121+
if (url.startsWith(baseURL)) {
122+
return {
123+
url,
124+
headers: oauthClient?.authHeaders,
125+
};
126+
}
127+
return { url };
128+
}
129+
130+
const computedCompare = computed(() => compareStore.isComparing);
131+
const mapStats = computed(() => compareStore.mapStats);
132+
const mapStyleA: Ref<StyleSpecification> = ref(THEMES[appStore.theme].mapStyle);
133+
watch(computedCompare, (newVal) => {
134+
if (!newVal && mapStore.map) {
135+
mapStore.getMap()?.jumpTo({
136+
center: mapStats.value?.center,
137+
zoom: mapStats.value?.zoom,
138+
bearing: mapStats.value?.bearing,
139+
pitch: mapStats.value?.pitch,
140+
});
141+
} else if (newVal) {
142+
mapStyleA.value = mapStore.getMap()!.getStyle();
143+
}
144+
});
145+
146+
watch(() => compareStore.mapAStyle, (newStyle) => {
147+
if (compareStore.isComparing && mapStore.map) {
148+
mapStyleA.value = newStyle;
149+
}
150+
}, { deep: true});
151+
152+
153+
154+
const mapStyleB = computed(() => compareStore.mapBStyle);
155+
const mapLayersA = computed(() => compareStore.mapLayersA);
156+
const mapLayersB = computed(() => compareStore.mapLayersB);
157+
158+
const swiperColor = computed(() => {
159+
const theme = useTheme();
160+
return theme.global.current.value.colors.primary
161+
});
162+
</script>
163+
164+
<template>
165+
<div>
166+
<ToggleCompare
167+
:map-style-a="mapStyleA"
168+
:map-style-b="mapStyleB"
169+
:map-layers-a="mapLayersA"
170+
:map-layers-b="mapLayersB"
171+
:compare-enabled="compareStore.isComparing"
172+
:camera="{
173+
center: mapStats.center,
174+
zoom: mapStats.zoom,
175+
}"
176+
:transform-request="transformRequest"
177+
:swiper-options="{
178+
darkMode: appStore.theme !== 'dark',
179+
orientation: compareStore.orientation,
180+
grabThickness: 20,
181+
lineColor: swiperColor,
182+
handleColor: swiperColor
183+
}"
184+
layer-order="bottommost"
185+
@panend="compareStore.updateMapStats($event)"
186+
@zoomend="compareStore.updateMapStats($event)"
187+
@pitchend="compareStore.updateMapStats($event)"
188+
@rotateend="compareStore.updateMapStats($event)"
189+
@map-ready="handleMapReady"
190+
class="map"
191+
/>
192+
193+
<div id="map-tooltip" ref="tooltip" class="tooltip pa-0">
194+
<MapTooltip />
195+
</div>
196+
</div>
197+
</template>
198+
199+
<style scoped>
200+
.map {
201+
height: 100%;
202+
width: 100%;
203+
position: relative;
204+
}
205+
206+
@keyframes spinner {
207+
to {
208+
transform: rotate(360deg);
209+
}
210+
}
211+
212+
.spinner:after {
213+
content: "";
214+
box-sizing: border-box;
215+
position: absolute;
216+
top: 50%;
217+
left: 50%;
218+
width: 40px;
219+
height: 40px;
220+
margin-top: -20px;
221+
margin-left: -20px;
222+
border-radius: 50%;
223+
border: 5px solid rgba(180, 180, 180, 0.6);
224+
border-top-color: rgba(0, 0, 0, 0.6);
225+
animation: spinner 0.6s linear infinite;
226+
}
227+
228+
.tooltip {
229+
border-radius: 5px;
230+
padding: 10px 20px;
231+
word-break: break-word;
232+
text-wrap: wrap;
233+
width: fit-content;
234+
min-width: 50px;
235+
max-width: 350px;
236+
}
237+
238+
.base-layer-control {
239+
float: right;
240+
position: absolute;
241+
top: 2%;
242+
right: 2%;
243+
z-index: 2;
244+
}
245+
</style>

0 commit comments

Comments
 (0)