Enhance frontend UI and functionality
- Added page transition effects in app.vue for smoother navigation. - Updated nuxt.config.ts to include custom font styles and page transitions. - Improved loading indicators in MapPageWrapper.vue and login.vue for better user experience. - Enhanced MapView.vue with a collapsible control panel and improved styling. - Introduced new icons for various components to enhance visual consistency. - Updated Tailwind CSS configuration to extend font families and improve theme management. - Refined layout styles in default.vue and admin pages for better responsiveness and aesthetics. - Implemented error handling and loading states across various forms for improved user feedback.
This commit is contained in:
@@ -17,108 +17,138 @@
|
||||
<!-- Grid coords & zoom (bottom-right) -->
|
||||
<div
|
||||
v-if="displayCoords"
|
||||
class="absolute bottom-2 right-2 z-[501] rounded bg-base-100/90 px-2 py-1 font-mono text-xs shadow"
|
||||
class="absolute bottom-2 right-2 z-[501] rounded-lg px-3 py-2 font-mono text-sm bg-base-100/95 backdrop-blur-sm border border-base-300/50 shadow"
|
||||
aria-label="Current grid position and zoom"
|
||||
title="mapId · x, y · zoom"
|
||||
>
|
||||
{{ mapid }} · {{ displayCoords.x }}, {{ displayCoords.y }} · z{{ displayCoords.z }}
|
||||
</div>
|
||||
<!-- Control panel -->
|
||||
<div class="absolute left-3 top-[10%] z-[502] card card-compact bg-base-100 shadow-xl w-56">
|
||||
<div class="card-body p-3 gap-2">
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex items-center gap-1">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-neutral btn-sm btn-square"
|
||||
title="Zoom in"
|
||||
aria-label="Zoom in"
|
||||
@click="zoomIn"
|
||||
>
|
||||
+
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-neutral btn-sm btn-square"
|
||||
title="Zoom out"
|
||||
aria-label="Zoom out"
|
||||
@click="zoomOutControl"
|
||||
>
|
||||
−
|
||||
</button>
|
||||
</div>
|
||||
<label class="label cursor-pointer justify-start gap-2 py-0">
|
||||
<input v-model="showGridCoordinates" type="checkbox" class="checkbox checkbox-sm" />
|
||||
<span class="label-text">Show grid coordinates</span>
|
||||
</label>
|
||||
<label class="label cursor-pointer justify-start gap-2 py-0">
|
||||
<input v-model="hideMarkers" type="checkbox" class="checkbox checkbox-sm" />
|
||||
<span class="label-text">Hide markers</span>
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-neutral btn-sm"
|
||||
title="Center map and minimum zoom"
|
||||
@click="zoomOut"
|
||||
>
|
||||
Reset view
|
||||
</button>
|
||||
<div class="form-control">
|
||||
<label class="label py-0"><span class="label-text">Jump to Map</span></label>
|
||||
<select v-model="selectedMapId" class="select select-bordered select-sm w-full">
|
||||
<option :value="null">Select map</option>
|
||||
<option v-for="m in maps" :key="m.ID" :value="m.ID">{{ m.Name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label py-0"><span class="label-text">Overlay Map</span></label>
|
||||
<select v-model="overlayMapId" class="select select-bordered select-sm w-full">
|
||||
<option :value="-1">None</option>
|
||||
<option v-for="m in maps" :key="'overlay-' + m.ID" :value="m.ID">{{ m.Name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label py-0"><span class="label-text">Jump to Quest Giver</span></label>
|
||||
<select v-model="selectedMarkerId" class="select select-bordered select-sm w-full">
|
||||
<option :value="null">Select quest giver</option>
|
||||
<option v-for="q in questGivers" :key="q.id" :value="q.id">{{ q.name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label py-0"><span class="label-text">Jump to Player</span></label>
|
||||
<select v-model="selectedPlayerId" class="select select-bordered select-sm w-full">
|
||||
<option :value="null">Select player</option>
|
||||
<option v-for="p in players" :key="p.id" :value="p.id">{{ p.name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div
|
||||
class="absolute left-3 top-[10%] z-[502] flex transition-all duration-300 ease-out"
|
||||
:class="panelCollapsed ? 'w-12' : 'w-64'"
|
||||
>
|
||||
<div
|
||||
class="rounded-xl bg-base-100/80 backdrop-blur-xl border border-base-300/50 shadow-xl overflow-hidden transition-all duration-300 flex flex-col"
|
||||
:class="panelCollapsed ? 'w-12 items-center py-2' : 'w-56'"
|
||||
>
|
||||
<div v-show="!panelCollapsed" class="flex flex-col p-4 gap-4 flex-1 min-w-0">
|
||||
<!-- Zoom -->
|
||||
<section class="flex flex-col gap-2">
|
||||
<h3 class="text-xs font-semibold uppercase tracking-wider text-base-content/70">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"
|
||||
title="Zoom in"
|
||||
aria-label="Zoom in"
|
||||
@click="zoomIn"
|
||||
>
|
||||
<icons-icon-zoom-in />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline btn-sm btn-square transition-all duration-200 hover:scale-105"
|
||||
title="Zoom out"
|
||||
aria-label="Zoom out"
|
||||
@click="zoomOutControl"
|
||||
>
|
||||
<icons-icon-zoom-out />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline btn-sm btn-square transition-all duration-200 hover:scale-105"
|
||||
title="Reset view — center map and minimum zoom"
|
||||
aria-label="Reset view"
|
||||
@click="zoomOut"
|
||||
>
|
||||
<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">Display</h3>
|
||||
<label class="label cursor-pointer justify-start gap-2 py-0 hover:bg-base-200/50 rounded-lg px-2 -mx-2">
|
||||
<input v-model="showGridCoordinates" type="checkbox" class="checkbox checkbox-sm" />
|
||||
<span class="label-text">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">
|
||||
<input v-model="hideMarkers" type="checkbox" class="checkbox checkbox-sm" />
|
||||
<span class="label-text">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">Navigation</h3>
|
||||
<div class="form-control">
|
||||
<label class="label py-0"><span class="label-text">Jump to Map</span></label>
|
||||
<select v-model="selectedMapId" class="select select-bordered select-sm w-full focus:ring-2 focus:ring-primary">
|
||||
<option :value="null">Select map</option>
|
||||
<option v-for="m in maps" :key="m.ID" :value="m.ID">{{ m.Name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label py-0"><span class="label-text">Overlay Map</span></label>
|
||||
<select v-model="overlayMapId" class="select select-bordered select-sm w-full focus:ring-2 focus:ring-primary">
|
||||
<option :value="-1">None</option>
|
||||
<option v-for="m in maps" :key="'overlay-' + m.ID" :value="m.ID">{{ m.Name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label py-0"><span class="label-text">Jump to Quest Giver</span></label>
|
||||
<select v-model="selectedMarkerId" class="select select-bordered select-sm w-full focus:ring-2 focus:ring-primary">
|
||||
<option :value="null">Select quest giver</option>
|
||||
<option v-for="q in questGivers" :key="q.id" :value="q.id">{{ q.name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label py-0"><span class="label-text">Jump to Player</span></label>
|
||||
<select v-model="selectedPlayerId" class="select select-bordered select-sm w-full focus:ring-2 focus:ring-primary">
|
||||
<option :value="null">Select player</option>
|
||||
<option v-for="p in players" :key="p.id" :value="p.id">{{ p.name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-ghost btn-sm btn-square shrink-0 m-1 transition-all duration-200 hover:scale-105"
|
||||
:title="panelCollapsed ? 'Expand panel' : 'Collapse panel'"
|
||||
:aria-label="panelCollapsed ? 'Expand panel' : 'Collapse panel'"
|
||||
@click="panelCollapsed = !panelCollapsed"
|
||||
>
|
||||
<icons-icon-chevron-right v-if="panelCollapsed" />
|
||||
<icons-icon-panel-left v-else />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Context menu (tile) -->
|
||||
<div
|
||||
v-show="contextMenu.tile.show"
|
||||
class="fixed z-[1000] bg-base-100 shadow-lg rounded-lg border border-base-300 py-1 min-w-[180px]"
|
||||
class="fixed z-[1000] bg-base-100/95 backdrop-blur-xl shadow-xl rounded-lg border border-base-300 py-1 min-w-[180px] transition-opacity duration-150"
|
||||
:style="{ left: contextMenu.tile.x + 'px', top: contextMenu.tile.y + 'px' }"
|
||||
>
|
||||
<button type="button" class="btn btn-ghost btn-sm w-full justify-start" @click="contextMenu.tile.data && (wipeTile(contextMenu.tile.data), (contextMenu.tile.show = false))">
|
||||
<button type="button" class="btn btn-ghost btn-sm w-full justify-start hover:bg-base-200 transition-colors" @click="contextMenu.tile.data && (wipeTile(contextMenu.tile.data), (contextMenu.tile.show = false))">
|
||||
Wipe tile {{ contextMenu.tile.data?.coords?.x }}, {{ contextMenu.tile.data?.coords?.y }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-ghost btn-sm w-full justify-start" @click="contextMenu.tile.data && (openCoordSet(contextMenu.tile.data), (contextMenu.tile.show = false))">
|
||||
<button type="button" class="btn btn-ghost btn-sm w-full justify-start hover:bg-base-200 transition-colors" @click="contextMenu.tile.data && (openCoordSet(contextMenu.tile.data), (contextMenu.tile.show = false))">
|
||||
Rewrite tile coords
|
||||
</button>
|
||||
</div>
|
||||
<!-- Context menu (marker) -->
|
||||
<div
|
||||
v-show="contextMenu.marker.show"
|
||||
class="fixed z-[1000] bg-base-100 shadow-lg rounded-lg border border-base-300 py-1 min-w-[180px]"
|
||||
class="fixed z-[1000] bg-base-100/95 backdrop-blur-xl shadow-xl rounded-lg border border-base-300 py-1 min-w-[180px] transition-opacity duration-150"
|
||||
:style="{ left: contextMenu.marker.x + 'px', top: contextMenu.marker.y + 'px' }"
|
||||
>
|
||||
<button type="button" class="btn btn-ghost btn-sm w-full justify-start" @click="contextMenu.marker.data?.id != null && (hideMarkerById(contextMenu.marker.data.id), (contextMenu.marker.show = false))">
|
||||
<button type="button" class="btn btn-ghost btn-sm w-full justify-start hover:bg-base-200 transition-colors" @click="contextMenu.marker.data?.id != null && (hideMarkerById(contextMenu.marker.data.id), (contextMenu.marker.show = false))">
|
||||
Hide marker {{ contextMenu.marker.data?.name }}
|
||||
</button>
|
||||
</div>
|
||||
<!-- Coord-set modal: close via Cancel, backdrop click, or Escape -->
|
||||
<dialog ref="coordSetModal" class="modal" @cancel="closeCoordSetModal">
|
||||
<div class="modal-box" @click.stop>
|
||||
<div class="modal-box transition-all duration-200" @click.stop>
|
||||
<h3 class="font-bold text-lg">Rewrite tile coords</h3>
|
||||
<p class="py-2">From ({{ coordSetFrom.x }}, {{ coordSetFrom.y }}) to:</p>
|
||||
<div class="flex gap-2">
|
||||
@@ -162,6 +192,7 @@ const api = useMapApi()
|
||||
|
||||
const showGridCoordinates = ref(false)
|
||||
const hideMarkers = ref(false)
|
||||
const panelCollapsed = ref(false)
|
||||
const trackingCharacterId = ref(-1)
|
||||
const maps = ref<{ ID: number; Name: string; size?: number }[]>([])
|
||||
const mapsLoaded = ref(false)
|
||||
@@ -361,6 +392,7 @@ onMounted(async () => {
|
||||
maxZoom: HnHMaxZoom,
|
||||
opacity: 0,
|
||||
visible: false,
|
||||
pane: 'tilePane',
|
||||
} as any)
|
||||
coordLayer.addTo(map)
|
||||
coordLayer.setZIndex(500)
|
||||
|
||||
Reference in New Issue
Block a user