Skip to content

Commit 2c43522

Browse files
authored
Merge pull request #42 from allmaps/develop
Added unlink option in compare mode
2 parents 4527c8b + eef46cf commit 2c43522

7 files changed

Lines changed: 200 additions & 30 deletions

File tree

config.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,8 @@ controls:
260260
followOrientation: Kaartoriëntatie volgen
261261
disableFocus: Kaartlaagfocus uitschakelen
262262
followFocus: Kaartlaagfocus volgen
263+
linkViews: Kaartbeelden koppelen
264+
unlinkViews: Kaartbeelden ontkoppelen
263265
opacity: Opacity
264266
adjustOpacity: Opacity aanpassen
265267
layerOpacity: Opacity kaartlaag

content/gouda/config.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,8 @@ controls:
259259
followOrientation: Kaartoriëntatie volgen
260260
disableFocus: Kaartlaagfocus uitschakelen
261261
followFocus: Kaartlaagfocus volgen
262+
linkViews: Kaartbeelden koppelen
263+
unlinkViews: Kaartbeelden ontkoppelen
262264
opacity: Opacity
263265
adjustOpacity: Opacity aanpassen
264266
layerOpacity: Opacity kaartlaag

src/lib/components/Map.svelte

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
GeocoderBounds,
1616
MapKeyboardCommand,
1717
MapLocation,
18+
MapLocationSyncCommand,
1819
MapToolbarCommand
1920
} from '$lib/types';
2021
@@ -69,6 +70,8 @@
6970
opacity = $bindable(viewState.opacity),
7071
rotateToMapOrientation = $bindable(false),
7172
focusActiveMap = $bindable(false),
73+
viewsLinked = $bindable(false),
74+
locationSyncCommand,
7275
inViewOnly = $bindable(false),
7376
currentLocation = $bindable({
7477
center: [...config.map.initialView.center] as [number, number],
@@ -88,7 +91,10 @@
8891
enableLocationMarker = false,
8992
navPosition = 'left',
9093
controlsPosition = 'top-right',
94+
showZoomControls = true,
95+
showLinkControl = false,
9196
showInViewControl = false,
97+
onLocationChange,
9298
autoplayActive = false,
9399
autoplayNextAnnotation,
94100
loaded = $bindable(false)
@@ -98,6 +104,8 @@
98104
opacity?: number;
99105
rotateToMapOrientation?: boolean;
100106
focusActiveMap?: boolean;
107+
viewsLinked?: boolean;
108+
locationSyncCommand?: MapLocationSyncCommand;
101109
inViewOnly?: boolean;
102110
currentLocation?: MapLocation;
103111
annotationsInView?: string[];
@@ -110,7 +118,10 @@
110118
enableLocationMarker?: boolean;
111119
navPosition?: 'left' | 'right';
112120
controlsPosition?: 'top-left' | 'top-right';
121+
showZoomControls?: boolean;
122+
showLinkControl?: boolean;
113123
showInViewControl?: boolean;
124+
onLocationChange?: (location: MapLocation) => void;
114125
autoplayActive?: boolean;
115126
autoplayNextAnnotation?: string;
116127
loaded?: boolean;
@@ -145,6 +156,8 @@
145156
let zoomLimitRetryTimer: ReturnType<typeof setTimeout> | undefined;
146157
let visibilityCheckFrame: number | undefined;
147158
let isSyncing = false;
159+
let previousLocationSyncCommandId = 0;
160+
let activeLocationSyncCommandId = 0;
148161
let warpedMapList = getWarpedMapList();
149162
let warpedMapLayer = new WarpedMapLayer({
150163
visible: false,
@@ -217,16 +230,48 @@
217230
218231
// Synchronize the map position with the bound location.
219232
$effect(() => {
220-
if (mapReady && map && currentLocation) {
221-
const center = currentLocation.center;
222-
const zoom = currentLocation.zoom;
223-
const bearing = currentLocation.bearing ?? 0;
224-
if (!mapMatchesLocation(center, zoom, bearing)) {
225-
clearPreferredSelectionZoom();
233+
if (!mapReady || !map || !currentLocation) return;
234+
235+
const pendingAnimatedSync =
236+
locationSyncCommand && locationSyncCommand.id !== previousLocationSyncCommandId
237+
? locationSyncCommand
238+
: undefined;
239+
240+
if (
241+
!pendingAnimatedSync &&
242+
activeLocationSyncCommandId &&
243+
locationSyncCommand?.id === activeLocationSyncCommandId
244+
) {
245+
return;
246+
}
247+
248+
const targetLocation = pendingAnimatedSync?.location ?? currentLocation;
249+
const center = targetLocation.center;
250+
const zoom = targetLocation.zoom;
251+
const bearing = targetLocation.bearing ?? 0;
252+
if (!mapMatchesLocation(center, zoom, bearing)) {
253+
clearPreferredSelectionZoom();
254+
255+
if (pendingAnimatedSync) {
256+
previousLocationSyncCommandId = pendingAnimatedSync.id;
257+
activeLocationSyncCommandId = pendingAnimatedSync.id;
258+
isSyncing = true;
259+
map.stop();
260+
map.once('moveend', () => {
261+
if (activeLocationSyncCommandId === pendingAnimatedSync.id) {
262+
activeLocationSyncCommandId = 0;
263+
}
264+
isSyncing = false;
265+
});
266+
map.flyTo({ center, zoom, bearing, pitch: 0, essential: true });
267+
} else {
226268
isSyncing = true;
227269
map.jumpTo({ center, zoom, bearing });
228270
isSyncing = false;
229271
}
272+
} else if (pendingAnimatedSync) {
273+
previousLocationSyncCommandId = pendingAnimatedSync.id;
274+
activeLocationSyncCommandId = 0;
230275
}
231276
});
232277
@@ -335,6 +380,7 @@
335380
if (!mapReady || !map || !command || command.id === previousKeyboardCommandId) return;
336381
337382
previousKeyboardCommandId = command.id;
383+
338384
let zoom = command.zoomDelta === undefined ? map.getZoom() : map.getZoom() + command.zoomDelta;
339385
340386
clearPreferredSelectionZoom();
@@ -1072,11 +1118,13 @@
10721118
10731119
mapInstance.on('move', () => {
10741120
if (!isSyncing) {
1075-
currentLocation = {
1121+
const nextLocation = {
10761122
center: mapInstance.getCenter().toArray() as [number, number],
10771123
zoom: mapInstance.getZoom(),
10781124
bearing: mapInstance.getBearing()
10791125
};
1126+
currentLocation = nextLocation;
1127+
onLocationChange?.(nextLocation);
10801128
}
10811129
10821130
if (visibilityWarningOpen) {
@@ -1160,10 +1208,13 @@
11601208
bind:opacity
11611209
bind:rotateToMapOrientation
11621210
bind:focusActiveMap
1211+
bind:viewsLinked
11631212
bind:inViewOnly
11641213
position={controlsPosition}
11651214
canZoomToMap={canZoomToActiveMap}
11661215
canFilterInView={annotationsInView.length > 0}
1216+
{showZoomControls}
1217+
{showLinkControl}
11671218
{showInViewControl}
11681219
onUserCameraAction={clearPreferredSelectionZoom}
11691220
/>

src/lib/components/MapControls.svelte

Lines changed: 62 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,33 @@
11
<script lang="ts">
2-
import { Compass, Focus, MapPinned, Minus, Plus, SlidersHorizontal } from '@lucide/svelte';
2+
import {
3+
Compass,
4+
Focus,
5+
Link,
6+
Unlink,
7+
MapPinned,
8+
Minus,
9+
Plus,
10+
SlidersHorizontal
11+
} from '@lucide/svelte';
312
import type { AppConfig } from '$lib/types';
413
import type maplibregl from 'maplibre-gl';
514
615
type ControlPosition = 'top-left' | 'top-right';
16+
const CONTROL_WIDTH_REM = 2.25;
717
818
let {
919
config,
1020
map,
1121
opacity = $bindable(100),
1222
rotateToMapOrientation = $bindable(false),
1323
focusActiveMap = $bindable(false),
24+
viewsLinked = $bindable(false),
1425
inViewOnly = $bindable(false),
1526
position = 'top-right',
1627
canZoomToMap = false,
1728
canFilterInView = false,
29+
showZoomControls = true,
30+
showLinkControl = false,
1831
showInViewControl = false,
1932
onUserCameraAction
2033
}: {
@@ -23,10 +36,13 @@
2336
opacity?: number;
2437
rotateToMapOrientation?: boolean;
2538
focusActiveMap?: boolean;
39+
viewsLinked?: boolean;
2640
inViewOnly?: boolean;
2741
position?: ControlPosition;
2842
canZoomToMap?: boolean;
2943
canFilterInView?: boolean;
44+
showZoomControls?: boolean;
45+
showLinkControl?: boolean;
3046
showInViewControl?: boolean;
3147
onUserCameraAction?: () => void;
3248
} = $props();
@@ -60,6 +76,14 @@
6076
}
6177
}
6278
]);
79+
let controlCount = $derived(
80+
(showZoomControls ? zoomControls.length : 0) +
81+
2 +
82+
(showLinkControl ? 1 : 0) +
83+
(showInViewControl ? 1 : 0) +
84+
1
85+
);
86+
let toolbarWidth = $derived(`${controlCount * CONTROL_WIDTH_REM}rem`);
6387
const controlButtonClass =
6488
'flex h-9 w-9 shrink-0 cursor-pointer items-center justify-center hover:bg-gray-100 focus-visible:z-10 focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-brand-main disabled:cursor-not-allowed disabled:text-gray-300 disabled:hover:bg-white';
6589
@@ -81,6 +105,10 @@
81105
focusActiveMap = !focusActiveMap;
82106
}
83107
108+
function toggleViewsLinked() {
109+
viewsLinked = !viewsLinked;
110+
}
111+
84112
function toggleInViewOnly() {
85113
if (!inViewOnly && !canFilterInView) return;
86114
@@ -108,20 +136,23 @@
108136
ondblclick={(event) => event.stopPropagation()}
109137
>
110138
<div
111-
class="inline-grid auto-cols-[2.25rem] grid-flow-col divide-x divide-gray-200 overflow-hidden rounded-md border border-gray-200 bg-white text-gray-800 shadow-lg"
139+
style:width={toolbarWidth}
140+
class="inline-grid auto-cols-[2.25rem] grid-flow-col divide-x divide-gray-200 overflow-hidden rounded-md border border-gray-200 bg-white text-gray-800 shadow-lg transition-[width] duration-200 ease-out"
112141
>
113-
{#each zoomControls as control (control.label)}
114-
{@const Icon = control.icon}
115-
<button
116-
type="button"
117-
aria-label={control.label}
118-
title={control.label}
119-
onclick={control.action}
120-
class={controlButtonClass}
121-
>
122-
<Icon class="h-4 w-4" />
123-
</button>
124-
{/each}
142+
{#if showZoomControls}
143+
{#each zoomControls as control (control.label)}
144+
{@const Icon = control.icon}
145+
<button
146+
type="button"
147+
aria-label={control.label}
148+
title={control.label}
149+
onclick={control.action}
150+
class={controlButtonClass}
151+
>
152+
<Icon class="h-4 w-4" />
153+
</button>
154+
{/each}
155+
{/if}
125156

126157
<button
127158
type="button"
@@ -151,6 +182,23 @@
151182
<Focus class="h-4 w-4" />
152183
</button>
153184

185+
{#if showLinkControl}
186+
<button
187+
type="button"
188+
aria-label={viewsLinked ? config.controls.unlinkViews : config.controls.linkViews}
189+
title={viewsLinked ? config.controls.unlinkViews : config.controls.linkViews}
190+
aria-pressed={viewsLinked}
191+
onclick={toggleViewsLinked}
192+
class={getToggleButtonClass(viewsLinked)}
193+
>
194+
{#if viewsLinked}
195+
<Link class="h-4 w-4" />
196+
{:else}
197+
<Unlink class="h-4 w-4" />
198+
{/if}
199+
</button>
200+
{/if}
201+
154202
{#if showInViewControl}
155203
<button
156204
type="button"

src/lib/components/MapPane.svelte

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
MapLayersOpenCommand,
1111
MapKeyboardCommand,
1212
MapLocation,
13+
MapLocationSyncCommand,
1314
MapMetadata,
1415
MapToolbarCommand,
1516
SliderKeyboardCommand
@@ -35,16 +36,20 @@
3536
mapToolbarCommand,
3637
paneSide = 'left',
3738
layersId = `map-layers-${navPosition}`,
38-
bordered = false,
3939
showMapYearTicks = true,
4040
showOnlyAvailableYears = config.slider.showOnlyAvailableYears ?? false,
4141
enableFlyTo = false,
4242
enableLocationMarker = false,
4343
enableLayersShortcut = false,
4444
showLayersPaneIndicator = false,
4545
showInViewControl = false,
46+
showZoomControls = true,
47+
showLinkControl = false,
4648
rotateToMapOrientation = $bindable(false),
4749
focusActiveMap = $bindable(false),
50+
viewsLinked = $bindable(false),
51+
locationSyncCommand,
52+
onLocationChange,
4853
autoplayActive = false,
4954
autoplayNextAnnotation,
5055
annotationsInView = $bindable<string[]>([]),
@@ -66,16 +71,20 @@
6671
mapToolbarCommand?: MapToolbarCommand;
6772
paneSide?: 'left' | 'right';
6873
layersId?: string;
69-
bordered?: boolean;
7074
showMapYearTicks?: boolean;
7175
showOnlyAvailableYears?: boolean;
7276
enableFlyTo?: boolean;
7377
enableLocationMarker?: boolean;
7478
enableLayersShortcut?: boolean;
7579
showLayersPaneIndicator?: boolean;
7680
showInViewControl?: boolean;
81+
showZoomControls?: boolean;
82+
showLinkControl?: boolean;
7783
rotateToMapOrientation?: boolean;
7884
focusActiveMap?: boolean;
85+
viewsLinked?: boolean;
86+
locationSyncCommand?: MapLocationSyncCommand;
87+
onLocationChange?: (location: MapLocation) => void;
7988
autoplayActive?: boolean;
8089
autoplayNextAnnotation?: string;
8190
annotationsInView?: string[];
@@ -103,11 +112,7 @@
103112
});
104113
</script>
105114

106-
<section
107-
class="map-pane relative flex min-h-0 min-w-0 flex-1 flex-row overflow-hidden {bordered
108-
? 'md:border-r-2 md:border-gray-300'
109-
: ''}"
110-
>
115+
<section class="map-pane relative flex min-h-0 min-w-0 flex-1 flex-row overflow-hidden">
111116
{#if !autoplayActive && mapLoaded}
112117
<div
113118
class="absolute inset-y-0 z-20 flex-none {navPosition === 'right' ? 'right-0' : 'left-0'}"
@@ -136,6 +141,8 @@
136141
bind:opacity
137142
bind:rotateToMapOrientation
138143
bind:focusActiveMap
144+
bind:viewsLinked
145+
{locationSyncCommand}
139146
bind:inViewOnly={sliderInViewOnly}
140147
bind:currentLocation
141148
bind:annotationsInView
@@ -151,6 +158,9 @@
151158
{navPosition}
152159
{controlsPosition}
153160
{showInViewControl}
161+
{showZoomControls}
162+
{showLinkControl}
163+
{onLocationChange}
154164
{autoplayActive}
155165
{autoplayNextAnnotation}
156166
/>

0 commit comments

Comments
 (0)