maplibre-preview 1.7.2 → 1.9.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.
@@ -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
- button.style-parameters-header type="button" onclick="toggleStyleParametersPanel()" id="style-parameters-toggle" aria-expanded="false"
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
- span.style-parameters-toggle-icon
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,10 +152,11 @@ 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 = {};
156
- let parameterizedUrlPrefixes = new Set();
159
+ let parameterizedUrlRules = [];
157
160
 
158
161
  const toDomId = (prefix, id) => `${prefix}-${String(id).replace(/[^a-zA-Z0-9_-]/g, '_')}`;
159
162
  const noCacheRequestOptions = () => mapCacheDisabled ? {cache: 'no-store'} : undefined;
@@ -166,7 +169,8 @@ javascript:
166
169
  return [];
167
170
  };
168
171
 
169
- const inferParameterInputType = (name) => /(^|_|-)(time|date|datetime)($|_|-)/i.test(name) ? 'datetime-local' : 'text';
172
+ const isTemporalParameter = (name) => /(^|_|-)(time|date|datetime)($|_|-)/i.test(name);
173
+ const inferParameterInputType = (name) => isTemporalParameter(name) ? 'datetime-local' : 'text';
170
174
 
171
175
  const parameterInputToQueryValue = (name, value) => {
172
176
  if (!value) return '';
@@ -208,9 +212,24 @@ javascript:
208
212
  return prefix ? new URL(prefix, window.location.href).toString() : null;
209
213
  };
210
214
 
211
- const rememberParameterizedUrl = (template) => {
215
+ const rememberParameterizedUrl = (template, parameterNames) => {
216
+ const names = normalizeQueryParams(parameterNames);
217
+ if (!names.length) return;
218
+
212
219
  const prefix = urlPrefixFromTemplate(template);
213
- prefix && parameterizedUrlPrefixes.add(prefix);
220
+ if (!prefix) return;
221
+
222
+ const existing = parameterizedUrlRules.find(rule => rule.prefix === prefix);
223
+ if (existing) {
224
+ names.forEach(name => existing.parameterNames.add(name));
225
+ } else {
226
+ parameterizedUrlRules.push({prefix, parameterNames: new Set(names)});
227
+ }
228
+ };
229
+
230
+ const rememberParameterizedSourceUrls = (sourceDef, parameterNames) => {
231
+ [sourceDef?.url, sourceDef?.meta_url, sourceDef?.data].forEach(url => rememberParameterizedUrl(url, parameterNames));
232
+ (sourceDef?.tiles || []).forEach(tileUrl => rememberParameterizedUrl(tileUrl, parameterNames));
214
233
  };
215
234
 
216
235
  const appendStyleParametersToUrl = (resourceUrl, values = styleParameterValues, parameterNames = Object.keys(values)) => {
@@ -239,16 +258,13 @@ javascript:
239
258
  const params = sourceDeclaredParameters(sourceDef);
240
259
  params.forEach(name => mergeStyleParameterDefinition(name, sourceName));
241
260
 
242
- if (params.length) {
243
- [sourceDef.url, sourceDef.meta_url, sourceDef.data].forEach(rememberParameterizedUrl);
244
- (sourceDef.tiles || []).forEach(rememberParameterizedUrl);
245
- }
261
+ rememberParameterizedSourceUrls(sourceDef, params);
246
262
  });
247
263
  };
248
264
 
249
265
  const fetchSourceMetadata = async (url) => {
250
266
  try {
251
- const response = await fetch(appendStyleParametersToUrl(url), noCacheRequestOptions());
267
+ const response = await fetch(url, noCacheRequestOptions());
252
268
  return response.ok ? response.json() : null;
253
269
  } catch (e) {
254
270
  console.warn('Could not inspect source metadata:', url, e);
@@ -269,7 +285,9 @@ javascript:
269
285
 
270
286
  const params = normalizeQueryParams(metadata.query_params || metadata.queryParams);
271
287
  params.forEach(name => mergeStyleParameterDefinition(name, sourceName));
272
- (metadata.tiles || []).forEach(rememberParameterizedUrl);
288
+ const parameterNames = getSourceParameterNames(sourceName, sourceDef);
289
+ rememberParameterizedSourceUrls(sourceDef, parameterNames);
290
+ (metadata.tiles || []).forEach(tileUrl => rememberParameterizedUrl(tileUrl, parameterNames));
273
291
  }
274
292
  }));
275
293
  };
@@ -294,6 +312,53 @@ javascript:
294
312
  return values;
295
313
  };
296
314
 
315
+ const normalizeLocalizedLabel = (value) => {
316
+ if (!value) return null;
317
+ if (typeof value === 'string') return value;
318
+ if (typeof value === 'object') return value.title || value.name || value.label || null;
319
+ return String(value);
320
+ };
321
+
322
+ const getLocalizedMetadataLabel = (id, style = originalStyle) => {
323
+ const locale = style?.metadata?.locale;
324
+ if (!locale || !id) return id;
325
+
326
+ for (const lang of ['en-US', 'en', 'ru']) {
327
+ const label = normalizeLocalizedLabel(locale[lang]?.[id]);
328
+ if (label) return label;
329
+ }
330
+
331
+ for (const lang in locale) {
332
+ const label = normalizeLocalizedLabel(locale[lang]?.[id]);
333
+ if (label) return label;
334
+ }
335
+
336
+ return id;
337
+ };
338
+
339
+ const compactList = (items, limit = 3) => {
340
+ const unique = [...new Set(items.filter(Boolean))];
341
+ return unique.length > limit
342
+ ? `${unique.slice(0, limit).join(', ')} +${unique.length - limit}`
343
+ : unique.join(', ');
344
+ };
345
+
346
+ const getStyleParameterContext = (definition) => {
347
+ const style = originalStyle || currentStyle;
348
+ const sources = [...definition.sources].sort();
349
+ const layers = (style?.layers || []).filter(layer => sources.includes(layer.source));
350
+ const filterIds = [...new Set(layers.map(layer => layer.metadata?.filter_id).filter(Boolean))].sort();
351
+ const filterLabels = filterIds.map(id => getLocalizedMetadataLabel(id, style));
352
+
353
+ return {
354
+ sources,
355
+ layers: layers.map(layer => layer.id).sort(),
356
+ filterLabels: filterLabels.length ? filterLabels : sources,
357
+ sourceSummary: compactList(sources),
358
+ filterSummary: compactList(filterLabels.length ? filterLabels : sources)
359
+ };
360
+ };
361
+
297
362
  const renderStyleParameterControls = () => {
298
363
  const panel = document.getElementById('style-parameters-panel');
299
364
  const fields = document.getElementById('style-parameter-fields');
@@ -306,28 +371,84 @@ javascript:
306
371
  fields.innerHTML = '';
307
372
 
308
373
  definitions.forEach(definition => {
309
- const row = document.createElement('label');
374
+ const row = document.createElement('div');
310
375
  row.className = 'style-parameter-row';
311
- row.htmlFor = `style-param-${definition.name}`;
376
+ const context = getStyleParameterContext(definition);
312
377
 
313
- const label = document.createElement('span');
378
+ const header = document.createElement('div');
379
+ header.className = 'style-parameter-header';
380
+ const label = document.createElement('label');
314
381
  label.className = 'style-parameter-label';
382
+ label.htmlFor = `style-param-${definition.name}`;
315
383
  label.textContent = definition.name;
384
+ const counts = document.createElement('span');
385
+ counts.className = 'style-parameter-counts';
386
+ counts.textContent = `${context.sources.length} src / ${context.layers.length} lyr`;
387
+ counts.title = `${context.sources.length} sources, ${context.layers.length} layers`;
388
+ header.appendChild(label);
389
+ header.appendChild(counts);
316
390
 
317
391
  const input = document.createElement('input');
318
392
  input.className = 'style-parameter-input';
319
393
  input.id = `style-param-${definition.name}`;
320
- input.type = inferParameterInputType(definition.name);
394
+ input.type = isTemporalParameter(definition.name) ? 'text' : inferParameterInputType(definition.name);
321
395
  input.value = queryValueToParameterInput(definition.name, styleParameterValues[definition.name]);
322
396
  input.dataset.parameterName = definition.name;
323
- input.title = [...definition.sources].join(', ');
397
+ input.title = [
398
+ `Sources: ${context.sources.join(', ')}`,
399
+ `Layers: ${context.layers.join(', ')}`
400
+ ].join('\n');
401
+ if (isTemporalParameter(definition.name)) {
402
+ input.classList.add('temporal');
403
+ input.readOnly = true;
404
+ input.placeholder = 'Select date and time';
405
+ input.setAttribute('aria-haspopup', 'dialog');
406
+ input.addEventListener('click', () => window.TemporalPicker.open(input));
407
+ input.addEventListener('focus', () => window.TemporalPicker.open(input));
408
+ input.addEventListener('keydown', event => {
409
+ if (event.key === 'Enter' || event.key === ' ') {
410
+ event.preventDefault();
411
+ window.TemporalPicker.open(input);
412
+ }
413
+ });
414
+ }
324
415
 
325
- row.appendChild(label);
326
- row.appendChild(input);
416
+ const parameterControl = document.createElement('div');
417
+ parameterControl.className = isTemporalParameter(definition.name)
418
+ ? 'style-parameter-input-group temporal'
419
+ : 'style-parameter-input-group';
420
+ parameterControl.appendChild(input);
421
+
422
+ const contextBlock = document.createElement('div');
423
+ contextBlock.className = 'style-parameter-context';
424
+ contextBlock.title = input.title;
425
+
426
+ const usedBy = document.createElement('span');
427
+ usedBy.className = 'style-parameter-context-row';
428
+ const usedByKey = document.createElement('span');
429
+ usedByKey.className = 'style-parameter-context-key';
430
+ usedByKey.textContent = 'Used by';
431
+ usedBy.appendChild(usedByKey);
432
+ usedBy.appendChild(document.createTextNode(` ${context.filterSummary || 'No linked layers'}`));
433
+ contextBlock.appendChild(usedBy);
434
+
435
+ const sourceInfo = document.createElement('span');
436
+ sourceInfo.className = 'style-parameter-context-row';
437
+ const sourceKey = document.createElement('span');
438
+ sourceKey.className = 'style-parameter-context-key';
439
+ sourceKey.textContent = 'Sources';
440
+ sourceInfo.appendChild(sourceKey);
441
+ sourceInfo.appendChild(document.createTextNode(` ${context.sourceSummary || 'None'}`));
442
+ contextBlock.appendChild(sourceInfo);
443
+
444
+ row.appendChild(header);
445
+ row.appendChild(parameterControl);
446
+ row.appendChild(contextBlock);
327
447
  fields.appendChild(row);
328
448
  });
329
449
 
330
450
  layoutBottomOverlays();
451
+ overlayLayoutManager?.refreshPanel('style-parameters');
331
452
  };
332
453
 
333
454
  const toggleStyleParametersPanel = () => {
@@ -338,12 +459,13 @@ javascript:
338
459
  const isCollapsed = panel.classList.toggle('collapsed');
339
460
  toggle?.setAttribute('aria-expanded', String(!isCollapsed));
340
461
  layoutBottomOverlays();
462
+ overlayLayoutManager?.refreshPanel('style-parameters');
341
463
  };
342
464
 
343
465
  const initializeStyleParameters = async (style) => {
344
466
  styleParameterDefinitions = new Map();
345
467
  styleParameterValues = {};
346
- parameterizedUrlPrefixes = new Set();
468
+ parameterizedUrlRules = [];
347
469
 
348
470
  collectStyleSourceParameters(style);
349
471
  styleParameterDefinitions.forEach((definition, name) => {
@@ -384,15 +506,27 @@ javascript:
384
506
  return modifiedStyle;
385
507
  };
386
508
 
387
- const shouldPatchRequestUrl = (resourceUrl) => {
388
- if (!Object.values(styleParameterValues).some(value => value !== undefined && value !== null && value !== '')) return false;
509
+ const parameterizedUrlRuleFor = (resourceUrl) => {
389
510
  const absolute = new URL(resourceUrl, window.location.href).toString();
390
- return absolute.includes('/rb_tiles/') || [...parameterizedUrlPrefixes].some(prefix => absolute.startsWith(prefix));
511
+ return parameterizedUrlRules
512
+ .filter(rule => absolute.startsWith(rule.prefix))
513
+ .sort((a, b) => b.prefix.length - a.prefix.length)[0] || null;
391
514
  };
392
515
 
393
- const patchRequestUrl = (resourceUrl) => shouldPatchRequestUrl(resourceUrl) ? appendStyleParametersToUrl(resourceUrl) : resourceUrl;
516
+ const patchRequestUrl = (resourceUrl) => {
517
+ const rule = parameterizedUrlRuleFor(resourceUrl);
518
+ if (!rule) return resourceUrl;
519
+
520
+ const parameterNames = [...rule.parameterNames];
521
+ if (!parameterNames.some(name => {
522
+ const value = styleParameterValues[name];
523
+ return value !== undefined && value !== null && value !== '';
524
+ })) return resourceUrl;
525
+
526
+ return appendStyleParametersToUrl(resourceUrl, styleParameterValues, parameterNames);
527
+ };
394
528
 
395
- const BOTTOM_OVERLAY_IDS = ['style-parameters-panel', 'loading-indicator', 'profile-overlay'];
529
+ const BOTTOM_OVERLAY_IDS = ['loading-indicator'];
396
530
  const BOTTOM_OVERLAY_BASE_OFFSET = 20;
397
531
  const BOTTOM_OVERLAY_GAP = 12;
398
532
 
@@ -419,6 +553,69 @@ javascript:
419
553
  element.style.bottom = `${bottomOffset}px`;
420
554
  bottomOffset += element.offsetHeight + BOTTOM_OVERLAY_GAP;
421
555
  });
556
+ overlayLayoutManager?.refreshBounds();
557
+ });
558
+ };
559
+
560
+ const getSystemReservedBottom = () => {
561
+ const loading = document.getElementById('loading-indicator');
562
+ return isVisibleBottomOverlay(loading) ? loading.offsetHeight + BOTTOM_OVERLAY_BASE_OFFSET + BOTTOM_OVERLAY_GAP : 0;
563
+ };
564
+
565
+ const leftControlStackOffset = (sectionName) => {
566
+ const mapSettings = document.getElementById('map-settings-wrapper');
567
+ const styleControls = document.getElementById('style-controls-wrapper');
568
+ const gap = 12;
569
+ const mapHeight = mapSettings?.getBoundingClientRect().height || mapSettings?.offsetHeight || 0;
570
+ const styleHeight = styleControls?.getBoundingClientRect().height || styleControls?.offsetHeight || 0;
571
+
572
+ return {
573
+ x: 0,
574
+ y: sectionName === 'map-settings' ? -((styleHeight + gap) / 2) : ((mapHeight + gap) / 2)
575
+ };
576
+ };
577
+
578
+ const initializeOverlayLayout = () => {
579
+ if (!window.OverlayLayoutManager) return;
580
+
581
+ overlayLayoutManager?.destroy();
582
+ overlayLayoutManager = new OverlayLayoutManager({
583
+ storageKey: 'maplibre-preview:overlay-layout:v3',
584
+ snapThreshold: 32,
585
+ mobileBreakpoint: 768,
586
+ edgeGap: 10,
587
+ getReservedBounds: () => ({bottom: getSystemReservedBottom()})
588
+ });
589
+ window.overlayLayoutManager = overlayLayoutManager;
590
+ window.resetOverlayLayout = () => overlayLayoutManager.resetLayout();
591
+
592
+ overlayLayoutManager.registerPanel({
593
+ id: 'map-settings',
594
+ element: '#map-settings-wrapper',
595
+ handleSelector: '.control-section-title',
596
+ defaultAnchor: 'top-left',
597
+ defaultOffset: {x: 0, y: 0}
598
+ });
599
+ overlayLayoutManager.registerPanel({
600
+ id: 'style-controls',
601
+ element: '#style-controls-wrapper',
602
+ handleSelector: '.control-section-title',
603
+ defaultAnchor: 'left',
604
+ defaultOffset: () => leftControlStackOffset('style-controls')
605
+ });
606
+ overlayLayoutManager.registerPanel({
607
+ id: 'style-parameters',
608
+ element: '#style-parameters-panel',
609
+ handleSelector: '.style-parameters-title',
610
+ defaultAnchor: 'bottom',
611
+ defaultOffset: {x: 0, y: 0}
612
+ });
613
+ overlayLayoutManager.registerPanel({
614
+ id: 'performance',
615
+ element: '#performance-panel',
616
+ handleSelector: '.performance-content',
617
+ defaultAnchor: 'top',
618
+ defaultOffset: {x: 0, y: -10}
422
619
  });
423
620
  };
424
621
 
@@ -648,6 +845,7 @@ javascript:
648
845
  });
649
846
  filters.init();
650
847
  createLayerButtons();
848
+ overlayLayoutManager?.refreshBounds();
651
849
  } catch (e) {
652
850
  console.warn('Filter initialization failed:', e);
653
851
  }
@@ -947,7 +1145,19 @@ javascript:
947
1145
 
948
1146
  [header, stats, chart].forEach(el => overlay.appendChild(el));
949
1147
  document.getElementById('map-container').appendChild(overlay);
950
- setTimeout(() => drawSimpleProfileChart(profile), 10);
1148
+ overlayLayoutManager?.registerPanel({
1149
+ id: 'profile',
1150
+ element: overlay,
1151
+ handleSelector: '.profile-title',
1152
+ defaultAnchor: 'bottom',
1153
+ defaultOffset: {x: 0, y: -16},
1154
+ snap: false,
1155
+ lockSizeOnDrag: true
1156
+ });
1157
+ setTimeout(() => {
1158
+ drawSimpleProfileChart(profile);
1159
+ overlayLayoutManager?.refreshPanel('profile');
1160
+ }, 10);
951
1161
  layoutBottomOverlays();
952
1162
  };
953
1163
 
@@ -1025,6 +1235,7 @@ javascript:
1025
1235
  };
1026
1236
 
1027
1237
  const hideProfile = () => {
1238
+ overlayLayoutManager?.unregisterPanel('profile');
1028
1239
  document.getElementById('profile-overlay')?.remove();
1029
1240
  layoutBottomOverlays();
1030
1241
  map.getLayer('profile-line') && (map.removeLayer('profile-line'), map.removeSource('profile-line'));
@@ -1491,4 +1702,5 @@ javascript:
1491
1702
  window.tileGridManager = null;
1492
1703
  window.addEventListener('resize', layoutBottomOverlays);
1493
1704
 
1705
+ initializeOverlayLayout();
1494
1706
  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,13 +39,34 @@ 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')
44
52
  expect(last_response.body).to include('styleParameterDefinitions')
53
+ expect(last_response.body).to include('parameterizedUrlRules')
45
54
  expect(last_response.body).to include('sourceDeclaredParameters')
46
55
  expect(last_response.body).to include('collectSourceMetadataParameters')
47
56
  expect(last_response.body).to include('applyStyleParametersToStyle')
57
+ expect(last_response.body).to include('rememberParameterizedSourceUrls')
58
+ expect(last_response.body).to include('parameterizedUrlRuleFor')
59
+ expect(last_response.body).to include('getStyleParameterContext')
60
+ expect(last_response.body).to include('isTemporalParameter')
61
+ expect(last_response.body).to include('/css/temporal_picker.css')
62
+ expect(last_response.body).to include('/js/temporal_picker.js')
63
+ expect(last_response.body).to include('window.TemporalPicker.open')
64
+ expect(last_response.body).to include('style-parameter-input-group')
65
+ expect(last_response.body).to include('style-parameter-counts')
66
+ expect(last_response.body).to include('style-parameter-context')
67
+ expect(last_response.body).to include('Used by')
68
+ expect(last_response.body).to include('Sources:')
69
+ expect(last_response.body).not_to include("absolute.includes('/rb_tiles/')")
48
70
  expect(last_response.body).to include('layoutBottomOverlays')
49
71
  expect(last_response.body).to include('showCollisionBoxes')
50
72
  expect(last_response.body).to include('showOverdrawInspector')
@@ -60,7 +82,7 @@ RSpec.describe MapLibrePreview do
60
82
  end
61
83
 
62
84
  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|
85
+ %w[/js/overlay_layout.js /js/filters.js /js/contour.js /js/tilegrid.js /js/temporal_picker.js /vendor/maplibre-gl/maplibre-gl.js /vendor/maplibre-contour/index.min.js /vendor/d3/d3.v7.min.js].each do |js_file|
64
86
  get js_file
65
87
  expect(last_response).to be_ok
66
88
  expect(last_response.content_type).to include('javascript')
@@ -69,11 +91,13 @@ RSpec.describe MapLibrePreview do
69
91
  end
70
92
 
71
93
  it 'serves required stylesheets' do
72
- get '/vendor/maplibre-gl/maplibre-gl.css'
94
+ %w[/vendor/maplibre-gl/maplibre-gl.css /css/temporal_picker.css].each do |css_file|
95
+ get css_file
73
96
 
74
- expect(last_response).to be_ok
75
- expect(last_response.content_type).to include('text/css')
76
- expect(last_response.body).not_to be_empty
97
+ expect(last_response).to be_ok
98
+ expect(last_response.content_type).to include('text/css')
99
+ expect(last_response.body).not_to be_empty
100
+ end
77
101
  end
78
102
  end
79
103
 
@@ -85,9 +109,12 @@ RSpec.describe MapLibrePreview do
85
109
  body = last_response.body
86
110
 
87
111
  expect(body).to include('/vendor/maplibre-gl/maplibre-gl.css')
112
+ expect(body).to include('/css/temporal_picker.css')
88
113
  expect(body).to include('/vendor/maplibre-gl/maplibre-gl.js')
89
114
  expect(body).to include('/vendor/maplibre-contour/index.min.js')
90
115
  expect(body).to include('/vendor/d3/d3.v7.min.js')
116
+ expect(body).to include('/js/overlay_layout.js')
117
+ expect(body).to include('/js/temporal_picker.js')
91
118
  expect(body).not_to include('unpkg.com')
92
119
  expect(body).not_to include('d3js.org')
93
120
  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.7.2
4
+ version: 1.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexander Ludov
@@ -201,8 +201,11 @@ files:
201
201
  - bin/maplibre-preview
202
202
  - docs/README_RU.md
203
203
  - lib/maplibre-preview.rb
204
+ - lib/maplibre-preview/public/css/temporal_picker.css
204
205
  - lib/maplibre-preview/public/js/contour.js
205
206
  - lib/maplibre-preview/public/js/filters.js
207
+ - lib/maplibre-preview/public/js/overlay_layout.js
208
+ - lib/maplibre-preview/public/js/temporal_picker.js
206
209
  - lib/maplibre-preview/public/js/tilegrid.js
207
210
  - lib/maplibre-preview/public/vendor/d3/d3.v7.min.js
208
211
  - lib/maplibre-preview/public/vendor/maplibre-contour/index.min.js