v1 Release

This commit is contained in:
2026-07-11 18:44:40 +10:00
parent 85eeba982f
commit 5ac580bb06
15 changed files with 171 additions and 72 deletions
@@ -1,43 +1,46 @@
<template>
<div class="flight-map-wrapper">
<PlaneLoader v-if="!mapReady" class="map-loader" />
<div ref="mapContainer" class="map-container" :class="{ 'map-hidden': !mapReady }" />
<button @click="exportMapBasic()" class="export-btn" title="Download as Image">
<span class="mdi mdi-download" />
</button>
<button class="projection-btn" @click="toggleProjection" :title="isGlobe ? 'Switch to flat map' : 'Switch to globe'">
<span class="mdi" :class="isGlobe ? 'mdi-map' : 'mdi-earth'"/>
</button>
<div v-if="!flights.length" class="empty-state">
<span class="mdi mdi-earth-off" />
<p>No flight data available</p>
</div>
<div v-if="showLegend" class="map-legend" :class="{ 'map-legend--open': legendOpen }">
<button class="map-legend__toggle" @click="toggleLegend">
<span class="mdi mdi-format-list-bulleted" />
<span class="map-legend__toggle-label">Legend</span>
<span class="mdi" :class="legendOpen ? 'mdi-chevron-down' : 'mdi-chevron-up'" />
<div>
<div class="flight-map-wrapper">
<PlaneLoader v-if="!mapReady" class="map-loader" />
<div ref="mapContainer" class="map-container" :class="{ 'map-hidden': !mapReady }" />
<button @click="exportMapBasic()" class="export-btn" title="Download as Image">
<span class="mdi mdi-download" />
</button>
<div class="map-legend__body">
<div class="map-legend__row" v-for="item in legendItems" :key="item.label">
<svg width="28" height="10" class="map-legend__swatch">
<line
x1="2" y1="5" x2="26" y2="5"
:stroke="item.color"
:stroke-dasharray="item.dashed ? '4 3' : undefined"
stroke-width="2"
stroke-linecap="round"
/>
</svg>
<span class="map-legend__label">{{ item.label }}</span>
<button class="projection-btn" @click="toggleProjection" :title="isGlobe ? 'Switch to flat map' : 'Switch to globe'">
<span class="mdi" :class="isGlobe ? 'mdi-map' : 'mdi-earth'"/>
</button>
<div v-if="!flights.length" class="empty-state">
<span class="mdi mdi-earth-off" />
<p>No flight data available</p>
</div>
<div v-if="showLegend" class="map-legend" :class="{ 'map-legend--open': legendOpen }">
<button class="map-legend__toggle" @click="toggleLegend">
<span class="mdi mdi-format-list-bulleted" />
<span class="map-legend__toggle-label">Legend</span>
<span class="mdi" :class="legendOpen ? 'mdi-chevron-down' : 'mdi-chevron-up'" />
</button>
<div class="map-legend__body">
<div class="map-legend__row" v-for="item in legendItems" :key="item.label">
<svg width="28" height="10" class="map-legend__swatch">
<line
x1="2" y1="5" x2="26" y2="5"
:stroke="item.color"
:stroke-dasharray="item.dashed ? '4 3' : undefined"
stroke-width="2"
stroke-linecap="round"
/>
</svg>
<span class="map-legend__label">{{ item.label }}</span>
</div>
</div>
</div>
</div>
<small class="attribution" v-if="compact" v-html="cartoAttribution" />
</div>
</template>
<script lang="ts">
import { defineComponent, ref, onMounted, onBeforeUnmount, watch, nextTick, PropType } from 'vue'
import {defineComponent, ref, onMounted, onBeforeUnmount, watch, nextTick, PropType, computed} from 'vue'
import maplibregl from 'maplibre-gl'
import 'maplibre-gl/dist/maplibre-gl.css'
import { Flight, Airport, SharedProps } from '@/Types/types'
@@ -72,6 +75,7 @@ interface RoutesGeoJSON {
const cartoAttribution = computed(() => `&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/attributions">CARTO</a>`)
// ── Constants ─────────────────────────────────────────────────────────────────
const PULSE_PHASES = 6
@@ -688,7 +692,7 @@ export default defineComponent({
map = new maplibregl.Map({
container: mapContainer.value!,
cooperativeGestures: true,
attributionControl: false,
attributionControl: props.compact ? false : {compact: true},
center: [0, 20],
zoom: 2,
renderWorldCopies: true,
@@ -700,6 +704,7 @@ export default defineComponent({
type: 'raster',
tileSize: 256,
maxzoom: 19,
attribution: cartoAttribution.value,
tiles: [
'https://a.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}@2x.png',
'https://b.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}@2x.png',
@@ -767,7 +772,7 @@ export default defineComponent({
if (map) { map.remove(); map = null }
})
return { mapContainer, mapReady, exportMapBasic, legendOpen, legendItems, isGlobe, toggleProjection, toggleLegend }
return { mapContainer, mapReady, exportMapBasic, legendOpen, legendItems, isGlobe, toggleProjection, toggleLegend, cartoAttribution }
},
})
@@ -816,6 +821,19 @@ export default defineComponent({
}
}
small.attribution{
font-size: 0.7em;
display: flex;
align-items: center;
justify-content: center;
color: var(--muted);
gap: 2px;
}
small.attribution * {
color: var(--muted)
}
.map-container { position: absolute; inset: 0; }
.map-loader { z-index: 10; }
.map-hidden { visibility: hidden; }
@@ -991,4 +1009,9 @@ export default defineComponent({
}
.projection-btn:hover { background: rgba(77,166,255,0.15); color: #4da6ff; }
.projection-btn.globe-active { color: #4da6ff; border-color: rgba(77,166,255,0.35); }
.maplibregl-ctrl-bottom-right {
left: 50%;
right: auto;
transform: translateX(-50%);
}
</style>