Enhance frontend components and introduce new features
- Added a custom light theme in app.css to match the dark theme's palette. - Introduced AdminBreadcrumbs component for improved navigation in admin pages. - Implemented Skeleton component for loading states in various views. - Added ToastContainer for displaying notifications and alerts. - Enhanced MapView with loading indicators and improved marker handling. - Updated MapCoordsDisplay to allow copying of shareable links. - Refactored MapControls and MapContextMenu for better usability. - Improved user experience in profile and admin pages with loading states and search functionality.
This commit is contained in:
@@ -26,6 +26,10 @@ export interface MapLayersOptions {
|
||||
getTrackingCharacterId: () => number
|
||||
setTrackingCharacterId: (id: number) => void
|
||||
onMarkerContextMenu: (clientX: number, clientY: number, id: number, name: string) => void
|
||||
/** Resolves relative marker icon path to absolute URL. If omitted, relative paths are used. */
|
||||
resolveIconUrl?: (path: string) => string
|
||||
/** Fallback icon URL when a marker image fails to load. */
|
||||
fallbackIconUrl?: string
|
||||
}
|
||||
|
||||
export interface MapLayersManager {
|
||||
@@ -55,6 +59,8 @@ export function createMapLayers(options: MapLayersOptions): MapLayersManager {
|
||||
getTrackingCharacterId,
|
||||
setTrackingCharacterId,
|
||||
onMarkerContextMenu,
|
||||
resolveIconUrl,
|
||||
fallbackIconUrl,
|
||||
} = options
|
||||
|
||||
const markers = createUniqueList<MapMarker>()
|
||||
@@ -93,9 +99,13 @@ export function createMapLayers(options: MapLayersOptions): MapLayersManager {
|
||||
function updateMarkers(markersData: ApiMarker[]) {
|
||||
const list = Array.isArray(markersData) ? markersData : []
|
||||
const ctx = markerCtx()
|
||||
const iconOptions =
|
||||
resolveIconUrl != null
|
||||
? { resolveIconUrl, fallbackIconUrl }
|
||||
: undefined
|
||||
uniqueListUpdate(
|
||||
markers,
|
||||
list.map((it) => createMarker(it as MarkerData)),
|
||||
list.map((it) => createMarker(it as MarkerData, iconOptions)),
|
||||
(marker: MapMarker) => {
|
||||
if (marker.map === getCurrentMapId() || marker.map === overlayLayer.map) marker.add(ctx)
|
||||
marker.setClickCallback(() => {
|
||||
|
||||
36
frontend-nuxt/composables/useToast.ts
Normal file
36
frontend-nuxt/composables/useToast.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
export type ToastType = 'success' | 'error' | 'info'
|
||||
|
||||
export interface Toast {
|
||||
id: number
|
||||
type: ToastType
|
||||
text: string
|
||||
}
|
||||
|
||||
const TOAST_STATE_KEY = 'hnh-map-toasts'
|
||||
const DEFAULT_DURATION_MS = 4000
|
||||
|
||||
let nextId = 0
|
||||
|
||||
export function useToast() {
|
||||
const toasts = useState<Toast[]>(TOAST_STATE_KEY, () => [])
|
||||
|
||||
function dismiss(id: number) {
|
||||
toasts.value = toasts.value.filter((t) => t.id !== id)
|
||||
}
|
||||
|
||||
function show(type: ToastType, text: string, durationMs = DEFAULT_DURATION_MS) {
|
||||
const id = ++nextId
|
||||
toasts.value = [...toasts.value, { id, type, text }]
|
||||
if (durationMs > 0 && import.meta.client) {
|
||||
setTimeout(() => dismiss(id), durationMs)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
toasts: readonly(toasts),
|
||||
success: (text: string, durationMs?: number) => show('success', text, durationMs),
|
||||
error: (text: string, durationMs?: number) => show('error', text, durationMs),
|
||||
info: (text: string, durationMs?: number) => show('info', text, durationMs),
|
||||
dismiss,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user