full frontend refactor
This commit is contained in:
169
ui/src/components/ContextMenu.vue
Normal file
169
ui/src/components/ContextMenu.vue
Normal file
@@ -0,0 +1,169 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="visible"
|
||||
class="context-menu"
|
||||
:style="{ left: x + 'px', top: y + 'px' }"
|
||||
>
|
||||
<!-- ============================================= -->
|
||||
<!-- HEADER - COORDINATES -->
|
||||
<!-- ============================================= -->
|
||||
<div class="context-menu-header">
|
||||
{{ formatCoordinate(lngLat.lng, 'lng') }}, {{ formatCoordinate(lngLat.lat, 'lat') }}
|
||||
<span v-if="tileData?.id" class="tile-name">{{ tileData.id }}</span>
|
||||
<span v-if="apiError" class="tile-error">⚠️ Lookup failed</span>
|
||||
</div>
|
||||
|
||||
<!-- ============================================= -->
|
||||
<!-- MENU ITEMS -->
|
||||
<!-- ============================================= -->
|
||||
|
||||
<!-- Always available -->
|
||||
<button @click="$emit('dropPin')" class="context-menu-item">📍 Drop Pin</button>
|
||||
<button @click="$emit('startMeasure')" class="context-menu-item">📏 Measure from here</button>
|
||||
|
||||
<!-- State A: No tile exists (or error checking) -->
|
||||
<button
|
||||
v-if="!tileData && !apiError"
|
||||
@click="$emit('requestTile')"
|
||||
class="context-menu-item"
|
||||
>
|
||||
📥 Request Tile
|
||||
</button>
|
||||
|
||||
<!-- State B: Tile exists but images not loaded -->
|
||||
<button
|
||||
v-if="tileData && !imagesLoaded && (tileData.jpg_available || tileData.png_available)"
|
||||
@click="$emit('loadImages')"
|
||||
class="context-menu-item"
|
||||
>
|
||||
📦 Load Tile Images
|
||||
</button>
|
||||
|
||||
<!-- State C: Images loaded, but mound not loaded -->
|
||||
<button
|
||||
v-if="tileData && !moundLoaded"
|
||||
@click="$emit('loadMound')"
|
||||
class="context-menu-item"
|
||||
>
|
||||
🔬 Load Interactive Data
|
||||
</button>
|
||||
|
||||
<!-- State D: Mound loaded, ready to open sandbox -->
|
||||
<button
|
||||
v-if="moundLoaded"
|
||||
@click="$emit('openSandbox')"
|
||||
class="context-menu-item"
|
||||
>
|
||||
🔬 Open in Shading Sandbox
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { formatCoordinate } from '../utils/coordinates.js';
|
||||
|
||||
// ============================================================================
|
||||
// INTERFACE
|
||||
// ============================================================================
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
x: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
y: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
lngLat: {
|
||||
type: Object, // { lng, lat }
|
||||
required: true
|
||||
},
|
||||
tileData: {
|
||||
type: Object, // Tile metadata from API
|
||||
default: null
|
||||
},
|
||||
imagesLoaded: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
moundLoaded: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
apiError: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
});
|
||||
|
||||
defineEmits(['dropPin', 'startMeasure', 'requestTile', 'loadImages', 'loadMound', 'openSandbox']);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* ============================================= */
|
||||
/* CONTEXT MENU CONTAINER */
|
||||
/* ============================================= */
|
||||
|
||||
.context-menu {
|
||||
position: absolute;
|
||||
background: white;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 12px rgba(0,0,0,0.4);
|
||||
z-index: 1000;
|
||||
min-width: 200px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ============================================= */
|
||||
/* HEADER */
|
||||
/* ============================================= */
|
||||
|
||||
.context-menu-header {
|
||||
padding: 10px 12px;
|
||||
background: #f5f5f5;
|
||||
font-size: 12px;
|
||||
font-family: monospace;
|
||||
border-bottom: 1px solid #ddd;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.context-menu-header .tile-name {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.context-menu-header .tile-error {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
font-size: 11px;
|
||||
color: #f44336;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* ============================================= */
|
||||
/* MENU ITEMS */
|
||||
/* ============================================= */
|
||||
|
||||
.context-menu-item {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
background: white;
|
||||
border: none;
|
||||
text-align: left;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.context-menu-item:hover {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user