- Consolidated global error handling in app.vue to redirect users to the login page on API authentication failure. - Enhanced MapView component by reintroducing event listeners for selected map and marker updates, improving interactivity. - Updated PasswordInput and various modal components to ensure proper input handling and accessibility compliance. - Refactored MapControls and MapControlsContent to streamline prop management and enhance user experience. - Improved error handling in local storage operations within useMapBookmarks and useRecentLocations composables. - Standardized input elements across forms for consistency in user interaction.
131 lines
4.2 KiB
TypeScript
131 lines
4.2 KiB
TypeScript
import type L from 'leaflet'
|
|
import { getColorForCharacterId, type CharacterColors } from '~/lib/characterColors'
|
|
import { HnHMaxZoom } from '~/lib/LeafletCustomTypes'
|
|
|
|
export type LeafletApi = L
|
|
|
|
function buildCharacterIconUrl(colors: CharacterColors): string {
|
|
const svg =
|
|
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 32" width="24" height="32">' +
|
|
`<path fill="${colors.fill}" stroke="${colors.stroke}" stroke-width="1" d="M12 2a6 6 0 0 1 6 6c0 4-6 10-6 10s-6-6-6-10a6 6 0 0 1 6-6z"/>` +
|
|
'<circle cx="12" cy="8" r="2.5" fill="white"/>' +
|
|
'</svg>'
|
|
return 'data:image/svg+xml,' + encodeURIComponent(svg)
|
|
}
|
|
|
|
export function createCharacterIcon(L: LeafletApi, colors: CharacterColors): L.Icon {
|
|
return new L.Icon({
|
|
iconUrl: buildCharacterIconUrl(colors),
|
|
iconSize: [24, 32],
|
|
iconAnchor: [12, 32],
|
|
popupAnchor: [0, -32],
|
|
})
|
|
}
|
|
|
|
export interface CharacterData {
|
|
name: string
|
|
position: { x: number; y: number }
|
|
type: string
|
|
id: number
|
|
map: number
|
|
/** True when this character was last updated by one of the current user's tokens. */
|
|
ownedByMe?: boolean
|
|
}
|
|
|
|
export interface CharacterMapViewRef {
|
|
map: L.Map
|
|
mapid: number
|
|
markerLayer?: L.LayerGroup
|
|
}
|
|
|
|
export interface MapCharacter {
|
|
id: number
|
|
name: string
|
|
position: { x: number; y: number }
|
|
type: string
|
|
map: number
|
|
text: string
|
|
value: number
|
|
ownedByMe?: boolean
|
|
leafletMarker: L.Marker | null
|
|
remove: (mapview: CharacterMapViewRef) => void
|
|
add: (mapview: CharacterMapViewRef) => void
|
|
update: (mapview: CharacterMapViewRef, updated: CharacterData | MapCharacter) => void
|
|
setClickCallback: (callback: (e: L.LeafletMouseEvent) => void) => void
|
|
}
|
|
|
|
export function createCharacter(data: CharacterData, L: LeafletApi): MapCharacter {
|
|
let leafletMarker: L.Marker | null = null
|
|
let onClick: ((e: L.LeafletMouseEvent) => void) | null = null
|
|
let ownedByMe = data.ownedByMe ?? false
|
|
const colors = getColorForCharacterId(data.id, { ownedByMe })
|
|
let characterIcon = createCharacterIcon(L, colors)
|
|
|
|
const character: MapCharacter = {
|
|
id: data.id,
|
|
name: data.name,
|
|
position: { ...data.position },
|
|
type: data.type,
|
|
map: data.map,
|
|
text: data.name,
|
|
value: data.id,
|
|
get ownedByMe() {
|
|
return ownedByMe
|
|
},
|
|
set ownedByMe(v: boolean | undefined) {
|
|
ownedByMe = v ?? false
|
|
},
|
|
|
|
get leafletMarker() {
|
|
return leafletMarker
|
|
},
|
|
|
|
remove(mapview: CharacterMapViewRef): void {
|
|
if (leafletMarker) {
|
|
const layer = mapview.markerLayer ?? mapview.map
|
|
layer.removeLayer(leafletMarker)
|
|
leafletMarker = null
|
|
}
|
|
},
|
|
|
|
add(mapview: CharacterMapViewRef): void {
|
|
if (character.map === mapview.mapid) {
|
|
const position = mapview.map.unproject([character.position.x, character.position.y], HnHMaxZoom)
|
|
leafletMarker = L.marker(position, { icon: characterIcon, title: character.name })
|
|
leafletMarker.on('click', (e: L.LeafletMouseEvent) => {
|
|
if (onClick) onClick(e)
|
|
})
|
|
const targetLayer = mapview.markerLayer ?? mapview.map
|
|
leafletMarker.addTo(targetLayer)
|
|
}
|
|
},
|
|
|
|
update(mapview: CharacterMapViewRef, updated: CharacterData | MapCharacter): void {
|
|
const updatedOwnedByMe = (updated as { ownedByMe?: boolean }).ownedByMe ?? false
|
|
if (ownedByMe !== updatedOwnedByMe) {
|
|
ownedByMe = updatedOwnedByMe
|
|
characterIcon = createCharacterIcon(L, getColorForCharacterId(character.id, { ownedByMe }))
|
|
if (leafletMarker) leafletMarker.setIcon(characterIcon)
|
|
}
|
|
if (character.map !== updated.map) {
|
|
character.remove(mapview)
|
|
}
|
|
character.map = updated.map
|
|
character.position = { ...updated.position }
|
|
if (!leafletMarker && character.map === mapview.mapid) {
|
|
character.add(mapview)
|
|
}
|
|
if (leafletMarker) {
|
|
const position = mapview.map.unproject([updated.position.x, updated.position.y], HnHMaxZoom)
|
|
leafletMarker.setLatLng(position)
|
|
}
|
|
},
|
|
|
|
setClickCallback(callback: (e: L.LeafletMouseEvent) => void): void {
|
|
onClick = callback
|
|
},
|
|
}
|
|
|
|
return character
|
|
}
|