full frontend refactor

This commit is contained in:
2026-01-25 10:26:23 +01:00
parent 317ee96ba3
commit 559a4c3e9f
18 changed files with 2803 additions and 1224 deletions

View 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>