Enhance map components and improve build processes

- Updated Makefile to include `--build` flag for `docker-compose.dev.yml` and `--no-cache` for `docker-compose.prod.yml` to ensure fresh builds.
- Added new CSS styles for Leaflet tooltips and popups to utilize theme colors, enhancing visual consistency.
- Enhanced MapView component with new props for markers and current zoom level, improving marker management and zoom functionality.
- Introduced new icons for copy and info actions to improve user interface clarity.
- Updated MapBookmarks and MapControls components to support new features and improve user experience with bookmarks and zoom controls.
- Refactored MapSearch to display coordinates and improve marker search functionality.
This commit is contained in:
2026-03-04 12:49:31 +03:00
parent dda35baeca
commit fc42d86ca0
13 changed files with 267 additions and 46 deletions

View File

@@ -1,5 +1,5 @@
import type L from 'leaflet'
import { HnHMaxZoom, ImageIcon } from '~/lib/LeafletCustomTypes'
import { HnHMaxZoom, ImageIcon, TileSize } from '~/lib/LeafletCustomTypes'
export interface MarkerData {
id: number
@@ -104,7 +104,15 @@ export function createMarker(
}
const position = mapview.map.unproject([marker.position.x, marker.position.y], HnHMaxZoom)
leafletMarker = L.marker(position, { icon, title: marker.name })
leafletMarker = L.marker(position, { icon })
const gridX = Math.floor(marker.position.x / TileSize)
const gridY = Math.floor(marker.position.y / TileSize)
const tooltipContent = `${marker.name} · ${gridX}, ${gridY}`
leafletMarker.bindTooltip(tooltipContent, {
direction: 'top',
permanent: false,
offset: L.point(0, -14),
})
leafletMarker.addTo(mapview.markerLayer)
const markerEl = (leafletMarker as unknown as { getElement?: () => HTMLElement }).getElement?.()
if (markerEl) markerEl.setAttribute('aria-label', marker.name)
@@ -125,6 +133,9 @@ export function createMarker(
if (leafletMarker) {
const position = mapview.map.unproject([updated.position.x, updated.position.y], HnHMaxZoom)
leafletMarker.setLatLng(position)
const gridX = Math.floor(updated.position.x / TileSize)
const gridY = Math.floor(updated.position.y / TileSize)
leafletMarker.setTooltipContent(`${marker.name} · ${gridX}, ${gridY}`)
}
},

View File

@@ -6,6 +6,10 @@ vi.mock('leaflet', () => {
addTo: vi.fn().mockReturnThis(),
setLatLng: vi.fn().mockReturnThis(),
remove: vi.fn().mockReturnThis(),
bindTooltip: vi.fn().mockReturnThis(),
setTooltipContent: vi.fn().mockReturnThis(),
openPopup: vi.fn().mockReturnThis(),
closePopup: vi.fn().mockReturnThis(),
}
return {
default: {
@@ -19,11 +23,13 @@ vi.mock('leaflet', () => {
vi.mock('~/lib/LeafletCustomTypes', () => ({
HnHMaxZoom: 6,
TileSize: 100,
ImageIcon: class {
constructor(_opts: Record<string, unknown>) {}
},
}))
import L from 'leaflet'
import { createMarker, type MarkerData, type MapViewRef } from '../Marker'
function makeMarkerData(overrides: Partial<MarkerData> = {}): MarkerData {
@@ -57,7 +63,7 @@ describe('createMarker', () => {
})
it('creates a marker with correct properties', () => {
const marker = createMarker(makeMarkerData())
const marker = createMarker(makeMarkerData(), undefined, L)
expect(marker.id).toBe(1)
expect(marker.name).toBe('Tower')
expect(marker.position).toEqual({ x: 100, y: 200 })
@@ -69,46 +75,46 @@ describe('createMarker', () => {
})
it('detects quest type', () => {
const marker = createMarker(makeMarkerData({ image: 'gfx/invobjs/small/bush' }))
const marker = createMarker(makeMarkerData({ image: 'gfx/invobjs/small/bush' }), undefined, L)
expect(marker.type).toBe('quest')
})
it('detects quest type for bumling', () => {
const marker = createMarker(makeMarkerData({ image: 'gfx/invobjs/small/bumling' }))
const marker = createMarker(makeMarkerData({ image: 'gfx/invobjs/small/bumling' }), undefined, L)
expect(marker.type).toBe('quest')
})
it('detects custom type', () => {
const marker = createMarker(makeMarkerData({ image: 'custom' }))
const marker = createMarker(makeMarkerData({ image: 'custom' }), undefined, L)
expect(marker.type).toBe('custom')
})
it('extracts type from gfx path', () => {
const marker = createMarker(makeMarkerData({ image: 'gfx/terobjs/mm/village' }))
const marker = createMarker(makeMarkerData({ image: 'gfx/terobjs/mm/village' }), undefined, L)
expect(marker.type).toBe('village')
})
it('starts with null leaflet marker', () => {
const marker = createMarker(makeMarkerData())
const marker = createMarker(makeMarkerData(), undefined, L)
expect(marker.leafletMarker).toBeNull()
})
it('add creates a leaflet marker for non-hidden markers', () => {
const marker = createMarker(makeMarkerData())
const marker = createMarker(makeMarkerData(), undefined, L)
const mapview = makeMapViewRef()
marker.add(mapview)
expect(mapview.map.unproject).toHaveBeenCalled()
})
it('add does nothing for hidden markers', () => {
const marker = createMarker(makeMarkerData({ hidden: true }))
const marker = createMarker(makeMarkerData({ hidden: true }), undefined, L)
const mapview = makeMapViewRef()
marker.add(mapview)
expect(mapview.map.unproject).not.toHaveBeenCalled()
})
it('update changes position and name', () => {
const marker = createMarker(makeMarkerData())
const marker = createMarker(makeMarkerData(), undefined, L)
const mapview = makeMapViewRef()
marker.update(mapview, {
@@ -122,7 +128,7 @@ describe('createMarker', () => {
})
it('setClickCallback and setContextMenu work', () => {
const marker = createMarker(makeMarkerData())
const marker = createMarker(makeMarkerData(), undefined, L)
const clickCb = vi.fn()
const contextCb = vi.fn()
@@ -131,7 +137,7 @@ describe('createMarker', () => {
})
it('remove on a marker without leaflet marker does nothing', () => {
const marker = createMarker(makeMarkerData())
const marker = createMarker(makeMarkerData(), undefined, L)
const mapview = makeMapViewRef()
marker.remove(mapview) // should not throw
expect(marker.leafletMarker).toBeNull()