full frontend refactor
This commit is contained in:
230
ui/src/components/FeaturePopup.vue
Normal file
230
ui/src/components/FeaturePopup.vue
Normal file
@@ -0,0 +1,230 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="visible"
|
||||
class="popup"
|
||||
:style="{ left: x + 'px', top: y + 'px' }"
|
||||
>
|
||||
<!-- ============================================= -->
|
||||
<!-- POPUP CONTENT -->
|
||||
<!-- ============================================= -->
|
||||
<div class="popup-content">
|
||||
<!-- PIN -->
|
||||
<div v-if="type === 'pin'">
|
||||
<strong>Pin #{{ feature.properties.number }}</strong>
|
||||
<div>{{ formatCoordinate(feature.geometry.coordinates[0], 'lng') }}, {{ formatCoordinate(feature.geometry.coordinates[1], 'lat') }}</div>
|
||||
<button @click="$emit('delete', feature.id)" class="popup-btn danger">Delete</button>
|
||||
</div>
|
||||
|
||||
<!-- LINE or RAY -->
|
||||
<div v-else-if="type === 'line' || type === 'ray'">
|
||||
<strong>{{ type === 'line' ? 'Line' : 'Ray' }}</strong>
|
||||
<div>Length: {{ formatDistance(feature.properties.length, imperialUnits) }}</div>
|
||||
<div>Bearing: {{ formatBearing(feature.properties.bearing) }}</div>
|
||||
<button @click="$emit('delete', feature.id)" class="popup-btn danger">Delete</button>
|
||||
</div>
|
||||
|
||||
<!-- HISTORIC SITE -->
|
||||
<div v-else-if="type === 'site'" class="site-popup">
|
||||
<strong>{{ feature.properties.name }}</strong>
|
||||
<div class="site-type">{{ feature.properties.type === 'road_confirmed' ? 'Confirmed Road Segment' : 'Earthwork' }}</div>
|
||||
<div class="site-description">
|
||||
<template v-for="(segment, idx) in parsedDescription" :key="idx">
|
||||
<template v-if="segment.type === 'text'">{{ segment.content }}</template>
|
||||
<a
|
||||
v-else-if="segment.type === 'citation'"
|
||||
href="#"
|
||||
@click.prevent="$emit('showBibliography', segment.key)"
|
||||
class="citation-link"
|
||||
:title="`View ${segment.key} in bibliography`"
|
||||
>[{{ segment.key }}]</a>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============================================= -->
|
||||
<!-- CLOSE BUTTON -->
|
||||
<!-- ============================================= -->
|
||||
<button @click="$emit('close')" class="popup-close">×</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { formatCoordinate, formatDistance, formatBearing } from '../utils/coordinates.js';
|
||||
import { parseCitations } from '../utils/citations.js';
|
||||
|
||||
// ============================================================================
|
||||
// INTERFACE
|
||||
// ============================================================================
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
x: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
y: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
type: {
|
||||
type: String, // 'pin', 'line', 'ray', 'site'
|
||||
default: null
|
||||
},
|
||||
feature: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
imperialUnits: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['close', 'delete', 'showBibliography']);
|
||||
|
||||
// ============================================================================
|
||||
// COMPUTED
|
||||
// ============================================================================
|
||||
|
||||
const parsedDescription = computed(() => {
|
||||
if (props.type === 'site' && props.feature?.properties?.description) {
|
||||
return parseCitations(props.feature.properties.description);
|
||||
}
|
||||
return [];
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* ============================================= */
|
||||
/* POPUP CONTAINER */
|
||||
/* ============================================= */
|
||||
|
||||
.popup {
|
||||
position: absolute;
|
||||
background: white;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 12px rgba(0,0,0,0.4);
|
||||
z-index: 1000;
|
||||
min-width: 180px;
|
||||
max-width: 350px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ============================================= */
|
||||
/* POPUP CONTENT */
|
||||
/* ============================================= */
|
||||
|
||||
.popup-content {
|
||||
padding: 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.popup-content strong {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.popup-content div {
|
||||
margin: 4px 0;
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* ============================================= */
|
||||
/* SITE-SPECIFIC STYLING */
|
||||
/* ============================================= */
|
||||
|
||||
.site-popup {
|
||||
max-width: 320px;
|
||||
}
|
||||
|
||||
.site-type {
|
||||
font-size: 11px !important;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
color: #999 !important;
|
||||
margin-bottom: 8px !important;
|
||||
}
|
||||
|
||||
.site-description {
|
||||
line-height: 1.5;
|
||||
color: #333 !important;
|
||||
font-size: 13px !important;
|
||||
}
|
||||
|
||||
.citation-link {
|
||||
color: #4A9EFF;
|
||||
text-decoration: none;
|
||||
font-family: monospace;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.citation-link:hover {
|
||||
color: #2E8FE3;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* ============================================= */
|
||||
/* POPUP BUTTONS */
|
||||
/* ============================================= */
|
||||
|
||||
.popup-btn {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
margin-top: 10px;
|
||||
background: white;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 3px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.popup-btn:hover {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.popup-btn.danger {
|
||||
color: #d32f2f;
|
||||
border-color: #d32f2f;
|
||||
}
|
||||
|
||||
.popup-btn.danger:hover {
|
||||
background: #ffebee;
|
||||
}
|
||||
|
||||
/* ============================================= */
|
||||
/* CLOSE BUTTON */
|
||||
/* ============================================= */
|
||||
|
||||
.popup-close {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 20px;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
color: #999;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.popup-close:hover {
|
||||
color: #333;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user