Files
hnh-map/frontend-nuxt/components/map/MapControlsContent.vue
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

191 lines
6.8 KiB
Vue

<template>
<div class="flex flex-col gap-4">
<!-- Search -->
<MapSearch
v-if="currentMapId != null && currentCoords != null"
:maps="maps"
:quest-givers="questGivers"
:current-map-id="currentMapId"
:current-coords="currentCoords"
:touch-friendly="touchFriendly"
@jump-to-marker="$emit('jumpToMarker', $event)"
/>
<!-- Zoom -->
<section class="flex flex-col gap-2">
<h3 class="text-xs font-semibold uppercase tracking-wider text-base-content/70 flex items-center gap-1.5">
<icons-icon-zoom-in class="size-3.5 opacity-80" aria-hidden="true" />
Zoom
</h3>
<div class="flex items-center gap-2">
<button
type="button"
class="btn btn-outline btn-sm btn-square transition-all duration-200 hover:scale-105 touch-manipulation"
:class="touchFriendly ? 'min-h-10 min-w-10' : ''"
title="Zoom in"
aria-label="Zoom in"
@click="$emit('zoomIn')"
>
<icons-icon-zoom-in />
</button>
<button
type="button"
class="btn btn-outline btn-sm btn-square transition-all duration-200 hover:scale-105 touch-manipulation"
:class="touchFriendly ? 'min-h-10 min-w-10' : ''"
title="Zoom out"
aria-label="Zoom out"
@click="$emit('zoomOut')"
>
<icons-icon-zoom-out />
</button>
<button
type="button"
class="btn btn-outline btn-sm btn-square transition-all duration-200 hover:scale-105 touch-manipulation"
:class="touchFriendly ? 'min-h-10 min-w-10' : ''"
title="Reset view — center map and minimum zoom"
aria-label="Reset view"
@click="$emit('resetView')"
>
<icons-icon-home />
</button>
</div>
</section>
<!-- Display -->
<section class="flex flex-col gap-2">
<h3 class="text-xs font-semibold uppercase tracking-wider text-base-content/70 flex items-center gap-1.5">
<icons-icon-eye class="size-3.5 opacity-80" aria-hidden="true" />
Display
</h3>
<label class="label cursor-pointer justify-start gap-2 py-0 hover:bg-base-200/50 rounded-lg px-2 -mx-2 touch-manipulation" :class="touchFriendly ? 'min-h-11' : ''">
<input v-model="hideMarkers" type="checkbox" class="checkbox checkbox-sm" />
<span>Hide markers</span>
</label>
</section>
<!-- Navigation -->
<section class="flex flex-col gap-3">
<h3 class="text-xs font-semibold uppercase tracking-wider text-base-content/70 flex items-center gap-1.5">
<icons-icon-map-pin class="size-3.5 opacity-80" aria-hidden="true" />
Navigation
</h3>
<fieldset class="fieldset">
<label class="label py-0"><span>Jump to Map</span></label>
<select
v-model="selectedMapIdSelect"
class="select select-sm w-full focus:ring-2 focus:ring-primary touch-manipulation"
:class="touchFriendly ? 'min-h-11 text-base' : ''"
>
<option value="">Select map</option>
<option v-for="m in maps" :key="m.ID" :value="String(m.ID)">{{ m.Name }}</option>
</select>
</fieldset>
<fieldset class="fieldset">
<label class="label py-0"><span>Overlay Map</span></label>
<select
v-model="overlayMapId"
class="select select-sm w-full focus:ring-2 focus:ring-primary touch-manipulation"
:class="touchFriendly ? 'min-h-11 text-base' : ''"
>
<option value="-1">None</option>
<option v-for="m in maps" :key="'overlay-' + m.ID" :value="String(m.ID)">{{ m.Name }}</option>
</select>
</fieldset>
<fieldset class="fieldset">
<label class="label py-0"><span>Jump to Quest Giver</span></label>
<select
v-model="selectedMarkerIdSelect"
class="select select-sm w-full focus:ring-2 focus:ring-primary touch-manipulation"
:class="touchFriendly ? 'min-h-11 text-base' : ''"
>
<option value="">Select quest giver</option>
<option v-for="q in questGivers" :key="q.id" :value="String(q.id)">{{ q.name }}</option>
</select>
</fieldset>
<fieldset class="fieldset">
<label class="label py-0"><span>Jump to Player</span></label>
<select
v-model="selectedPlayerIdSelect"
class="select select-sm w-full focus:ring-2 focus:ring-primary touch-manipulation"
:class="touchFriendly ? 'min-h-11 text-base' : ''"
>
<option value="">Select player</option>
<option v-for="p in players" :key="p.id" :value="String(p.id)">{{ p.name }}</option>
</select>
</fieldset>
</section>
<!-- Saved locations -->
<MapBookmarks
:maps="maps"
:current-map-id="currentMapId ?? null"
:current-coords="currentCoords ?? null"
:selected-marker-for-bookmark="selectedMarkerForBookmark ?? null"
:touch-friendly="touchFriendly"
/>
<p class="text-xs text-base-content/50 mt-2">
<kbd class="kbd kbd-xs">?</kbd> for shortcuts
</p>
</div>
</template>
<script setup lang="ts">
import type { MapInfo } from '~/types/api'
import type { SelectedMarkerForBookmark } from '~/components/map/MapBookmarks.vue'
import MapBookmarks from '~/components/map/MapBookmarks.vue'
interface QuestGiver {
id: number
name: string
}
interface Player {
id: number
name: string
}
const props = withDefaults(
defineProps<{
maps: MapInfo[]
questGivers: QuestGiver[]
players: Player[]
touchFriendly?: boolean
selectedMapIdSelect: string
overlayMapId: number
selectedMarkerIdSelect: string
selectedPlayerIdSelect: string
currentMapId?: number
currentCoords?: { x: number; y: number; z: number } | null
selectedMarkerForBookmark?: SelectedMarkerForBookmark
}>(),
{ touchFriendly: false, currentMapId: 0, currentCoords: null, selectedMarkerForBookmark: null }
)
const emit = defineEmits<{
zoomIn: []
zoomOut: []
resetView: []
jumpToMarker: [id: number]
'update:hideMarkers': [v: boolean]
'update:selectedMapIdSelect': [v: string]
'update:overlayMapId': [v: number]
'update:selectedMarkerIdSelect': [v: string]
'update:selectedPlayerIdSelect': [v: string]
}>()
const hideMarkers = defineModel<boolean>('hideMarkers', { required: true })
const selectedMapIdSelect = computed({
get: () => props.selectedMapIdSelect,
set: (v) => emit('update:selectedMapIdSelect', v),
})
const overlayMapId = computed({
get: () => String(props.overlayMapId),
set: (v) => emit('update:overlayMapId', v === '' ? -1 : Number(v)),
})
const selectedMarkerIdSelect = computed({
get: () => props.selectedMarkerIdSelect,
set: (v) => emit('update:selectedMarkerIdSelect', v),
})
const selectedPlayerIdSelect = computed({
get: () => props.selectedPlayerIdSelect,
set: (v) => emit('update:selectedPlayerIdSelect', v),
})
</script>