diff --git a/Makefile b/Makefile index 6a1ba5b..361d788 100644 --- a/Makefile +++ b/Makefile @@ -3,10 +3,10 @@ TOOLS_COMPOSE = docker compose -f docker-compose.tools.yml dev: - docker compose -f docker-compose.dev.yml up + docker compose -f docker-compose.dev.yml up --build build: - docker compose -f docker-compose.prod.yml build + docker compose -f docker-compose.prod.yml build --no-cache test: test-backend test-frontend diff --git a/frontend-nuxt/assets/css/leaflet-overrides.css b/frontend-nuxt/assets/css/leaflet-overrides.css index 1c23fa2..0fb9f94 100644 --- a/frontend-nuxt/assets/css/leaflet-overrides.css +++ b/frontend-nuxt/assets/css/leaflet-overrides.css @@ -21,3 +21,39 @@ .leaflet-tile.tile-fresh { animation: tile-fresh-glow 0.4s ease-out; } + +/* Leaflet tooltip: use theme colors (dark/light) */ +.leaflet-tooltip { + background-color: var(--color-base-100); + color: var(--color-base-content); + border-color: var(--color-base-300); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25); +} +.leaflet-tooltip-top:before { + border-top-color: var(--color-base-100); +} +.leaflet-tooltip-bottom:before { + border-bottom-color: var(--color-base-100); +} +.leaflet-tooltip-left:before { + border-left-color: var(--color-base-100); +} +.leaflet-tooltip-right:before { + border-right-color: var(--color-base-100); +} + +/* Leaflet popup: use theme colors (dark/light) */ +.leaflet-popup-content-wrapper, +.leaflet-popup-tip { + background: var(--color-base-100); + color: var(--color-base-content); + box-shadow: 0 3px 14px rgba(0, 0, 0, 0.35); +} +.leaflet-container a.leaflet-popup-close-button { + color: var(--color-base-content); + opacity: 0.7; +} +.leaflet-container a.leaflet-popup-close-button:hover, +.leaflet-container a.leaflet-popup-close-button:focus { + opacity: 1; +} diff --git a/frontend-nuxt/components/MapView.vue b/frontend-nuxt/components/MapView.vue index bf04242..5ede178 100644 --- a/frontend-nuxt/components/MapView.vue +++ b/frontend-nuxt/components/MapView.vue @@ -100,12 +100,15 @@ :maps="maps" :quest-givers="questGivers" :players="players" + :markers="allMarkers" + :current-zoom="currentZoom" :current-map-id="mapLogic.state.mapid.value" :current-coords="mapLogic.state.displayCoords.value" :selected-marker-for-bookmark="selectedMarkerForBookmark" @zoom-in="mapLogic.zoomIn(leafletMap)" @zoom-out="mapLogic.zoomOutControl(leafletMap)" @reset-view="mapLogic.resetView(leafletMap)" + @set-zoom="onSetZoom" @jump-to-marker="mapLogic.state.selectedMarkerId.value = $event" /> ([]) const mapsLoaded = ref(false) const questGivers = ref>([]) const players = ref>([]) +/** All markers from API for search suggestions (updated when markers load or on merge). */ +const allMarkers = ref([]) +/** Current map zoom level (1–6) for zoom slider. Updated on zoomend. */ +const currentZoom = ref(HnHDefaultZoom) /** Single source of truth: layout updates me, we derive auths for context menu. */ const me = useState('me', () => null) const auths = computed(() => me.value?.auths ?? []) @@ -347,6 +354,10 @@ function reloadPage() { if (import.meta.client) window.location.reload() } +function onSetZoom(z: number) { + if (leafletMap) leafletMap.setZoom(z) +} + function onKeydown(e: KeyboardEvent) { const target = e.target as HTMLElement const inInput = /^(INPUT|TEXTAREA|SELECT)$/.test(target?.tagName ?? '') @@ -462,6 +473,16 @@ onMounted(async () => { getTrackingCharacterId: () => mapLogic.state.trackingCharacterId.value, setTrackingCharacterId: (id: number) => { mapLogic.state.trackingCharacterId.value = id }, onMarkerContextMenu: mapLogic.openMarkerContextMenu, + onAddMarkerToBookmark: (markerId, getMarkerById) => { + const m = getMarkerById(markerId) + if (!m) return + openBookmarkModal(m.name, 'Add bookmark', { + kind: 'add', + mapId: m.map, + x: Math.floor(m.position.x / TileSize), + y: Math.floor(m.position.y / TileSize), + }) + }, resolveIconUrl: (path) => resolvePath(path), fallbackIconUrl: FALLBACK_MARKER_ICON, }) @@ -479,7 +500,9 @@ onMounted(async () => { layersManager!.changeMap(mapTo) api.getMarkers().then((body) => { if (!mounted) return - layersManager!.updateMarkers(Array.isArray(body) ? body : []) + const list = Array.isArray(body) ? body : [] + allMarkers.value = list + layersManager!.updateMarkers(list) questGivers.value = layersManager!.getQuestGivers() }) leafletMap!.setView(latLng, leafletMap!.getZoom()) @@ -530,7 +553,9 @@ onMounted(async () => { // Markers load asynchronously after map is visible. api.getMarkers().then((body) => { if (!mounted) return - layersManager!.updateMarkers(Array.isArray(body) ? body : []) + const list = Array.isArray(body) ? body : [] + allMarkers.value = list + layersManager!.updateMarkers(list) questGivers.value = layersManager!.getQuestGivers() updateSelectedMarkerForBookmark() }) @@ -650,6 +675,10 @@ onMounted(async () => { leafletMap.on('moveend', () => mapLogic.updateDisplayCoords(leafletMap)) mapLogic.updateDisplayCoords(leafletMap) + currentZoom.value = leafletMap.getZoom() + leafletMap.on('zoomend', () => { + if (leafletMap) currentZoom.value = leafletMap.getZoom() + }) leafletMap.on('drag', () => { mapLogic.state.trackingCharacterId.value = -1 }) diff --git a/frontend-nuxt/components/icons/IconCopy.vue b/frontend-nuxt/components/icons/IconCopy.vue new file mode 100644 index 0000000..e1b3cee --- /dev/null +++ b/frontend-nuxt/components/icons/IconCopy.vue @@ -0,0 +1,6 @@ + diff --git a/frontend-nuxt/components/icons/IconInfo.vue b/frontend-nuxt/components/icons/IconInfo.vue new file mode 100644 index 0000000..5b0310f --- /dev/null +++ b/frontend-nuxt/components/icons/IconInfo.vue @@ -0,0 +1,7 @@ + diff --git a/frontend-nuxt/components/map/MapBookmarks.vue b/frontend-nuxt/components/map/MapBookmarks.vue index 7b68713..0afb433 100644 --- a/frontend-nuxt/components/map/MapBookmarks.vue +++ b/frontend-nuxt/components/map/MapBookmarks.vue @@ -7,7 +7,7 @@