maplibre-preview 1.7.2 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/lib/maplibre-preview/public/js/overlay_layout.js +415 -0
- data/lib/maplibre-preview/public/js/tilegrid.js +9 -0
- data/lib/maplibre-preview/version.rb +1 -1
- data/lib/maplibre-preview/views/maplibre_layout.slim +38 -5
- data/lib/maplibre-preview/views/maplibre_map.slim +87 -4
- data/spec/maplibre_preview_spec.rb +10 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 61c7dd8ecf3753aa43ba5272cc06dab258cbb643824e74336e21e07680795cd6
|
|
4
|
+
data.tar.gz: 6a008af2ffe1b2f3331dc752ab36041767299cc38271f9f9433567f268952198
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c75eede1968d1ddfaa8c388de5c827bf6f8e27f5bcc628938aca1c61f7ccf2896cb70f9a498f6262f352a4c1a211881a87ac09133da2445cd3e6c02f6fa42aac
|
|
7
|
+
data.tar.gz: 435d1192991bacd4730d547eb3f580f99c8d287fabb29e221432a44b309c6b73e25c198f6abea453e0a9ab0ba5d4de6b83adff1fcd4e68cbd73ec03c67e9f22e
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.8.0] - 2026-05-14
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **Movable overlay windows** - added `OverlayLayoutManager` for shared drag, edge clamping, snapping and persisted positions of movable map UI panels
|
|
7
|
+
- **Window layout reset** - added `Reset window layout` action in Map Settings to clear saved panel positions from local storage and restore defaults
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
- **Panel layout foundation** - moved Map Settings, Style Controls, Style Parameters, Performance, Elevation Profile and Tile Boundaries to the shared overlay layout manager
|
|
11
|
+
- **Default panel positions** - kept static map controls attached to map edges and placed Map Settings in the top-left default position
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- **Overlay visibility** - fixed managed panel z-index so map settings, filter controls and static MapLibre controls remain visible
|
|
15
|
+
- **Elevation profile frame** - fixed profile window positioning and sizing while dragging near the bottom edge
|
|
16
|
+
|
|
3
17
|
## [1.7.2] - 2026-05-14
|
|
4
18
|
|
|
5
19
|
### Changed
|
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
class OverlayLayoutManager {
|
|
2
|
+
constructor(options = {}) {
|
|
3
|
+
this.storageKey = options.storageKey || 'maplibre-preview:overlay-layout:v3';
|
|
4
|
+
this.snapThreshold = options.snapThreshold || 32;
|
|
5
|
+
this.mobileBreakpoint = options.mobileBreakpoint || 768;
|
|
6
|
+
this.edgeGap = options.edgeGap || 10;
|
|
7
|
+
this.getReservedBounds = options.getReservedBounds || (() => ({}));
|
|
8
|
+
this.panels = new Map();
|
|
9
|
+
this.state = this.loadState();
|
|
10
|
+
this.drag = null;
|
|
11
|
+
this.zIndex = 1100;
|
|
12
|
+
this.resizeHandler = () => this.refreshBounds();
|
|
13
|
+
window.addEventListener('resize', this.resizeHandler);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
registerPanel(config) {
|
|
17
|
+
const element = this.resolveElement(config.element);
|
|
18
|
+
if (!element) return null;
|
|
19
|
+
|
|
20
|
+
const id = config.id || element.id;
|
|
21
|
+
if (!id) return null;
|
|
22
|
+
|
|
23
|
+
const existing = this.panels.get(id);
|
|
24
|
+
if (existing) this.unregisterPanel(id);
|
|
25
|
+
|
|
26
|
+
const panel = {
|
|
27
|
+
id,
|
|
28
|
+
element,
|
|
29
|
+
handle: this.resolveHandle(element, config.handleSelector),
|
|
30
|
+
defaultAnchor: config.defaultAnchor || 'left',
|
|
31
|
+
defaultOffset: config.defaultOffset || {x: 0, y: 0},
|
|
32
|
+
snap: config.snap !== false,
|
|
33
|
+
lockSizeOnDrag: config.lockSizeOnDrag === true,
|
|
34
|
+
movable: config.movable !== false,
|
|
35
|
+
cleanup: []
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
element.dataset.overlayPanelId = id;
|
|
39
|
+
element.classList.add('overlay-managed-panel');
|
|
40
|
+
this.panels.set(id, panel);
|
|
41
|
+
|
|
42
|
+
if (panel.movable && panel.handle) {
|
|
43
|
+
this.prepareHandle(panel);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
this.applyStoredOrDefaultPosition(panel);
|
|
47
|
+
return panel;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
unregisterPanel(id) {
|
|
51
|
+
const panel = this.panels.get(id);
|
|
52
|
+
if (!panel) return;
|
|
53
|
+
|
|
54
|
+
panel.cleanup.forEach(fn => fn());
|
|
55
|
+
panel.element.classList.remove('overlay-managed-panel', 'overlay-dragging');
|
|
56
|
+
panel.element.removeAttribute('data-overlay-panel-id');
|
|
57
|
+
this.panels.delete(id);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
movePanelTo(id, anchor) {
|
|
61
|
+
const panel = this.panels.get(id);
|
|
62
|
+
if (!panel) return;
|
|
63
|
+
|
|
64
|
+
const nextState = {mode: 'anchor', anchor, offset: {x: 0, y: 0}};
|
|
65
|
+
this.setPanelState(panel, nextState);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
resetPanel(id) {
|
|
69
|
+
const panel = this.panels.get(id);
|
|
70
|
+
if (!panel) return;
|
|
71
|
+
|
|
72
|
+
delete this.state[id];
|
|
73
|
+
this.persistState();
|
|
74
|
+
this.applyStoredOrDefaultPosition(panel);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
resetLayout() {
|
|
78
|
+
this.state = {};
|
|
79
|
+
this.clearStoredState();
|
|
80
|
+
this.panels.forEach(panel => this.applyStoredOrDefaultPosition(panel));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
refreshPanel(id) {
|
|
84
|
+
const panel = this.panels.get(id);
|
|
85
|
+
if (!panel) return;
|
|
86
|
+
if (this.drag?.panel?.id === id) return;
|
|
87
|
+
|
|
88
|
+
const panelState = this.state[id] || this.defaultState(panel);
|
|
89
|
+
this.applyPanelState(panel, panelState);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
refreshBounds() {
|
|
93
|
+
if (this.drag) return;
|
|
94
|
+
this.panels.forEach(panel => this.refreshPanel(panel.id));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
destroy() {
|
|
98
|
+
window.removeEventListener('resize', this.resizeHandler);
|
|
99
|
+
[...this.panels.keys()].forEach(id => this.unregisterPanel(id));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
resolveElement(elementOrSelector) {
|
|
103
|
+
if (!elementOrSelector) return null;
|
|
104
|
+
return typeof elementOrSelector === 'string'
|
|
105
|
+
? document.querySelector(elementOrSelector)
|
|
106
|
+
: elementOrSelector;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
resolveHandle(element, selector) {
|
|
110
|
+
return selector ? element.querySelector(selector) : element;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
prepareHandle(panel) {
|
|
114
|
+
const pointerDown = event => this.onPointerDown(event, panel);
|
|
115
|
+
const keyDown = event => this.onHandleKeyDown(event, panel);
|
|
116
|
+
|
|
117
|
+
panel.handle.classList.add('overlay-panel-handle');
|
|
118
|
+
panel.handle.tabIndex = panel.handle.tabIndex >= 0 ? panel.handle.tabIndex : 0;
|
|
119
|
+
if (!panel.handle.querySelector('button, input, select, textarea, a')) {
|
|
120
|
+
panel.handle.setAttribute('role', panel.handle.getAttribute('role') || 'button');
|
|
121
|
+
}
|
|
122
|
+
panel.handle.setAttribute('aria-label', panel.handle.getAttribute('aria-label') || `Move ${panel.id} panel`);
|
|
123
|
+
panel.handle.addEventListener('pointerdown', pointerDown);
|
|
124
|
+
panel.handle.addEventListener('keydown', keyDown);
|
|
125
|
+
panel.cleanup.push(() => panel.handle.removeEventListener('pointerdown', pointerDown));
|
|
126
|
+
panel.cleanup.push(() => panel.handle.removeEventListener('keydown', keyDown));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
onPointerDown(event, panel) {
|
|
130
|
+
if (event.button !== 0 || !panel.movable || this.isInteractiveTarget(event.target)) return;
|
|
131
|
+
|
|
132
|
+
const rect = panel.element.getBoundingClientRect();
|
|
133
|
+
const pointerMove = moveEvent => this.onPointerMove(moveEvent);
|
|
134
|
+
const pointerUp = upEvent => this.onPointerUp(upEvent);
|
|
135
|
+
|
|
136
|
+
if (panel.lockSizeOnDrag) {
|
|
137
|
+
panel.element.style.width = `${Math.round(rect.width)}px`;
|
|
138
|
+
panel.element.style.height = `${Math.round(rect.height)}px`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
this.drag = {
|
|
142
|
+
panel,
|
|
143
|
+
pointerId: event.pointerId,
|
|
144
|
+
startX: event.clientX,
|
|
145
|
+
startY: event.clientY,
|
|
146
|
+
originX: rect.left,
|
|
147
|
+
originY: rect.top,
|
|
148
|
+
pointerMove,
|
|
149
|
+
pointerUp
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
panel.element.classList.add('overlay-dragging');
|
|
153
|
+
this.bringToFront(panel);
|
|
154
|
+
try {
|
|
155
|
+
panel.handle.setPointerCapture?.(event.pointerId);
|
|
156
|
+
} catch (e) {
|
|
157
|
+
// Some synthetic pointer events do not create an active pointer capture target.
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
panel.handle.addEventListener('pointermove', pointerMove);
|
|
161
|
+
panel.handle.addEventListener('pointerup', pointerUp);
|
|
162
|
+
panel.handle.addEventListener('pointercancel', pointerUp);
|
|
163
|
+
event.preventDefault();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
onPointerMove(event) {
|
|
167
|
+
if (!this.drag || event.pointerId !== this.drag.pointerId) return;
|
|
168
|
+
|
|
169
|
+
const nextX = this.drag.originX + event.clientX - this.drag.startX;
|
|
170
|
+
const nextY = this.drag.originY + event.clientY - this.drag.startY;
|
|
171
|
+
const position = this.clampPosition(this.drag.panel, nextX, nextY);
|
|
172
|
+
|
|
173
|
+
this.applyPosition(this.drag.panel, position.x, position.y);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
onPointerUp(event) {
|
|
177
|
+
if (!this.drag || event.pointerId !== this.drag.pointerId) return;
|
|
178
|
+
|
|
179
|
+
const panel = this.drag.panel;
|
|
180
|
+
const rect = panel.element.getBoundingClientRect();
|
|
181
|
+
const nextState = this.stateFromPosition(panel, rect.left, rect.top);
|
|
182
|
+
this.cleanupDrag();
|
|
183
|
+
this.setPanelState(panel, nextState);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
cleanupDrag() {
|
|
187
|
+
if (!this.drag) return null;
|
|
188
|
+
|
|
189
|
+
const drag = this.drag;
|
|
190
|
+
try {
|
|
191
|
+
drag.panel.handle.releasePointerCapture?.(drag.pointerId);
|
|
192
|
+
} catch (e) {
|
|
193
|
+
// Pointer capture may already be released by the browser.
|
|
194
|
+
}
|
|
195
|
+
drag.panel.handle.removeEventListener('pointermove', drag.pointerMove);
|
|
196
|
+
drag.panel.handle.removeEventListener('pointerup', drag.pointerUp);
|
|
197
|
+
drag.panel.handle.removeEventListener('pointercancel', drag.pointerUp);
|
|
198
|
+
drag.panel.element.classList.remove('overlay-dragging');
|
|
199
|
+
this.drag = null;
|
|
200
|
+
return drag;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
onHandleKeyDown(event, panel) {
|
|
204
|
+
if (event.key === 'Escape' && this.drag) {
|
|
205
|
+
this.cleanupDrag();
|
|
206
|
+
this.refreshPanel(panel.id);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (!event.shiftKey || !['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(event.key)) return;
|
|
211
|
+
|
|
212
|
+
const rect = panel.element.getBoundingClientRect();
|
|
213
|
+
const step = event.altKey ? 32 : 8;
|
|
214
|
+
const delta = {
|
|
215
|
+
ArrowLeft: [-step, 0],
|
|
216
|
+
ArrowRight: [step, 0],
|
|
217
|
+
ArrowUp: [0, -step],
|
|
218
|
+
ArrowDown: [0, step]
|
|
219
|
+
}[event.key];
|
|
220
|
+
const position = this.clampPosition(panel, rect.left + delta[0], rect.top + delta[1]);
|
|
221
|
+
|
|
222
|
+
this.setPanelState(panel, {mode: 'free', x: position.x, y: position.y});
|
|
223
|
+
event.preventDefault();
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
isInteractiveTarget(target) {
|
|
227
|
+
return target?.closest?.('button, input, select, textarea, a, label, [data-overlay-ignore-drag="true"]');
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
bringToFront(panel) {
|
|
231
|
+
this.zIndex += 1;
|
|
232
|
+
panel.element.style.zIndex = String(this.zIndex);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
loadState() {
|
|
236
|
+
try {
|
|
237
|
+
return JSON.parse(localStorage.getItem(this.storageKey) || '{}');
|
|
238
|
+
} catch (e) {
|
|
239
|
+
console.warn('OverlayLayoutManager: could not load layout state', e);
|
|
240
|
+
return {};
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
persistState() {
|
|
245
|
+
try {
|
|
246
|
+
localStorage.setItem(this.storageKey, JSON.stringify(this.state));
|
|
247
|
+
} catch (e) {
|
|
248
|
+
console.warn('OverlayLayoutManager: could not persist layout state', e);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
clearStoredState() {
|
|
253
|
+
try {
|
|
254
|
+
localStorage.removeItem(this.storageKey);
|
|
255
|
+
} catch (e) {
|
|
256
|
+
console.warn('OverlayLayoutManager: could not clear layout state', e);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
defaultState(panel) {
|
|
261
|
+
const offset = typeof panel.defaultOffset === 'function'
|
|
262
|
+
? panel.defaultOffset(panel)
|
|
263
|
+
: panel.defaultOffset;
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
mode: 'anchor',
|
|
267
|
+
anchor: panel.defaultAnchor,
|
|
268
|
+
offset: {...offset}
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
applyStoredOrDefaultPosition(panel) {
|
|
273
|
+
const panelState = this.state[panel.id] || this.defaultState(panel);
|
|
274
|
+
this.applyPanelState(panel, panelState);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
setPanelState(panel, panelState) {
|
|
278
|
+
this.state[panel.id] = panelState;
|
|
279
|
+
this.persistState();
|
|
280
|
+
this.applyPanelState(panel, panelState);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
applyPanelState(panel, panelState) {
|
|
284
|
+
const responsiveState = this.isConstrainedViewport() && panelState.mode === 'free'
|
|
285
|
+
? this.defaultState(panel)
|
|
286
|
+
: panelState;
|
|
287
|
+
const position = responsiveState.mode === 'free'
|
|
288
|
+
? this.clampPosition(panel, responsiveState.x, responsiveState.y)
|
|
289
|
+
: this.positionForAnchor(panel, responsiveState.anchor, responsiveState.offset || {x: 0, y: 0});
|
|
290
|
+
|
|
291
|
+
this.applyPosition(panel, position.x, position.y);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
applyPosition(panel, x, y) {
|
|
295
|
+
panel.element.style.position = 'fixed';
|
|
296
|
+
panel.element.style.left = `${Math.round(x)}px`;
|
|
297
|
+
panel.element.style.top = `${Math.round(y)}px`;
|
|
298
|
+
panel.element.style.right = 'auto';
|
|
299
|
+
panel.element.style.bottom = 'auto';
|
|
300
|
+
panel.element.style.transform = 'none';
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
positionForAnchor(panel, anchor, offset) {
|
|
304
|
+
const rect = this.panelRect(panel);
|
|
305
|
+
const viewport = this.viewportBounds();
|
|
306
|
+
const centerX = (viewport.width - rect.width) / 2;
|
|
307
|
+
const centerY = (viewport.height - viewport.reservedBottom - rect.height) / 2;
|
|
308
|
+
let x = centerX;
|
|
309
|
+
let y = centerY;
|
|
310
|
+
|
|
311
|
+
if (anchor === 'left') {
|
|
312
|
+
x = this.edgeGap + (offset.x || 0);
|
|
313
|
+
y = centerY + (offset.y || 0);
|
|
314
|
+
} else if (anchor === 'right') {
|
|
315
|
+
x = viewport.width - rect.width - this.edgeGap + (offset.x || 0);
|
|
316
|
+
y = centerY + (offset.y || 0);
|
|
317
|
+
} else if (anchor === 'top') {
|
|
318
|
+
x = centerX + (offset.x || 0);
|
|
319
|
+
y = this.edgeGap + (offset.y || 0);
|
|
320
|
+
} else if (anchor === 'bottom') {
|
|
321
|
+
x = centerX + (offset.x || 0);
|
|
322
|
+
y = viewport.height - viewport.reservedBottom - rect.height - this.edgeGap + (offset.y || 0);
|
|
323
|
+
} else if (anchor === 'top-left') {
|
|
324
|
+
x = this.edgeGap + (offset.x || 0);
|
|
325
|
+
y = this.edgeGap + (offset.y || 0);
|
|
326
|
+
} else if (anchor === 'top-right') {
|
|
327
|
+
x = viewport.width - rect.width - this.edgeGap + (offset.x || 0);
|
|
328
|
+
y = this.edgeGap + (offset.y || 0);
|
|
329
|
+
} else if (anchor === 'bottom-left') {
|
|
330
|
+
x = this.edgeGap + (offset.x || 0);
|
|
331
|
+
y = viewport.height - viewport.reservedBottom - rect.height - this.edgeGap + (offset.y || 0);
|
|
332
|
+
} else if (anchor === 'bottom-right') {
|
|
333
|
+
x = viewport.width - rect.width - this.edgeGap + (offset.x || 0);
|
|
334
|
+
y = viewport.height - viewport.reservedBottom - rect.height - this.edgeGap + (offset.y || 0);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return this.clampPosition(panel, x, y);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
stateFromPosition(panel, x, y) {
|
|
341
|
+
if (this.isConstrainedViewport()) {
|
|
342
|
+
return {mode: 'anchor', anchor: this.nearestAnchor(panel, x, y).anchor, offset: {x: 0, y: 0}};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const nearest = this.nearestAnchor(panel, x, y);
|
|
346
|
+
if (!panel.snap || nearest.distance > this.snapThreshold) {
|
|
347
|
+
const clamped = this.clampPosition(panel, x, y);
|
|
348
|
+
return {mode: 'free', x: clamped.x, y: clamped.y};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const rect = this.panelRect(panel);
|
|
352
|
+
const viewport = this.viewportBounds();
|
|
353
|
+
const centerX = (viewport.width - rect.width) / 2;
|
|
354
|
+
const centerY = (viewport.height - viewport.reservedBottom - rect.height) / 2;
|
|
355
|
+
const offset = {x: 0, y: 0};
|
|
356
|
+
|
|
357
|
+
if (nearest.anchor === 'left' || nearest.anchor === 'right') {
|
|
358
|
+
offset.y = y - centerY;
|
|
359
|
+
} else {
|
|
360
|
+
offset.x = x - centerX;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return {mode: 'anchor', anchor: nearest.anchor, offset};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
nearestAnchor(panel, x, y) {
|
|
367
|
+
const rect = this.panelRect(panel);
|
|
368
|
+
const viewport = this.viewportBounds();
|
|
369
|
+
const distances = [
|
|
370
|
+
['left', x],
|
|
371
|
+
['right', viewport.width - (x + rect.width)],
|
|
372
|
+
['top', y],
|
|
373
|
+
['bottom', viewport.height - viewport.reservedBottom - (y + rect.height)]
|
|
374
|
+
].map(([anchor, distance]) => ({anchor, distance: Math.abs(distance)}));
|
|
375
|
+
|
|
376
|
+
return distances.sort((a, b) => a.distance - b.distance)[0];
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
clampPosition(panel, x, y) {
|
|
380
|
+
const rect = this.panelRect(panel);
|
|
381
|
+
const viewport = this.viewportBounds();
|
|
382
|
+
const minX = this.edgeGap;
|
|
383
|
+
const minY = this.edgeGap;
|
|
384
|
+
const maxX = Math.max(minX, viewport.width - rect.width - this.edgeGap);
|
|
385
|
+
const maxY = Math.max(minY, viewport.height - viewport.reservedBottom - rect.height - this.edgeGap);
|
|
386
|
+
|
|
387
|
+
return {
|
|
388
|
+
x: Math.min(maxX, Math.max(minX, x)),
|
|
389
|
+
y: Math.min(maxY, Math.max(minY, y))
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
panelRect(panel) {
|
|
394
|
+
const rect = panel.element.getBoundingClientRect();
|
|
395
|
+
return {
|
|
396
|
+
width: rect.width || panel.element.offsetWidth || 1,
|
|
397
|
+
height: rect.height || panel.element.offsetHeight || 1
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
viewportBounds() {
|
|
402
|
+
const reserved = this.getReservedBounds() || {};
|
|
403
|
+
return {
|
|
404
|
+
width: window.innerWidth,
|
|
405
|
+
height: window.innerHeight,
|
|
406
|
+
reservedBottom: Math.max(0, Number(reserved.bottom || 0))
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
isConstrainedViewport() {
|
|
411
|
+
return window.innerWidth < this.mobileBreakpoint;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
window.OverlayLayoutManager = OverlayLayoutManager;
|
|
@@ -44,6 +44,13 @@ class TileGridManager {
|
|
|
44
44
|
`;
|
|
45
45
|
|
|
46
46
|
document.getElementById('map-container').appendChild(this.panelContainer);
|
|
47
|
+
window.overlayLayoutManager?.registerPanel({
|
|
48
|
+
id: 'tilegrid',
|
|
49
|
+
element: this.panelContainer,
|
|
50
|
+
handleSelector: '.tilegrid-title',
|
|
51
|
+
defaultAnchor: 'right',
|
|
52
|
+
defaultOffset: {x: 0, y: 0}
|
|
53
|
+
});
|
|
47
54
|
}
|
|
48
55
|
|
|
49
56
|
setupEventListeners() {
|
|
@@ -114,6 +121,7 @@ class TileGridManager {
|
|
|
114
121
|
|
|
115
122
|
if (this.panelContainer) {
|
|
116
123
|
this.panelContainer.style.display = this.isVisible ? 'block' : 'none';
|
|
124
|
+
this.isVisible && window.overlayLayoutManager?.refreshPanel('tilegrid');
|
|
117
125
|
}
|
|
118
126
|
|
|
119
127
|
const btn = document.getElementById('tilegrid-mode-btn');
|
|
@@ -163,6 +171,7 @@ class TileGridManager {
|
|
|
163
171
|
}
|
|
164
172
|
|
|
165
173
|
if (this.panelContainer && this.panelContainer.parentNode) {
|
|
174
|
+
window.overlayLayoutManager?.unregisterPanel('tilegrid');
|
|
166
175
|
this.panelContainer.parentNode.removeChild(this.panelContainer);
|
|
167
176
|
}
|
|
168
177
|
|
|
@@ -10,6 +10,7 @@ html
|
|
|
10
10
|
script[src="#{base_path}/vendor/maplibre-gl/maplibre-gl.js"]
|
|
11
11
|
script[src="#{base_path}/vendor/maplibre-contour/index.min.js"]
|
|
12
12
|
script[src="#{base_path}/vendor/d3/d3.v7.min.js"]
|
|
13
|
+
script[src="#{base_path}/js/overlay_layout.js"]
|
|
13
14
|
script[src="#{base_path}/js/filters.js"]
|
|
14
15
|
script[src="#{base_path}/js/contour.js"]
|
|
15
16
|
script[src="#{base_path}/js/tilegrid.js"]
|
|
@@ -174,7 +175,7 @@ html
|
|
|
174
175
|
| .controls a { color: #6897bb; text-decoration: none; margin-right: 15px; }
|
|
175
176
|
| .controls a:hover { text-decoration: underline; }
|
|
176
177
|
| .layer-controls-wrapper {
|
|
177
|
-
| position:
|
|
178
|
+
| position: static; z-index: 1000;
|
|
178
179
|
| display: flex; align-items: flex-start; gap: 0;
|
|
179
180
|
| }
|
|
180
181
|
| .layer-controls {
|
|
@@ -204,6 +205,22 @@ html
|
|
|
204
205
|
| .control-section-header {
|
|
205
206
|
| display: flex; align-items: center; justify-content: space-between; gap: 8px;
|
|
206
207
|
| }
|
|
208
|
+
| .overlay-managed-panel {
|
|
209
|
+
| z-index: 1000; will-change: left, top;
|
|
210
|
+
| }
|
|
211
|
+
| .overlay-dragging {
|
|
212
|
+
| cursor: grabbing;
|
|
213
|
+
| user-select: none;
|
|
214
|
+
| transition: none !important;
|
|
215
|
+
| }
|
|
216
|
+
| .overlay-panel-handle {
|
|
217
|
+
| cursor: grab; touch-action: none; user-select: none;
|
|
218
|
+
| -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none;
|
|
219
|
+
| }
|
|
220
|
+
| .overlay-panel-handle:focus-visible {
|
|
221
|
+
| outline: 1px solid #6897bb;
|
|
222
|
+
| outline-offset: 2px;
|
|
223
|
+
| }
|
|
207
224
|
| .control-section-title {
|
|
208
225
|
| color: #ffc66d; font-size: 11px; font-weight: bold; line-height: 1.2;
|
|
209
226
|
| text-transform: uppercase;
|
|
@@ -330,13 +347,21 @@ html
|
|
|
330
347
|
| color: #a9b7c6; overflow: hidden;
|
|
331
348
|
| }
|
|
332
349
|
| .style-parameters-header {
|
|
333
|
-
| display:
|
|
350
|
+
| display: flex; align-items: center; gap: 12px;
|
|
334
351
|
| width: 100%; background: transparent; color: inherit; border: none;
|
|
335
|
-
| padding: 10px 12px;
|
|
352
|
+
| padding: 10px 12px; text-align: left;
|
|
336
353
|
| }
|
|
337
354
|
| .style-parameters-header:hover {
|
|
338
355
|
| background: rgba(75, 77, 79, 0.95);
|
|
339
356
|
| }
|
|
357
|
+
| .style-parameters-toggle {
|
|
358
|
+
| display: inline-flex; align-items: center; justify-content: center;
|
|
359
|
+
| width: 22px; height: 22px; background: transparent; color: #a9b7c6;
|
|
360
|
+
| border: 1px solid transparent; border-radius: 2px; cursor: pointer;
|
|
361
|
+
| }
|
|
362
|
+
| .style-parameters-toggle:hover {
|
|
363
|
+
| background: #4b4d4f; border-color: #555555;
|
|
364
|
+
| }
|
|
340
365
|
| .style-parameters-title {
|
|
341
366
|
| color: #ffc66d; font-size: 11px; font-weight: bold; line-height: 1.2;
|
|
342
367
|
| text-transform: uppercase;
|
|
@@ -487,6 +512,10 @@ html
|
|
|
487
512
|
| transition: width 0.3s ease; width: 0%;
|
|
488
513
|
| }
|
|
489
514
|
| /* MapLibre Controls */
|
|
515
|
+
| .maplibregl-ctrl-top-left, .maplibregl-ctrl-top-right,
|
|
516
|
+
| .maplibregl-ctrl-bottom-left, .maplibregl-ctrl-bottom-right {
|
|
517
|
+
| z-index: 1000 !important;
|
|
518
|
+
| }
|
|
490
519
|
| .maplibregl-ctrl-scale, .maplibregl-ctrl-group, .maplibregl-ctrl-terrain, .maplibregl-ctrl-globe {
|
|
491
520
|
| background: rgba(60, 63, 65, 0.95) !important; border: 1px solid #555555 !important;
|
|
492
521
|
| border-radius: 4px !important;
|
|
@@ -512,10 +541,12 @@ html
|
|
|
512
541
|
| background: rgba(60, 63, 65, 0.95); border: 1px solid #555555;
|
|
513
542
|
| border-radius: 6px; padding: 12px; width: 70vw; max-width: 800px;
|
|
514
543
|
| backdrop-filter: blur(10px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
|
544
|
+
| overflow: hidden;
|
|
515
545
|
| }
|
|
516
546
|
| .profile-header {
|
|
517
|
-
| display: flex; justify-content:
|
|
547
|
+
| display: flex; justify-content: flex-start; align-items: center;
|
|
518
548
|
| margin-bottom: 8px; border-bottom: 1px solid #555555; padding-bottom: 8px;
|
|
549
|
+
| gap: 8px;
|
|
519
550
|
| }
|
|
520
551
|
| .profile-title { color: #ffc66d; font-weight: bold; font-size: 12px; }
|
|
521
552
|
| .profile-close {
|
|
@@ -523,6 +554,7 @@ html
|
|
|
523
554
|
| font-size: 14px; padding: 0; width: 16px; height: 16px;
|
|
524
555
|
| display: flex; align-items: center; justify-content: center;
|
|
525
556
|
| border-radius: 2px; transition: all 0.2s ease;
|
|
557
|
+
| margin-left: auto;
|
|
526
558
|
| }
|
|
527
559
|
| .profile-close:hover { background: #4b4d4f; color: #a9b7c6; }
|
|
528
560
|
| .profile-stats {
|
|
@@ -567,7 +599,7 @@ html
|
|
|
567
599
|
| box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
|
568
600
|
| }
|
|
569
601
|
| .tilegrid-header {
|
|
570
|
-
| display: flex; justify-content:
|
|
602
|
+
| display: flex; justify-content: flex-start; align-items: center; gap: 8px;
|
|
571
603
|
| margin-bottom: 12px; padding-bottom: 8px; border-bottom: 1px solid #555555;
|
|
572
604
|
| }
|
|
573
605
|
| .tilegrid-title {
|
|
@@ -578,6 +610,7 @@ html
|
|
|
578
610
|
| font-size: 14px; padding: 0; width: 16px; height: 16px;
|
|
579
611
|
| display: flex; align-items: center; justify-content: center;
|
|
580
612
|
| border-radius: 2px; transition: all 0.2s ease;
|
|
613
|
+
| margin-left: auto;
|
|
581
614
|
| }
|
|
582
615
|
| .tilegrid-close:hover {
|
|
583
616
|
| background: #4b4d4f; color: #a9b7c6;
|
|
@@ -35,6 +35,7 @@ ruby:
|
|
|
35
35
|
span.setting-value id="terrain-exaggeration-value" 1.0x
|
|
36
36
|
input.setting-range id="terrain-exaggeration-slider" type="range" min="0" max="3" step="0.1" value="1" oninput="setTerrainExaggeration(this.value)"
|
|
37
37
|
button.control-button onclick="toggleAntialias()" id="antialias-btn" Antialias
|
|
38
|
+
button.control-button onclick="resetOverlayLayout()" id="overlay-layout-reset-btn" Reset window layout
|
|
38
39
|
|
|
39
40
|
#settings-debug-panel.control-panel.settings-panel
|
|
40
41
|
button.control-button onclick="toggleMapCache()" id="map-cache-btn" Cache
|
|
@@ -78,12 +79,13 @@ ruby:
|
|
|
78
79
|
#map.map-layer data-style-url="#{style_url}"
|
|
79
80
|
|
|
80
81
|
#style-parameters-panel.style-parameters-overlay.collapsed style="display: none;"
|
|
81
|
-
|
|
82
|
+
.style-parameters-header
|
|
82
83
|
span.style-parameters-title Style parameters
|
|
83
84
|
span.style-parameters-summary
|
|
84
85
|
span#style-parameters-count 0
|
|
85
86
|
span params
|
|
86
|
-
|
|
87
|
+
button.style-parameters-toggle type="button" onclick="toggleStyleParametersPanel()" id="style-parameters-toggle" aria-expanded="false" title="Collapse/Expand Style parameters"
|
|
88
|
+
span.style-parameters-toggle-icon ▲
|
|
87
89
|
.style-parameters-body
|
|
88
90
|
#style-parameter-fields.style-parameter-fields
|
|
89
91
|
.style-parameter-actions
|
|
@@ -150,6 +152,7 @@ javascript:
|
|
|
150
152
|
let currentProfile = null;
|
|
151
153
|
let contourManager = null;
|
|
152
154
|
let tileGridManager = null;
|
|
155
|
+
let overlayLayoutManager = null;
|
|
153
156
|
let originalStyle = null;
|
|
154
157
|
let styleParameterDefinitions = new Map();
|
|
155
158
|
let styleParameterValues = {};
|
|
@@ -328,6 +331,7 @@ javascript:
|
|
|
328
331
|
});
|
|
329
332
|
|
|
330
333
|
layoutBottomOverlays();
|
|
334
|
+
overlayLayoutManager?.refreshPanel('style-parameters');
|
|
331
335
|
};
|
|
332
336
|
|
|
333
337
|
const toggleStyleParametersPanel = () => {
|
|
@@ -338,6 +342,7 @@ javascript:
|
|
|
338
342
|
const isCollapsed = panel.classList.toggle('collapsed');
|
|
339
343
|
toggle?.setAttribute('aria-expanded', String(!isCollapsed));
|
|
340
344
|
layoutBottomOverlays();
|
|
345
|
+
overlayLayoutManager?.refreshPanel('style-parameters');
|
|
341
346
|
};
|
|
342
347
|
|
|
343
348
|
const initializeStyleParameters = async (style) => {
|
|
@@ -392,7 +397,7 @@ javascript:
|
|
|
392
397
|
|
|
393
398
|
const patchRequestUrl = (resourceUrl) => shouldPatchRequestUrl(resourceUrl) ? appendStyleParametersToUrl(resourceUrl) : resourceUrl;
|
|
394
399
|
|
|
395
|
-
const BOTTOM_OVERLAY_IDS = ['
|
|
400
|
+
const BOTTOM_OVERLAY_IDS = ['loading-indicator'];
|
|
396
401
|
const BOTTOM_OVERLAY_BASE_OFFSET = 20;
|
|
397
402
|
const BOTTOM_OVERLAY_GAP = 12;
|
|
398
403
|
|
|
@@ -419,6 +424,69 @@ javascript:
|
|
|
419
424
|
element.style.bottom = `${bottomOffset}px`;
|
|
420
425
|
bottomOffset += element.offsetHeight + BOTTOM_OVERLAY_GAP;
|
|
421
426
|
});
|
|
427
|
+
overlayLayoutManager?.refreshBounds();
|
|
428
|
+
});
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
const getSystemReservedBottom = () => {
|
|
432
|
+
const loading = document.getElementById('loading-indicator');
|
|
433
|
+
return isVisibleBottomOverlay(loading) ? loading.offsetHeight + BOTTOM_OVERLAY_BASE_OFFSET + BOTTOM_OVERLAY_GAP : 0;
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
const leftControlStackOffset = (sectionName) => {
|
|
437
|
+
const mapSettings = document.getElementById('map-settings-wrapper');
|
|
438
|
+
const styleControls = document.getElementById('style-controls-wrapper');
|
|
439
|
+
const gap = 12;
|
|
440
|
+
const mapHeight = mapSettings?.getBoundingClientRect().height || mapSettings?.offsetHeight || 0;
|
|
441
|
+
const styleHeight = styleControls?.getBoundingClientRect().height || styleControls?.offsetHeight || 0;
|
|
442
|
+
|
|
443
|
+
return {
|
|
444
|
+
x: 0,
|
|
445
|
+
y: sectionName === 'map-settings' ? -((styleHeight + gap) / 2) : ((mapHeight + gap) / 2)
|
|
446
|
+
};
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
const initializeOverlayLayout = () => {
|
|
450
|
+
if (!window.OverlayLayoutManager) return;
|
|
451
|
+
|
|
452
|
+
overlayLayoutManager?.destroy();
|
|
453
|
+
overlayLayoutManager = new OverlayLayoutManager({
|
|
454
|
+
storageKey: 'maplibre-preview:overlay-layout:v3',
|
|
455
|
+
snapThreshold: 32,
|
|
456
|
+
mobileBreakpoint: 768,
|
|
457
|
+
edgeGap: 10,
|
|
458
|
+
getReservedBounds: () => ({bottom: getSystemReservedBottom()})
|
|
459
|
+
});
|
|
460
|
+
window.overlayLayoutManager = overlayLayoutManager;
|
|
461
|
+
window.resetOverlayLayout = () => overlayLayoutManager.resetLayout();
|
|
462
|
+
|
|
463
|
+
overlayLayoutManager.registerPanel({
|
|
464
|
+
id: 'map-settings',
|
|
465
|
+
element: '#map-settings-wrapper',
|
|
466
|
+
handleSelector: '.control-section-title',
|
|
467
|
+
defaultAnchor: 'top-left',
|
|
468
|
+
defaultOffset: {x: 0, y: 0}
|
|
469
|
+
});
|
|
470
|
+
overlayLayoutManager.registerPanel({
|
|
471
|
+
id: 'style-controls',
|
|
472
|
+
element: '#style-controls-wrapper',
|
|
473
|
+
handleSelector: '.control-section-title',
|
|
474
|
+
defaultAnchor: 'left',
|
|
475
|
+
defaultOffset: () => leftControlStackOffset('style-controls')
|
|
476
|
+
});
|
|
477
|
+
overlayLayoutManager.registerPanel({
|
|
478
|
+
id: 'style-parameters',
|
|
479
|
+
element: '#style-parameters-panel',
|
|
480
|
+
handleSelector: '.style-parameters-title',
|
|
481
|
+
defaultAnchor: 'bottom',
|
|
482
|
+
defaultOffset: {x: 0, y: 0}
|
|
483
|
+
});
|
|
484
|
+
overlayLayoutManager.registerPanel({
|
|
485
|
+
id: 'performance',
|
|
486
|
+
element: '#performance-panel',
|
|
487
|
+
handleSelector: '.performance-content',
|
|
488
|
+
defaultAnchor: 'top',
|
|
489
|
+
defaultOffset: {x: 0, y: -10}
|
|
422
490
|
});
|
|
423
491
|
};
|
|
424
492
|
|
|
@@ -648,6 +716,7 @@ javascript:
|
|
|
648
716
|
});
|
|
649
717
|
filters.init();
|
|
650
718
|
createLayerButtons();
|
|
719
|
+
overlayLayoutManager?.refreshBounds();
|
|
651
720
|
} catch (e) {
|
|
652
721
|
console.warn('Filter initialization failed:', e);
|
|
653
722
|
}
|
|
@@ -947,7 +1016,19 @@ javascript:
|
|
|
947
1016
|
|
|
948
1017
|
[header, stats, chart].forEach(el => overlay.appendChild(el));
|
|
949
1018
|
document.getElementById('map-container').appendChild(overlay);
|
|
950
|
-
|
|
1019
|
+
overlayLayoutManager?.registerPanel({
|
|
1020
|
+
id: 'profile',
|
|
1021
|
+
element: overlay,
|
|
1022
|
+
handleSelector: '.profile-title',
|
|
1023
|
+
defaultAnchor: 'bottom',
|
|
1024
|
+
defaultOffset: {x: 0, y: -16},
|
|
1025
|
+
snap: false,
|
|
1026
|
+
lockSizeOnDrag: true
|
|
1027
|
+
});
|
|
1028
|
+
setTimeout(() => {
|
|
1029
|
+
drawSimpleProfileChart(profile);
|
|
1030
|
+
overlayLayoutManager?.refreshPanel('profile');
|
|
1031
|
+
}, 10);
|
|
951
1032
|
layoutBottomOverlays();
|
|
952
1033
|
};
|
|
953
1034
|
|
|
@@ -1025,6 +1106,7 @@ javascript:
|
|
|
1025
1106
|
};
|
|
1026
1107
|
|
|
1027
1108
|
const hideProfile = () => {
|
|
1109
|
+
overlayLayoutManager?.unregisterPanel('profile');
|
|
1028
1110
|
document.getElementById('profile-overlay')?.remove();
|
|
1029
1111
|
layoutBottomOverlays();
|
|
1030
1112
|
map.getLayer('profile-line') && (map.removeLayer('profile-line'), map.removeSource('profile-line'));
|
|
@@ -1491,4 +1573,5 @@ javascript:
|
|
|
1491
1573
|
window.tileGridManager = null;
|
|
1492
1574
|
window.addEventListener('resize', layoutBottomOverlays);
|
|
1493
1575
|
|
|
1576
|
+
initializeOverlayLayout();
|
|
1494
1577
|
initializeMap();
|
|
@@ -14,6 +14,7 @@ RSpec.describe MapLibrePreview do
|
|
|
14
14
|
expect(last_response.body).to include('maplibre-gl')
|
|
15
15
|
expect(last_response.body).to include('maplibre-contour')
|
|
16
16
|
expect(last_response.body).to include('d3')
|
|
17
|
+
expect(last_response.body).to include('overlay_layout')
|
|
17
18
|
end
|
|
18
19
|
|
|
19
20
|
it 'renders map cache toggle wiring' do
|
|
@@ -38,6 +39,13 @@ RSpec.describe MapLibrePreview do
|
|
|
38
39
|
expect(last_response.body).to include('id="style-parameter-fields"')
|
|
39
40
|
expect(last_response.body).to include('id="style-parameters-apply"')
|
|
40
41
|
expect(last_response.body).to include('id="style-parameters-reset"')
|
|
42
|
+
expect(last_response.body).to include('OverlayLayoutManager')
|
|
43
|
+
expect(last_response.body).to include('overlayLayoutManager')
|
|
44
|
+
expect(last_response.body).to include('resetOverlayLayout')
|
|
45
|
+
expect(last_response.body).to include('id="overlay-layout-reset-btn"')
|
|
46
|
+
expect(last_response.body).to include('Reset window layout')
|
|
47
|
+
expect(last_response.body).to include('maplibre-preview:overlay-layout:v3')
|
|
48
|
+
expect(last_response.body).not_to include('overlay-panel-dock-actions')
|
|
41
49
|
expect(last_response.body).to include('mapCacheDisabled')
|
|
42
50
|
expect(last_response.body).to include('basemapOpacity')
|
|
43
51
|
expect(last_response.body).to include('terrainExaggeration')
|
|
@@ -60,7 +68,7 @@ RSpec.describe MapLibrePreview do
|
|
|
60
68
|
end
|
|
61
69
|
|
|
62
70
|
it 'serves all required JavaScript modules' do
|
|
63
|
-
%w[/js/filters.js /js/contour.js /js/tilegrid.js /vendor/maplibre-gl/maplibre-gl.js /vendor/maplibre-contour/index.min.js /vendor/d3/d3.v7.min.js].each do |js_file|
|
|
71
|
+
%w[/js/overlay_layout.js /js/filters.js /js/contour.js /js/tilegrid.js /vendor/maplibre-gl/maplibre-gl.js /vendor/maplibre-contour/index.min.js /vendor/d3/d3.v7.min.js].each do |js_file|
|
|
64
72
|
get js_file
|
|
65
73
|
expect(last_response).to be_ok
|
|
66
74
|
expect(last_response.content_type).to include('javascript')
|
|
@@ -88,6 +96,7 @@ RSpec.describe MapLibrePreview do
|
|
|
88
96
|
expect(body).to include('/vendor/maplibre-gl/maplibre-gl.js')
|
|
89
97
|
expect(body).to include('/vendor/maplibre-contour/index.min.js')
|
|
90
98
|
expect(body).to include('/vendor/d3/d3.v7.min.js')
|
|
99
|
+
expect(body).to include('/js/overlay_layout.js')
|
|
91
100
|
expect(body).not_to include('unpkg.com')
|
|
92
101
|
expect(body).not_to include('d3js.org')
|
|
93
102
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: maplibre-preview
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alexander Ludov
|
|
@@ -203,6 +203,7 @@ files:
|
|
|
203
203
|
- lib/maplibre-preview.rb
|
|
204
204
|
- lib/maplibre-preview/public/js/contour.js
|
|
205
205
|
- lib/maplibre-preview/public/js/filters.js
|
|
206
|
+
- lib/maplibre-preview/public/js/overlay_layout.js
|
|
206
207
|
- lib/maplibre-preview/public/js/tilegrid.js
|
|
207
208
|
- lib/maplibre-preview/public/vendor/d3/d3.v7.min.js
|
|
208
209
|
- lib/maplibre-preview/public/vendor/maplibre-contour/index.min.js
|