Implement error handling and visual enhancements in map components
- Introduced MapErrorBoundary component to handle map loading errors gracefully. - Enhanced MapView with a reconnection status indicator for live updates. - Added tile freshness animation to indicate updated tiles visually. - Preloaded marker icon images to improve rendering performance. - Updated various pages to utilize the new MapErrorBoundary for better user experience.
This commit is contained in:
@@ -6,6 +6,31 @@ import type { MapInfo } from '~/types/api'
|
||||
|
||||
type SmartTileLayerInstance = InstanceType<typeof SmartTileLayer>
|
||||
|
||||
/** Known marker icon paths (without .png) to preload so markers render without broken images. */
|
||||
const MARKER_ICON_PATHS = [
|
||||
'gfx/terobjs/mm/custom',
|
||||
'gfx/terobjs/mm/tower',
|
||||
'gfx/terobjs/mm/village',
|
||||
'gfx/terobjs/mm/dungeon',
|
||||
'gfx/terobjs/mm/cave',
|
||||
'gfx/terobjs/mm/settlement',
|
||||
'gfx/invobjs/small/bush',
|
||||
'gfx/invobjs/small/bumling',
|
||||
]
|
||||
|
||||
/**
|
||||
* Preloads marker icon images so they are in the browser cache before markers render.
|
||||
* Call from client only. resolvePath should produce absolute URLs for static assets.
|
||||
*/
|
||||
export function preloadMarkerIcons(resolvePath: (path: string) => string): void {
|
||||
if (import.meta.server) return
|
||||
for (const base of MARKER_ICON_PATHS) {
|
||||
const url = resolvePath(`${base}.png`)
|
||||
const img = new Image()
|
||||
img.src = url
|
||||
}
|
||||
}
|
||||
|
||||
export interface MapInitResult {
|
||||
map: L.Map
|
||||
layer: SmartTileLayerInstance
|
||||
@@ -87,5 +112,11 @@ export async function initLeafletMap(
|
||||
const markerIconPath = baseURL.endsWith('/') ? baseURL : baseURL + '/'
|
||||
L.Icon.Default.imagePath = markerIconPath
|
||||
|
||||
const resolvePath = (path: string) => {
|
||||
const p = path.startsWith('/') ? path : `/${path}`
|
||||
return baseURL === '/' ? p : `${baseURL.replace(/\/$/, '')}${p}`
|
||||
}
|
||||
preloadMarkerIcons(resolvePath)
|
||||
|
||||
return { map, layer, overlayLayer, coordLayer, markerLayer, backendBase }
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import type { Ref } from 'vue'
|
||||
import type { SmartTileLayer } from '~/lib/SmartTileLayer'
|
||||
import { TileSize } from '~/lib/LeafletCustomTypes'
|
||||
import type L from 'leaflet'
|
||||
|
||||
type SmartTileLayerInstance = InstanceType<typeof SmartTileLayer>
|
||||
|
||||
export type SseConnectionState = 'connecting' | 'open' | 'error'
|
||||
|
||||
interface TileUpdate {
|
||||
M: number
|
||||
X: number
|
||||
@@ -25,6 +28,8 @@ export interface UseMapUpdatesOptions {
|
||||
map: L.Map
|
||||
getCurrentMapId: () => number
|
||||
onMerge: (mapTo: number, shift: { x: number; y: number }) => void
|
||||
/** Optional ref updated with SSE connection state for reconnection indicator. */
|
||||
connectionStateRef?: Ref<SseConnectionState>
|
||||
}
|
||||
|
||||
export interface UseMapUpdatesReturn {
|
||||
@@ -32,13 +37,24 @@ export interface UseMapUpdatesReturn {
|
||||
}
|
||||
|
||||
export function startMapUpdates(options: UseMapUpdatesOptions): UseMapUpdatesReturn {
|
||||
const { backendBase, layer, overlayLayer, map, getCurrentMapId, onMerge } = options
|
||||
const { backendBase, layer, overlayLayer, map, getCurrentMapId, onMerge, connectionStateRef } = options
|
||||
|
||||
const updatesPath = `${backendBase}/updates`
|
||||
const updatesUrl = import.meta.client ? `${window.location.origin}${updatesPath}` : updatesPath
|
||||
const source = new EventSource(updatesUrl)
|
||||
|
||||
if (connectionStateRef) {
|
||||
connectionStateRef.value = 'connecting'
|
||||
}
|
||||
source.onopen = () => {
|
||||
if (connectionStateRef) connectionStateRef.value = 'open'
|
||||
}
|
||||
source.onerror = () => {
|
||||
if (connectionStateRef) connectionStateRef.value = 'error'
|
||||
}
|
||||
|
||||
source.onmessage = (event: MessageEvent) => {
|
||||
if (connectionStateRef) connectionStateRef.value = 'open'
|
||||
try {
|
||||
const raw: unknown = event?.data
|
||||
if (raw == null || typeof raw !== 'string' || raw.trim() === '') return
|
||||
|
||||
Reference in New Issue
Block a user