diff --git a/ui/src/App.vue b/ui/src/App.vue
index 0a201db..6a5b928 100644
--- a/ui/src/App.vue
+++ b/ui/src/App.vue
@@ -30,7 +30,6 @@
v-model:imperialUnits="imperialUnits"
:visibleSites="visibleSites"
:allHistoricMarkersHidden="allHistoricMarkersHidden"
- :tileRequests="tileRequests"
@toggleSite="toggleSite"
@jumpToSite="jumpToSite"
@toggleAllSites="hideAllHistoricMarkers"
@@ -39,7 +38,6 @@
@update:lidarOpacity="updateLidarOpacity"
@update:showLidar="toggleLidar"
@update:showGeometry="toggleGeometry"
- @dismissRequest="dismissTileRequest"
/>
@@ -56,20 +54,12 @@
@@ -122,6 +112,7 @@ import { KNOWN_SITES } from './data/historicSites.js';
// UTILITIES
// ============================================================================
import { calculateDistance, calculateBearing, extendRay } from './utils/geometry.js';
+import { webMercatorToLonLat } from './utils/coordinates.js';
import { useTilesStore } from './stores/tiles.js';
// For generating the pre-baked tiles:
@@ -162,7 +153,7 @@ const sandboxRef = ref(null);
const sandboxVisible = ref(false);
const sandboxOffscreen = ref(false);
const baseLayer = ref('osm');
-const historicMarkersExpanded = ref(true);
+const historicMarkersExpanded = ref(false);
const visibleSites = ref({});
const allHistoricMarkersHidden = ref(false);
const showLidar = ref(true);
@@ -175,10 +166,6 @@ const lidarOpacity = ref(80);
// ============================================================================
const currentTileData = ref(null);
-// Tile request tracking: { requestId: { lat, lng, status, message, tileId } }
-const tileRequests = ref({});
-let nextRequestId = 1;
-
// ============================================================================
// REFS - Geometry State
// ============================================================================
@@ -191,16 +178,7 @@ const nextFeatureId = ref(1);
// ============================================================================
// REFS - UI Overlays
// ============================================================================
-const contextMenu = ref({
- visible: false,
- x: 0,
- y: 0,
- lngLat: null,
- tileData: null, // API tile metadata
- imagesLoaded: false, // JPG/PNG loaded on map
- moundLoaded: false, // .mound data in cache
- apiError: false // Failed to fetch tile info
-});
+const contextMenuRef = ref(null);
const popup = ref({
visible: false,
@@ -220,31 +198,6 @@ const highlightedCitation = ref(null);
let map = null;
let drawingHandler = null;
-// ============================================================================
-// COORDINATE UTILITIES
-// ============================================================================
-
-function webMercatorToLonLat(x, y) {
- const R = 6378137;
- const lon = (x / R) * (180 / Math.PI);
- const lat = (2 * Math.atan(Math.exp(y / R)) - Math.PI / 2) * (180 / Math.PI);
- return [lon, lat];
-}
-
-// ============================================================================
-// TILE UTILITIES
-// ============================================================================
-
-// Check if tile images are loaded on map
-function areTileImagesLoaded(tileId) {
- return tilesStore.areImagesOnMap(tileId);
-}
-
-// Check if mound data is cached
-function isMoundDataCached(tileId) {
- return tilesStore.hasMoundData(tileId);
-}
-
// ============================================================================
// FILE PARSING
// ============================================================================
@@ -296,13 +249,13 @@ function parseMoundBuffer(buffer) {
async function loadTileImages(tileId, tileMetadata, imageUrl = null) {
// If no explicit imageUrl provided (loading from server), skip if already loaded
- if (!imageUrl && areTileImagesLoaded(tileId)) {
+ if (!imageUrl && tilesStore.areImagesOnMap(tileId)) {
console.log(`Images for ${tileId} already loaded`);
return;
}
// If imageUrl is provided (sandbox render), allow overwriting
- const isOverwriting = imageUrl && areTileImagesLoaded(tileId);
+ const isOverwriting = imageUrl && tilesStore.areImagesOnMap(tileId);
try {
// Determine which format to load (prefer PNG over JPG)
@@ -381,157 +334,46 @@ async function loadInitialTiles() {
console.log(`Loaded ${tilesStore.getAllTileIdsWithImages.length} initial tiles`);
}
-async function loadMoundData(tileId) {
- if (isMoundDataCached(tileId)) {
- console.log(`Mound data for ${tileId} already cached`);
- return;
- }
-
- try {
- await tilesStore.fetchMoundData(tileId, parseMoundBuffer);
- console.log(`Loaded mound data for ${tileId}`);
- } catch (err) {
- console.error(`Failed to load mound data for ${tileId}:`, err);
- throw err;
- }
-}
-
// ============================================================================
-// CONTEXT MENU HANDLERS
+// CONTEXT MENU HANDLER
// ============================================================================
async function handleContextMenu(e) {
e.preventDefault();
- const lng = e.lngLat.lng;
- const lat = e.lngLat.lat;
-
- // Reset context menu state
- contextMenu.value = {
- visible: false,
- x: e.point.x,
- y: e.point.y,
- lngLat: { lng, lat },
- tileData: null,
- imagesLoaded: false,
- moundLoaded: false,
- apiError: false
- };
-
- // Try to fetch tile metadata from API
- try {
- const tileData = await tilesStore.fetchMetadataByCoords(lat, lng);
-
- if (tileData) {
- contextMenu.value.tileData = tileData;
- contextMenu.value.imagesLoaded = areTileImagesLoaded(tileData.id);
- contextMenu.value.moundLoaded = isMoundDataCached(tileData.id);
- }
- } catch (err) {
- console.error('Failed to fetch tile info:', err);
- contextMenu.value.apiError = true;
+ if (contextMenuRef.value) {
+ contextMenuRef.value.show(e.point.x, e.point.y, {
+ lng: e.lngLat.lng,
+ lat: e.lngLat.lat
+ });
}
-
- // Show context menu
- contextMenu.value.visible = true;
}
-async function requestTileFromContextMenu() {
- const { lng, lat } = contextMenu.value.lngLat;
- contextMenu.value.visible = false;
-
- const requestId = `req-${nextRequestId++}`;
-
- // Initialize request tracking
- tileRequests.value[requestId] = {
- lat,
- lng,
- status: 'looking_up',
- message: null,
- tileId: null
- };
-
- // Start SSE request
- tilesStore.requestTileProcessing(
- lat,
- lng,
- async (data) => {
- // Update request status
- tileRequests.value[requestId].status = data.status;
- tileRequests.value[requestId].message = data.message || null;
-
- if (data.tile_id) {
- tileRequests.value[requestId].tileId = data.tile_id;
- }
-
- // On ready: load mound and open sandbox
- if (data.status === 'ready' && data.tile_id) {
- try {
- await loadMoundData(data.tile_id);
- await openSandboxWithTile(data.tile_id);
- } catch (err) {
- console.error('Failed to load mound after request:', err);
- tileRequests.value[requestId].status = 'error';
- tileRequests.value[requestId].message = 'Failed to load data';
- }
- }
-
- // Auto-dismiss after 10 seconds if complete
- if (data.status === 'ready' || data.status === 'error') {
- setTimeout(() => {
- delete tileRequests.value[requestId];
- }, 10000);
- }
- },
- (error) => {
- console.error('Request tile error:', error);
- tileRequests.value[requestId].status = 'error';
- tileRequests.value[requestId].message = 'Connection failed';
-
- setTimeout(() => {
- delete tileRequests.value[requestId];
- }, 10000);
- }
- );
-}
+// ============================================================================
+// CONTEXT MENU - MAP OPERATION HANDLERS
+// ============================================================================
-async function loadImagesFromContextMenu() {
- const tileData = contextMenu.value.tileData;
- contextMenu.value.visible = false;
-
- if (tileData) {
+async function handleAddImagesToMap(tileId) {
+ const tileMetadata = tilesStore.getMetadata(tileId);
+ if (tileMetadata) {
try {
- await loadTileImages(tileData.id, tileData);
+ await loadTileImages(tileId, tileMetadata);
} catch (err) {
console.error('Failed to load images:', err);
}
}
}
-async function loadMoundFromContextMenu() {
- const tileData = contextMenu.value.tileData;
- contextMenu.value.visible = false;
-
- if (tileData) {
- try {
- await loadMoundData(tileData.id);
- } catch (err) {
- console.error('Failed to load mound data:', err);
- }
- }
+function handleOpenSandbox(tileId) {
+ openSandboxWithTile(tileId);
}
-async function openSandboxFromContextMenu() {
- const tileData = contextMenu.value.tileData;
- contextMenu.value.visible = false;
-
- if (tileData) {
- await openSandboxWithTile(tileData.id);
- }
+function handleDropPin(lngLat) {
+ dropPin(lngLat);
}
-function dismissTileRequest(requestId) {
- delete tileRequests.value[requestId];
+function handleStartMeasure(lngLat) {
+ startMeasure(lngLat);
}
// ============================================================================
@@ -657,10 +499,6 @@ function openSandbox() {
}
async function openSandboxWithTile(tileId) {
- if (!isMoundDataCached(tileId)) {
- await loadMoundData(tileId);
- }
-
sandboxVisible.value = true;
setTimeout(() => {
@@ -714,7 +552,8 @@ function setDrawMode(mode) {
}
drawingHandler = (e) => {
- if (contextMenu.value.visible) return;
+ // Don't draw if context menu is visible
+ if (contextMenuRef.value?.visible) return;
drawPoints.value.push([e.lngLat.lng, e.lngLat.lat]);
@@ -758,8 +597,8 @@ function completeDrawing() {
setDrawMode(null);
}
-function dropPin() {
- const { lng, lat } = contextMenu.value.lngLat;
+function dropPin(lngLat) {
+ const { lng, lat } = lngLat;
const feature = {
type: 'Feature',
@@ -777,13 +616,11 @@ function dropPin() {
geometryFeatures.value.features.push(feature);
updateGeometryLayer();
saveGeometry();
- contextMenu.value.visible = false;
}
-function startMeasure() {
- contextMenu.value.visible = false;
+function startMeasure(lngLat) {
setDrawMode('line');
- const { lng, lat } = contextMenu.value.lngLat;
+ const { lng, lat } = lngLat;
drawPoints.value = [[lng, lat]];
}
@@ -882,7 +719,8 @@ onMounted(() => {
type: 'raster',
tiles: ['https://tile.openstreetmap.org/{z}/{x}/{y}.png'],
tileSize: 256,
- attribution: '© OpenStreetMap contributors'
+ attribution: '© OpenStreetMap contributors',
+ maxZoom: 20
},
satellite: {
type: 'raster',
@@ -890,7 +728,8 @@ onMounted(() => {
'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}'
],
tileSize: 256,
- attribution: '© Esri'
+ attribution: '© Esri',
+ maxZoom: 21
}
},
layers: [
@@ -945,12 +784,16 @@ onMounted(() => {
popup.value.visible = false;
}
- contextMenu.value.visible = false;
+ if (contextMenuRef.value) {
+ contextMenuRef.value.hide();
+ }
});
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
- contextMenu.value.visible = false;
+ if (contextMenuRef.value) {
+ contextMenuRef.value.hide();
+ }
popup.value.visible = false;
bibliographyVisible.value = false;
if (drawMode.value) {
diff --git a/ui/src/components/ContextMenu.vue b/ui/src/components/ContextMenu.vue
index f3a5f96..c24102b 100644
--- a/ui/src/components/ContextMenu.vue
+++ b/ui/src/components/ContextMenu.vue
@@ -9,8 +9,9 @@
@@ -18,40 +19,47 @@
-
-
+
+
+
+
+