Files
hnh-map/frontend-nuxt/lib/Marker.ts
Nikolay Tatarinov adfdfd01c4 Update frontend components for accessibility and functionality improvements
- Modified docker-compose.dev.yml to conditionally run npm install based on the presence of package-lock.json.
- Upgraded Nuxt version in package-lock.json from 3.21.1 to 4.3.1 for enhanced features.
- Enhanced ConfirmModal component with aria-modal attribute for better accessibility.
- Updated MapErrorBoundary component's error message for clarity.
- Added role and aria-label attributes to MapView and MapSearch components for improved screen reader support.
- Refactored various components to manage focus behavior on modal close, enhancing user experience.
- Improved ToastContainer styling for better responsiveness and visibility.
- Updated layout components to include skip navigation links for improved accessibility.
2026-03-04 01:00:56 +03:00

149 lines
4.5 KiB
TypeScript

import type L from 'leaflet'
import { HnHMaxZoom, ImageIcon } from '~/lib/LeafletCustomTypes'
export interface MarkerData {
id: number
position: { x: number; y: number }
name: string
image: string
hidden: boolean
map: number
}
export interface MapViewRef {
map: L.Map
mapid: number
markerLayer: L.LayerGroup
}
export interface MapMarker {
id: number
position: { x: number; y: number }
name: string
image: string
type: string
text: string
value: number
hidden: boolean
map: number
leafletMarker: L.Marker | null
remove: (mapview: MapViewRef) => void
add: (mapview: MapViewRef) => void
update: (mapview: MapViewRef, updated: MarkerData | MapMarker) => void
jumpTo: (map: L.Map) => void
setClickCallback: (callback: (e: L.LeafletMouseEvent) => void) => void
setContextMenu: (callback: (e: L.LeafletMouseEvent) => void) => void
}
function detectType(name: string): string {
if (name === 'gfx/invobjs/small/bush' || name === 'gfx/invobjs/small/bumling') return 'quest'
if (name === 'custom') return 'custom'
return name.substring('gfx/terobjs/mm/'.length)
}
export interface MarkerIconOptions {
/** Resolves relative icon path to absolute URL (e.g. with app base path). */
resolveIconUrl: (path: string) => string
/** Optional fallback URL when the icon image fails to load. */
fallbackIconUrl?: string
}
export type LeafletApi = typeof import('leaflet')
export function createMarker(
data: MarkerData,
iconOptions: MarkerIconOptions | undefined,
L: LeafletApi
): MapMarker {
let leafletMarker: L.Marker | null = null
let onClick: ((e: L.LeafletMouseEvent) => void) | null = null
let onContext: ((e: L.LeafletMouseEvent) => void) | null = null
const marker: MapMarker = {
id: data.id,
position: { ...data.position },
name: data.name,
image: data.image,
type: detectType(data.image),
text: data.name,
value: data.id,
hidden: data.hidden,
map: data.map,
get leafletMarker() {
return leafletMarker
},
remove(_mapview: MapViewRef): void {
if (leafletMarker) {
leafletMarker.remove()
leafletMarker = null
}
},
add(mapview: MapViewRef): void {
if (!marker.hidden) {
const resolve = iconOptions?.resolveIconUrl ?? ((path: string) => path)
const fallback = iconOptions?.fallbackIconUrl
let icon: L.Icon
if (marker.image === 'gfx/terobjs/mm/custom') {
icon = new ImageIcon({
iconUrl: resolve('gfx/terobjs/mm/custom.png'),
iconSize: [21, 23],
iconAnchor: [11, 21],
popupAnchor: [1, 3],
tooltipAnchor: [1, 3],
fallbackIconUrl: fallback,
})
} else {
icon = new ImageIcon({
iconUrl: resolve(`${marker.image}.png`),
iconSize: [32, 32],
fallbackIconUrl: fallback,
})
}
const position = mapview.map.unproject([marker.position.x, marker.position.y], HnHMaxZoom)
leafletMarker = L.marker(position, { icon, title: marker.name })
leafletMarker.addTo(mapview.markerLayer)
const markerEl = (leafletMarker as unknown as { getElement?: () => HTMLElement }).getElement?.()
if (markerEl) markerEl.setAttribute('aria-label', marker.name)
leafletMarker.on('click', (e: L.LeafletMouseEvent) => {
if (onClick) onClick(e)
})
leafletMarker.on('contextmenu', (e: L.LeafletMouseEvent) => {
if (onContext) onContext(e)
})
}
},
update(mapview: MapViewRef, updated: MarkerData | MapMarker): void {
marker.position = { ...updated.position }
marker.name = updated.name
marker.hidden = updated.hidden
marker.map = updated.map
if (leafletMarker) {
const position = mapview.map.unproject([updated.position.x, updated.position.y], HnHMaxZoom)
leafletMarker.setLatLng(position)
}
},
jumpTo(map: L.Map): void {
if (leafletMarker) {
const position = map.unproject([marker.position.x, marker.position.y], HnHMaxZoom)
leafletMarker.setLatLng(position)
}
},
setClickCallback(callback: (e: L.LeafletMouseEvent) => void): void {
onClick = callback
},
setContextMenu(callback: (e: L.LeafletMouseEvent) => void): void {
onContext = callback
},
}
return marker
}