Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { pickLayers, resolveMapStyle, styleForBaseLayer, basesNeedStyleSwitch, a
import { GeocoderIControl } from 'maplibre/geocoder_control'
import { OverlayLayersToggleIControl } from 'maplibre/overlay_layers_toggle_control'
import { DeclusterViewportIControl } from 'maplibre/decluster_viewport_control'
import { DestinationsMapLayers } from 'maplibre/destinations_map_layers'
import { DestinationsMapLayers, CLUSTER_LAYER_ID } from 'maplibre/destinations_map_layers'

const DEFAULT_ZOOM = 12
const MIN_CHARS = 3
Expand Down Expand Up @@ -571,7 +571,7 @@ export default class extends Controller {
overlayLayers,
this._overlayVisibility,
this._overlayToggles,
{ includeRaster: resolved.mode === 'vector' }
{ includeRaster: resolved.mode === 'vector', beforeId: CLUSTER_LAYER_ID }
).catch(() => { /* overlay style fetch failed */ })
}
if (map.isStyleLoaded()) runOverlays()
Expand Down Expand Up @@ -655,7 +655,7 @@ export default class extends Controller {
this._mapOverlayLayers || [],
this._overlayVisibility,
this._overlayToggles,
{ includeRaster: true }
{ includeRaster: true, beforeId: CLUSTER_LAYER_ID }
).catch(() => { /* overlay style fetch failed */ })
this._mapLayers = new DestinationsMapLayers(this._map, this._destinationsMapLayersOptions)
// Keep current camera; fitBounds on reconnect fights setStyle and can drop the viewport fetch.
Expand Down
21 changes: 13 additions & 8 deletions app/javascript/maplibre/raster_layers.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ function resolveStyleResourceUrl (styleUrl, resource) {
* Skips background layers and layers authored with visibility:"none"
* @returns {string[]} layer ids added for this overlay (toggle-controlled)
*/
export function mergeVectorOverlayStyle (map, style, overlayIndex, visible, styleUrl = null) {
export function mergeVectorOverlayStyle (map, style, overlayIndex, visible, styleUrl = null, beforeId = null) {
const prefix = 'overlay-' + overlayIndex + '-'
const layerIds = []
const layers = (style.layers || []).filter((layer) => {
Expand All @@ -190,6 +190,7 @@ export function mergeVectorOverlayStyle (map, style, overlayIndex, visible, styl
if (raw.url) raw.url = resolveStyleResourceUrl(styleUrl, raw.url)
map.addSource(sid, raw)
})
const insertBefore = beforeId && map.getLayer(beforeId) ? beforeId : undefined
layers.forEach((layer) => {
const lid = prefix + layer.id
layerIds.push(lid)
Expand All @@ -205,15 +206,15 @@ export function mergeVectorOverlayStyle (map, style, overlayIndex, visible, styl
next.layout.visibility = visible ? 'visible' : 'none'
if (layer.source) next.source = prefix + layer.source
try {
map.addLayer(next)
map.addLayer(next, insertBefore)
} catch (e) {
// Skip layers that the current map glyphs/sprite cannot render.
}
})
return layerIds
}

function applySingleRasterOverlay (map, overlay, index, visible) {
function applySingleRasterOverlay (map, overlay, index, visible, beforeId = null) {
const sid = 'overlay-' + index
const layerId = sid + '-layer'
if (!map.getSource(sid)) {
Expand All @@ -224,14 +225,15 @@ function applySingleRasterOverlay (map, overlay, index, visible) {
attribution: overlay.attribution || ''
})
}
const insertBefore = beforeId && map.getLayer(beforeId) ? beforeId : undefined
if (!map.getLayer(layerId)) {
map.addLayer({
id: layerId,
type: 'raster',
source: sid,
layout: { visibility: visible ? 'visible' : 'none' },
paint: { 'raster-opacity': 0.75 }
})
}, insertBefore)
} else {
map.setLayoutProperty(layerId, 'visibility', visible ? 'visible' : 'none')
}
Expand All @@ -246,11 +248,14 @@ function applySingleRasterOverlay (map, overlay, index, visible) {
* @param {object[]} overlays from pickLayers
* @param {Record<string, boolean>} visibilityByGroupId keyed by overlay-N
* @param {object[]} [overlayToggles]
* @param {{ includeRaster?: boolean }} [options] includeRaster=false skips raster overlays already baked in composite style
* @param {{ includeRaster?: boolean, beforeId?: string }} [options]
* includeRaster=false skips raster overlays already baked in composite style
* beforeId inserts overlay layers below an existing map layer (e.g. markers)
*/
export async function applyOverlays (map, overlays, visibilityByGroupId = {}, overlayToggles = null, options = {}) {
if (!map || !overlays || !overlays.length) return []
const includeRaster = options.includeRaster !== false
const beforeId = options.beforeId || null
const toggles = []
for (let j = 0; j < overlays.length; j++) {
const o = overlays[j]
Expand All @@ -262,13 +267,13 @@ export async function applyOverlays (map, overlays, visibilityByGroupId = {}, ov
if (o.vectorStyleUrl) {
try {
const style = await fetchStyleJson(o.vectorStyleUrl)
layerIds = mergeVectorOverlayStyle(map, style, j, visible, o.vectorStyleUrl)
layerIds = mergeVectorOverlayStyle(map, style, j, visible, o.vectorStyleUrl, beforeId)
} catch (e) {
// Vector overlays are never baked into the composite style — always allow raster fallback.
if (o.url) layerIds = applySingleRasterOverlay(map, o, j, visible)
if (o.url) layerIds = applySingleRasterOverlay(map, o, j, visible, beforeId)
}
} else if (includeRaster && o.url) {
layerIds = applySingleRasterOverlay(map, o, j, visible)
layerIds = applySingleRasterOverlay(map, o, j, visible, beforeId)
} else if (!o.vectorStyleUrl && o.url) {
// Raster already in composite style — keep toggle metadata only.
layerIds = [groupId + '-layer']
Expand Down
7 changes: 7 additions & 0 deletions app/views/v2/layouts/application.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,13 @@
%i.fa.fa-chart-line.fa-fw
.hidden-menu
= t 'customers.menu.analytics'
- if @reseller.extra_dashboard_url && current_user.customer
%li.menu-section.menu-list-item
= link_to @reseller.extra_dashboard_url.gsub('{LG}', I18n.locale.to_s).gsub('{ID}', current_user.customer.id.to_s), target: '_blank' do
.d-flex.align-items-center.justify-content-center
%i.fa.fa-chart-column.fa-fw
.hidden-menu
= t 'customers.menu.extra_analytics'

- if current_user.customer && @reseller.subscription_url && (current_user.customer.test? || (current_user.customer.end_subscription && current_user.customer.end_subscription < Time.now))
%li#menu-subscription.subscription.menu-section.menu-list-item
Expand Down
10 changes: 10 additions & 0 deletions test/controllers/destinations_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,16 @@ def around
assert_select 'button.floating-btn.xl-floating-button.destinations-position-drag-cancel.d-none', 1
end

test 'v2 layout menu-left includes extra dashboard when configured' do
customer = users(:user_one).customer
@reseller.update!(extra_dashboard_url: 'https://extra.example.com/{LG}/{ID}')

get :index
assert_response :success
expected = "https://extra.example.com/#{I18n.locale}/#{customer.id}"
assert_select ".menu-left a[href='#{expected}'][target='_blank']", text: I18n.t('customers.menu.extra_analytics')
end

test 'v2 index includes vector_style_url in map layers config as-is' do
layer = users(:user_one).layer
style_url = 'https://maps.example.com/styles/custom/style.json'
Expand Down
32 changes: 28 additions & 4 deletions test/javascript/maplibre_vector_style_test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ function buildRasterStyle (mapLayers) {
return { style: { version: 8, sources, layers }, overlayToggles }
}

function mergeVectorOverlayStyle (map, style, overlayIndex, visible) {
function mergeVectorOverlayStyle (map, style, overlayIndex, visible, beforeId = null) {
const prefix = 'overlay-' + overlayIndex + '-'
const layerIds = []
const layers = (style.layers || []).filter((layer) => {
Expand All @@ -84,6 +84,7 @@ function mergeVectorOverlayStyle (map, style, overlayIndex, visible) {
const sid = prefix + id
if (!map.getSource(sid)) map.addSource(sid, { ...style.sources[id] })
})
const insertBefore = beforeId && map.getLayer(beforeId) ? beforeId : undefined
layers.forEach((layer) => {
const lid = prefix + layer.id
layerIds.push(lid)
Expand All @@ -93,7 +94,7 @@ function mergeVectorOverlayStyle (map, style, overlayIndex, visible) {
id: lid,
source: layer.source ? prefix + layer.source : undefined,
layout: { ...(layer.layout || {}), visibility: visible ? 'visible' : 'none' }
})
}, insertBefore)
}
})
return layerIds
Expand All @@ -102,11 +103,17 @@ function mergeVectorOverlayStyle (map, style, overlayIndex, visible) {
function fakeMap () {
const sources = new Map()
const layers = new Map()
const order = []
return {
getSource: (id) => sources.get(id),
addSource: (id, src) => { sources.set(id, src) },
getLayer: (id) => layers.get(id),
addLayer: (layer) => { layers.set(layer.id, layer) },
addLayer: (layer, beforeId) => {
layers.set(layer.id, layer)
const idx = beforeId ? order.indexOf(beforeId) : -1
if (idx >= 0) order.splice(idx, 0, layer.id)
else order.push(layer.id)
},
setLayoutProperty: (id, key, value) => {
const layer = layers.get(id)
if (layer) {
Expand All @@ -115,7 +122,8 @@ function fakeMap () {
}
},
_sources: sources,
_layers: layers
_layers: layers,
_order: order
}
}

Expand Down Expand Up @@ -234,6 +242,22 @@ describe('overlay layers prefer vector_style_url', () => {
assert.ok(map.getLayer('overlay-0-highway'))
})

it('inserts overlay layers before markers when beforeId exists', () => {
const map = fakeMap()
map.addLayer({ id: 'destinations-v2-clusters', type: 'circle' })
mergeVectorOverlayStyle(map, {
version: 8,
sources: {
restrictions: { type: 'vector', url: 'https://maps.example.com/tiles.json' }
},
layers: [
{ id: 'highway', type: 'line', source: 'restrictions', 'source-layer': 'highway' }
]
}, 0, true, 'destinations-v2-clusters')

assert.deepEqual(map._order, ['overlay-0-highway', 'destinations-v2-clusters'])
})

it('pickLayers exposes overlay vectorStyleUrl for applyOverlays', () => {
const { overlays } = pickLayers({
Truck: {
Expand Down
Loading