Enhance map functionality with bookmark features and UI improvements
- Introduced a new MapBookmarkNameModal for adding and editing bookmarks. - Updated MapView to manage selected markers for bookmarking and handle bookmark name submissions. - Enhanced MapContextMenu with an option to add markers to bookmarks. - Improved MapBookmarks component to support editing bookmark names and adding selected markers. - Refactored MapControls and MapControlsContent to integrate selected marker functionality for bookmarks. - Updated useMapBookmarks composable to include bookmark updating logic. - Removed unused grid coordinates toggle from the UI for a cleaner interface.
This commit is contained in:
74
frontend-nuxt/components/map/MapBookmarkNameModal.vue
Normal file
74
frontend-nuxt/components/map/MapBookmarkNameModal.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<dialog ref="modalRef" class="modal" @cancel="$emit('close')">
|
||||
<div class="modal-box transition-all duration-200" @click.stop>
|
||||
<h3 class="font-bold text-lg">{{ title }}</h3>
|
||||
<div class="py-2">
|
||||
<label class="label py-0"><span>Name</span></label>
|
||||
<input
|
||||
ref="inputRef"
|
||||
v-model="localName"
|
||||
type="text"
|
||||
class="input input-bordered w-full"
|
||||
placeholder="Bookmark name"
|
||||
@keydown.enter.prevent="onSubmit"
|
||||
/>
|
||||
</div>
|
||||
<div class="modal-action">
|
||||
<form method="dialog" @submit.prevent="onSubmit">
|
||||
<button type="submit" class="btn btn-primary">Save</button>
|
||||
<button type="button" class="btn" @click="$emit('close')">Cancel</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-backdrop cursor-pointer" aria-label="Close" @click="$emit('close')" />
|
||||
</dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
open: boolean
|
||||
defaultName: string
|
||||
title?: string
|
||||
}>(),
|
||||
{ title: 'Add bookmark' }
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
close: []
|
||||
submit: [name: string]
|
||||
}>()
|
||||
|
||||
const modalRef = ref<HTMLDialogElement | null>(null)
|
||||
const inputRef = ref<HTMLInputElement | null>(null)
|
||||
const localName = ref(props.defaultName)
|
||||
|
||||
watch(
|
||||
() => props.open,
|
||||
(open) => {
|
||||
if (open) {
|
||||
localName.value = props.defaultName || ''
|
||||
nextTick(() => {
|
||||
modalRef.value?.showModal()
|
||||
inputRef.value?.focus()
|
||||
})
|
||||
} else {
|
||||
modalRef.value?.close()
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.defaultName,
|
||||
(name) => {
|
||||
if (props.open) localName.value = name || ''
|
||||
}
|
||||
)
|
||||
|
||||
function onSubmit() {
|
||||
const name = localName.value.trim() || props.defaultName || 'Bookmark'
|
||||
emit('submit', name)
|
||||
emit('close')
|
||||
}
|
||||
</script>
|
||||
@@ -22,6 +22,15 @@
|
||||
>
|
||||
<span class="truncate">{{ b.name }}</span>
|
||||
</button>
|
||||
<button
|
||||
v-if="openBookmarkModal"
|
||||
type="button"
|
||||
class="btn btn-ghost btn-xs btn-square shrink-0 opacity-70 hover:opacity-100"
|
||||
aria-label="Edit bookmark name"
|
||||
@click="openBookmarkModal(b.name, 'Edit bookmark name', { kind: 'edit', editId: b.id })"
|
||||
>
|
||||
<icons-icon-pencil class="size-3.5" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-ghost btn-xs btn-square shrink-0 opacity-70 hover:opacity-100 hover:text-error"
|
||||
@@ -33,17 +42,30 @@
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline btn-sm w-full"
|
||||
:class="touchFriendly ? 'min-h-11' : ''"
|
||||
:disabled="!canAddCurrent"
|
||||
title="Save current map position"
|
||||
@click="onAddCurrent"
|
||||
>
|
||||
<icons-icon-plus class="size-4" />
|
||||
Add current location
|
||||
</button>
|
||||
<div v-if="openBookmarkModal" class="flex flex-col gap-1">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary btn-sm w-full"
|
||||
:class="touchFriendly ? 'min-h-11' : ''"
|
||||
:disabled="!selectedMarkerForBookmark"
|
||||
title="Add selected quest giver as bookmark"
|
||||
@click="onAddSelectedMarker"
|
||||
>
|
||||
<icons-icon-plus class="size-4" />
|
||||
Add selected marker
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline btn-sm w-full"
|
||||
:class="touchFriendly ? 'min-h-11' : ''"
|
||||
:disabled="!canAddCurrent"
|
||||
title="Save current map position"
|
||||
@click="onAddCurrent"
|
||||
>
|
||||
<icons-icon-plus class="size-4" />
|
||||
Add current location
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -52,18 +74,27 @@ import type { MapInfo } from '~/types/api'
|
||||
import { useMapBookmarks } from '~/composables/useMapBookmarks'
|
||||
import { useMapNavigate } from '~/composables/useMapNavigate'
|
||||
|
||||
export type SelectedMarkerForBookmark = { mapId: number; x: number; y: number; name: string } | null
|
||||
|
||||
type BookmarkModalPayload =
|
||||
| { kind: 'add'; mapId: number; x: number; y: number; zoom?: number }
|
||||
| { kind: 'edit'; editId: string }
|
||||
type OpenBookmarkModalFn = (defaultName: string, title: string, data: BookmarkModalPayload) => void
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
maps: MapInfo[]
|
||||
currentMapId: number | null
|
||||
currentCoords: { x: number; y: number; z: number } | null
|
||||
selectedMarkerForBookmark?: SelectedMarkerForBookmark
|
||||
touchFriendly?: boolean
|
||||
}>(),
|
||||
{ touchFriendly: false }
|
||||
{ selectedMarkerForBookmark: null, touchFriendly: false }
|
||||
)
|
||||
|
||||
const { bookmarks, add, remove } = useMapBookmarks()
|
||||
const { bookmarks, remove } = useMapBookmarks()
|
||||
const { goToCoords } = useMapNavigate()
|
||||
const openBookmarkModal = inject<OpenBookmarkModalFn>('openBookmarkModal')
|
||||
|
||||
const canAddCurrent = computed(
|
||||
() =>
|
||||
@@ -80,17 +111,20 @@ function onRemove(id: string) {
|
||||
remove(id)
|
||||
}
|
||||
|
||||
function onAddSelectedMarker() {
|
||||
const m = props.selectedMarkerForBookmark
|
||||
if (!m || !openBookmarkModal) return
|
||||
openBookmarkModal(m.name, 'Add bookmark', { kind: 'add', mapId: m.mapId, x: m.x, y: m.y })
|
||||
}
|
||||
|
||||
function onAddCurrent() {
|
||||
if (!canAddCurrent.value) return
|
||||
if (!canAddCurrent.value || !openBookmarkModal) return
|
||||
const mapId = props.currentMapId!
|
||||
const { x, y, z } = props.currentCoords!
|
||||
const mapName = props.maps.find((m) => m.ID === mapId)?.Name ?? `Map ${mapId}`
|
||||
add({
|
||||
name: `${mapName} ${x}, ${y}`,
|
||||
mapId,
|
||||
x,
|
||||
y,
|
||||
zoom: z,
|
||||
})
|
||||
openBookmarkModal(
|
||||
`${props.maps.find((map) => map.ID === mapId)?.Name ?? `Map ${mapId}`} ${x}, ${y}`,
|
||||
'Add bookmark',
|
||||
{ kind: 'add', mapId, x, y, zoom: z }
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -28,6 +28,14 @@
|
||||
:style="{ left: contextMenu.marker.x + 'px', top: contextMenu.marker.y + 'px' }"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-ghost btn-sm w-full justify-start hover:bg-base-200 transition-colors"
|
||||
@click="onAddToBookmarks"
|
||||
>
|
||||
Add to bookmarks
|
||||
</button>
|
||||
<button
|
||||
v-if="isAdmin"
|
||||
type="button"
|
||||
class="btn btn-ghost btn-sm w-full justify-start hover:bg-base-200 transition-colors"
|
||||
@click="onHideMarker(contextMenu.marker.data?.id)"
|
||||
@@ -41,14 +49,19 @@
|
||||
<script setup lang="ts">
|
||||
import type { ContextMenuState } from '~/composables/useMapLogic'
|
||||
|
||||
defineProps<{
|
||||
contextMenu: ContextMenuState
|
||||
}>()
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
contextMenu: ContextMenuState
|
||||
isAdmin?: boolean
|
||||
}>(),
|
||||
{ isAdmin: false }
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
wipeTile: [coords: { x: number; y: number } | undefined]
|
||||
rewriteCoords: [coords: { x: number; y: number } | undefined]
|
||||
hideMarker: [id: number | undefined]
|
||||
addToBookmarks: []
|
||||
}>()
|
||||
|
||||
function onWipeTile(coords: { x: number; y: number } | undefined) {
|
||||
@@ -59,6 +72,10 @@ function onRewriteCoords(coords: { x: number; y: number } | undefined) {
|
||||
if (coords) emit('rewriteCoords', coords)
|
||||
}
|
||||
|
||||
function onAddToBookmarks() {
|
||||
emit('addToBookmarks')
|
||||
}
|
||||
|
||||
function onHideMarker(id: number | undefined) {
|
||||
if (id != null) emit('hideMarker', id)
|
||||
}
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
class="flex flex-col p-4 gap-4 flex-1 min-w-0 overflow-hidden"
|
||||
>
|
||||
<MapControlsContent
|
||||
v-model:show-grid-coordinates="showGridCoordinates"
|
||||
v-model:hide-markers="hideMarkers"
|
||||
:selected-map-id-select="selectedMapIdSelect"
|
||||
@update:selected-map-id-select="(v) => (selectedMapIdSelect = v)"
|
||||
@@ -52,6 +51,7 @@
|
||||
:players="players"
|
||||
:current-map-id="currentMapId ?? undefined"
|
||||
:current-coords="currentCoords"
|
||||
:selected-marker-for-bookmark="selectedMarkerForBookmark"
|
||||
@zoom-in="$emit('zoomIn')"
|
||||
@zoom-out="$emit('zoomOut')"
|
||||
@reset-view="$emit('resetView')"
|
||||
@@ -128,7 +128,6 @@
|
||||
</div>
|
||||
<div class="overflow-y-auto overscroll-contain flex-1 p-4 pb-8">
|
||||
<MapControlsContent
|
||||
v-model:show-grid-coordinates="showGridCoordinates"
|
||||
v-model:hide-markers="hideMarkers"
|
||||
:selected-map-id-select="selectedMapIdSelect"
|
||||
@update:selected-map-id-select="(v) => (selectedMapIdSelect = v)"
|
||||
@@ -143,6 +142,7 @@
|
||||
:players="players"
|
||||
:current-map-id="currentMapId ?? undefined"
|
||||
:current-coords="currentCoords"
|
||||
:selected-marker-for-bookmark="selectedMarkerForBookmark"
|
||||
:touch-friendly="true"
|
||||
@zoom-in="$emit('zoomIn')"
|
||||
@zoom-out="$emit('zoomOut')"
|
||||
@@ -167,6 +167,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { MapInfo } from '~/types/api'
|
||||
import type { SelectedMarkerForBookmark } from '~/components/map/MapBookmarks.vue'
|
||||
import MapControlsContent from '~/components/map/MapControlsContent.vue'
|
||||
|
||||
interface QuestGiver {
|
||||
@@ -186,8 +187,16 @@ const props = withDefaults(
|
||||
players: Player[]
|
||||
currentMapId?: number | null
|
||||
currentCoords?: { x: number; y: number; z: number } | null
|
||||
selectedMarkerForBookmark?: SelectedMarkerForBookmark
|
||||
}>(),
|
||||
{ maps: () => [], questGivers: () => [], players: () => [], currentMapId: null, currentCoords: null }
|
||||
{
|
||||
maps: () => [],
|
||||
questGivers: () => [],
|
||||
players: () => [],
|
||||
currentMapId: null,
|
||||
currentCoords: null,
|
||||
selectedMarkerForBookmark: null,
|
||||
}
|
||||
)
|
||||
|
||||
defineEmits<{
|
||||
@@ -197,7 +206,6 @@ defineEmits<{
|
||||
jumpToMarker: [id: number]
|
||||
}>()
|
||||
|
||||
const showGridCoordinates = defineModel<boolean>('showGridCoordinates', { default: false })
|
||||
const hideMarkers = defineModel<boolean>('hideMarkers', { default: false })
|
||||
const panelCollapsed = ref(false)
|
||||
const sheetOpen = ref(false)
|
||||
@@ -233,6 +241,8 @@ const selectedPlayerIdSelect = computed({
|
||||
selectedPlayerId.value = v === '' ? null : Number(v)
|
||||
},
|
||||
})
|
||||
|
||||
const selectedMarkerForBookmark = toRef(props, 'selectedMarkerForBookmark')
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -55,15 +55,6 @@
|
||||
<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="showGridCoordinates"
|
||||
type="checkbox"
|
||||
class="checkbox checkbox-sm"
|
||||
data-testid="show-grid-coordinates"
|
||||
/>
|
||||
<span>Show grid coordinates</span>
|
||||
</label>
|
||||
<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>
|
||||
@@ -125,6 +116,7 @@
|
||||
:maps="maps"
|
||||
:current-map-id="currentMapId ?? null"
|
||||
:current-coords="currentCoords ?? null"
|
||||
:selected-marker-for-bookmark="selectedMarkerForBookmark ?? null"
|
||||
:touch-friendly="touchFriendly"
|
||||
/>
|
||||
</div>
|
||||
@@ -132,6 +124,7 @@
|
||||
|
||||
<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 {
|
||||
@@ -156,8 +149,9 @@ const props = withDefaults(
|
||||
selectedPlayerIdSelect: string
|
||||
currentMapId?: number
|
||||
currentCoords?: { x: number; y: number; z: number } | null
|
||||
selectedMarkerForBookmark?: SelectedMarkerForBookmark
|
||||
}>(),
|
||||
{ touchFriendly: false, currentMapId: 0, currentCoords: null }
|
||||
{ touchFriendly: false, currentMapId: 0, currentCoords: null, selectedMarkerForBookmark: null }
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -165,7 +159,6 @@ const emit = defineEmits<{
|
||||
zoomOut: []
|
||||
resetView: []
|
||||
jumpToMarker: [id: number]
|
||||
'update:showGridCoordinates': [v: boolean]
|
||||
'update:hideMarkers': [v: boolean]
|
||||
'update:selectedMapIdSelect': [v: string]
|
||||
'update:overlayMapId': [v: number]
|
||||
@@ -173,7 +166,6 @@ const emit = defineEmits<{
|
||||
'update:selectedPlayerIdSelect': [v: string]
|
||||
}>()
|
||||
|
||||
const showGridCoordinates = defineModel<boolean>('showGridCoordinates', { required: true })
|
||||
const hideMarkers = defineModel<boolean>('hideMarkers', { required: true })
|
||||
|
||||
const selectedMapIdSelect = computed({
|
||||
|
||||
@@ -40,10 +40,6 @@
|
||||
<dt class="text-base-content/80"><kbd class="kbd kbd-sm">H</kbd></dt>
|
||||
<dd>Toggle markers visibility</dd>
|
||||
</div>
|
||||
<div class="flex justify-between gap-4 py-1 border-b border-base-300/50">
|
||||
<dt class="text-base-content/80"><kbd class="kbd kbd-sm">G</kbd></dt>
|
||||
<dd>Toggle grid coordinates</dd>
|
||||
</div>
|
||||
<div class="flex justify-between gap-4 py-1 border-b border-base-300/50">
|
||||
<dt class="text-base-content/80"><kbd class="kbd kbd-sm">F</kbd></dt>
|
||||
<dd>Focus search</dd>
|
||||
|
||||
Reference in New Issue
Block a user