Pridani linku na planovac do menu, drobny bugfixing

This commit is contained in:
Michal Kváček
2024-05-06 09:40:25 +02:00
parent be8966b5c1
commit 670e80d998
12 changed files with 245 additions and 98 deletions
+57
View File
@@ -0,0 +1,57 @@
<script setup>
import {computed} from "vue";
const props = defineProps({
lowerLimit: {
type: [String, Number],
required: true,
},
lowerLimitUnit: {
type: String,
required: false // GND nema jednotky
},
upperLimit: {
type: [String, Number],
required: true,
},
upperLimitUnit: {
type: String,
required: false // GND nema jednotky
}
})
const getLimitFormatted = (limit, unit) => {
return `${limit} ${unit}`
}
const upper = computed(() => getLimitFormatted(props.upperLimit, props.upperLimitUnit));
const lower = computed(() => getLimitFormatted(props.lowerLimit, props.lowerLimitUnit));
</script>
<template>
<div class="altitude-limit">
<span class="upper">
{{ upper}}
</span>
<span class="lower">
{{ lower }}
</span>
</div>
</template>
<style lang="scss">
.altitude-limit {
display: inline-block;
text-align: center;
vertical-align: middle;
font-size: 12px;
.upper, .lower {
display: block;
}
.upper {
border-bottom: 1px solid black;
}
}
</style>
+9 -24
View File
@@ -2,9 +2,9 @@
import {computed, ref, watch} from "vue";
import {Marker, CustomMarker, Polyline} from "vue3-google-map";
import MapWrapper from "components/map/MapWrapper.vue";
import AirspaceInfoWindow from "components/map/AirspaceInfoWindow.vue";
import {useFlightPlanObjects, useFlightPlanUtils} from "src/composables/flightPlan";
import {useGeocode} from "src/composables/geocoding";
import AltitudeLimit from "components/AltitudeLimit.vue";
const props = defineProps({
flightPlan: {
@@ -35,24 +35,14 @@ const {center} = useGeocode();
const {markerObject, googleMapsMarkerObject, infoWindowObject, editableNameTypes} = useFlightPlanObjects();
const {getMarkersFrom} = useFlightPlanUtils();
const infoWindow = ref(infoWindowObject({}));
const handleMapClick = ({clickCoordinates, airspaces, airport, reportingPoint}) => {
infoWindow.value = infoWindowObject({
active: true,
position: clickCoordinates,
airspaces,
airport,
reportingPoint,
})
}
const mapWrap = ref(null);
const handleMarkerClick = (marker) => {
infoWindow.value = infoWindowObject({active: true, marker})
mapWrap.value.openInfoWindow({marker});
}
const handlePoiClick = (poi) => {
infoWindow.value = infoWindowObject({active: true, poi})
mapWrap.value.openInfoWindow({poi});
}
const markers = ref([]);
@@ -69,10 +59,10 @@ const addMarker = async (point) => {
const insertAt = Math.min(markers.value.length, props.insertPosition);
markers.value.splice(insertAt, 0, markerObject({...point}));
infoWindow.value = infoWindowObject({active: false});
// infoWindow.value = infoWindowObject({active: false});
emits("markers-changed", markers.value);
emits("change-insert-position",insertAt + 1);
emits("change-insert-position", insertAt + 1);
}
const {searchPlaceName} = useGeocode();
@@ -88,6 +78,7 @@ const handleMarkerMove = async (idx, event) => {
<template>
<MapWrapper
ref="mapWrap"
style="width: 100%; height: 100%; min-height: 500px"
:center="center"
:zoom="8"
@@ -95,15 +86,9 @@ const handleMarkerMove = async (idx, event) => {
:airports="airports"
v-bind="$attrs"
:fullscreen-control="false"
@map-click="handleMapClick"
>
<AirspaceInfoWindow
v-if="infoWindow.active"
:info-window="infoWindow"
@close="infoWindow.active=false"
@add-to-route="addMarker"
/>
@add-to-route="addMarker"
>
<template v-for="poi in pointsOfInterest" :key="poi.name">
<CustomMarker
v-if="poi.gpsLatitude && poi.gpsLongitude"
@@ -19,7 +19,7 @@ const props = defineProps({
v-ripple
v-for="flightPlan of items" :key="flightPlan">
<q-item-section avatar>
<q-avatar icon="person" color="primary" text-color="white"/>
<q-avatar icon="flight" color="primary" text-color="white"/>
</q-item-section>
<q-item-section>
<q-item-label>
+4 -1
View File
@@ -20,7 +20,7 @@ const props = defineProps({
});
const emits = defineEmits(["update:gpsLatitude", "update:gpsLongitude", "notFound"]);
const {emitModels, setCoordinatesFromGeocode, center, zoom} = useGeocode(emits);
const {emitModels, setCoordinatesFromGeocode, searchGeocode, center, zoom} = useGeocode(emits);
const markerDropped = (position) => {
emitModels(position.latLng.lat(), position.latLng.lng());
}
@@ -30,6 +30,9 @@ onMounted(() => {
center.value.lng = props.gpsLongitude;
center.value.lat = props.gpsLatitude
} else if (props.label) {
searchGeocode(props.label).then(result => {
});
setCoordinatesFromGeocode(props.label);
} else {
console.error("Set either label or gps coordinates.");
+58 -12
View File
@@ -2,16 +2,28 @@
import {InfoWindow} from "vue3-google-map";
import {useFlightPlanUtils} from "src/composables/flightPlan";
import {useGeocode} from "src/composables/geocoding";
import {onMounted, ref, watch} from "vue";
import {ref, watch} from "vue";
import {useAirspaceColors} from "src/composables/airspaces";
import AltitudeLimit from "components/AltitudeLimit.vue";
const props = defineProps({
infoWindow: {
type: Object,
required: true
},
highlightedAirspaces: {
type: Array,
required: false,
default: () => []
},
showActions: {
type: Boolean,
required: false,
default: false
}
});
const emits = defineEmits(["add-to-route", "close"])
const emits = defineEmits(["add-to-route", "highlight-airspace", "close"])
const {getIcon} = useFlightPlanUtils();
const {searchPlaceName} = useGeocode()
@@ -41,6 +53,11 @@ const addPoint = async () => {
}
emits("add-to-route", point)
}
const {getColorByType} = useAirspaceColors();
const highlightAirspace = (airspace) => {
emits('highlight-airspace', airspace.name)
}
</script>
@@ -52,25 +69,54 @@ const addPoint = async () => {
<div class="text-h5">{{ title }}</div>
<div v-if="infoWindow.marker">
<span class="text-h6">
<q-icon :name="getIcon(infoWindow.marker.type)"/>
{{ infoWindow.marker.name }}
</span>
<span class="text-h6">
<q-icon :name="getIcon(infoWindow.marker.type)"/>
{{ infoWindow.marker.name }}
</span>
</div>
<div v-if="infoWindow.poi">
<q-img :src="infoWindow.poi.titlePhoto.thumbnailUrl" v-if="infoWindow.poi.titlePhoto"/>
{{ infoWindow.poi.name }}
{{ infoWindow.poi }}
</div>
<template v-if="infoWindow.airspaces?.length">
<span class="text-h6">Prostory</span>
<div v-for="airspace in infoWindow.airspaces" :key="airspace">
{{ airspace.name }}
</div>
<q-list>
<q-item-label v-for="airspace in infoWindow.airspaces" :key="airspace.name">
<q-avatar size="16px" class="q-mx-sm" :style="`border: solid 1px; background: ${getColorByType(airspace.type)}`"/>
<strong>
{{ airspace.name }}
</strong>
<AltitudeLimit
class="q-mx-md"
:upper-limit="airspace.upperLimit"
:upper-limit-unit="airspace.upperAltUnit"
:lower-limit="airspace.lowerLimit"
:lower-limit-unit="airspace.lowerAltUnit"
/>
<q-btn
v-if="airspace.type !== 'FIR'"
color="white"
:text-color="props.highlightedAirspaces.includes(airspace.name) ? 'primary' : 'grey-6'"
size="x-small"
round
text
icon="mdi-eye"
@click="highlightAirspace(airspace)"
/>
</q-item-label>
</q-list>
</template>
<q-btn class="q-mt-lg" color="secondary" @click="addPoint()">Přidat bod do trasy</q-btn>
<q-btn
v-if="showActions"
class="q-mt-lg"
color="secondary"
@click="addPoint()">
Přidat bod do trasy
</q-btn>
</InfoWindow>
</template>
+59 -31
View File
@@ -2,6 +2,9 @@
import {Circle, GoogleMap, CustomControl, CustomMarker, Polygon} from "vue3-google-map";
import {computed, ref, watch} from "vue";
import {useBasicVectorOperations, useVectorUtilities} from "src/graphql/fragments/vectorUtilities";
import {useAirspaceColors} from "src/composables/airspaces";
import AirspaceInfoWindow from "components/map/AirspaceInfoWindow.vue";
import {useFlightPlanObjects} from "src/composables/flightPlan";
const props = defineProps({
bounds: {
@@ -18,6 +21,16 @@ const props = defineProps({
required: false,
default: () => []
},
showInfoWindow: {
type: Boolean,
required: false,
default: false,
},
allowAddMarker: {
type: Boolean,
required: false,
default: false
},
showLayerControls: {
type: Boolean,
required: false,
@@ -25,7 +38,7 @@ const props = defineProps({
}
});
const emits = defineEmits(["map-click"]);
const emits = defineEmits(["map-click", "add-to-route"]);
const key = process.env.GOOGLE_MAPS_API_KEY;
@@ -68,18 +81,7 @@ const displayAirspaceLayers = ref({
});
const airspaces = computed(() => {
const colorsMap = {
TMA: "#800080",
CTR: "#366e36",
PROHIBIT: "#FF0000",
RMZ: "#FFdd00",
RESTRICT: "#ff6a00",
DANGER: "#FFA500",
TRAGA: "#008000",
ATZ: "#4e7cfc",
FIR: '#ffffff',
PARA: "#000000"
}
const colorsMap = useAirspaceColors().colors;
return props.airspacesData
.filter(airspace => !!colorsMap[airspace.type])
@@ -103,9 +105,6 @@ const airspaces = computed(() => {
if (airspace.mapBounds.length > 0) {
data['path'] = airspace.mapBounds;
} else {
data['center'] = airspace.mapCenter;
data['radius'] = airspace.radius
}
return data
@@ -121,41 +120,61 @@ const airportsOnMap = computed(() => {
const {isPointInPolygon, vectorDistanceToKm} = useVectorUtilities();
const {vectorDistance} = useBasicVectorOperations();
const highlightedAirspaces = ref([]);
const getAirspacesAtCoordinates = (clickCoordinates) => {
const airspacesUnderClick = []
for (const airspace of props.airspacesData) {
if (airspace.mapBounds && isPointInPolygon(clickCoordinates, airspace.mapBounds)) {
airspacesUnderClick.push(airspace);
} else if (airspace.mapCenter && airspace.radius) {
const radDistance = vectorDistance(clickCoordinates, airspace.mapCenter);
const distanceKm = vectorDistanceToKm(radDistance)
if (distanceKm * 1000 <= airspace.radius) {
airspacesUnderClick.push(airspace)
}
}
}
return airspacesUnderClick;
}
const handleMapClick = (e) => {
const clickCoordinates = {lat: e.latLng.lat(), lng: e.latLng.lng()}
const airspacesUnderClick = getAirspacesAtCoordinates(clickCoordinates);
const {infoWindowObject} = useFlightPlanObjects();
const infoWindow = ref(infoWindowObject({}));
const openInfoWindow = (data) => {
infoWindow.value = infoWindowObject({active: true, ...data});
}
defineExpose({
openInfoWindow
});
const emitMapClick = (data) => {
openInfoWindow(data);
emits("map-click", data);
}
const highlightedAirspaces = ref([]);
const handleMapClick = (e) => {
const position = {lat: e.latLng.lat(), lng: e.latLng.lng()};
const airspacesUnderClick = getAirspacesAtCoordinates(position);
highlightedAirspaces.value = airspacesUnderClick.map(a => a.name);
emits("map-click", {airspaces: airspacesUnderClick, clickCoordinates});
emitMapClick({position, airspaces: airspacesUnderClick});
}
const handleReportingPointClick = (airport, reportingPoint) => {
const position = {lat: reportingPoint.gpsLatitude, lng: reportingPoint.gpsLongitude};
emits("map-click", {clickCoordinates: position, airport, reportingPoint})
const airspacesUnderClick = getAirspacesAtCoordinates(position);
highlightedAirspaces.value = [];
emitMapClick({position, airport, reportingPoint, airspaces: airspacesUnderClick})
}
const handleAirportClick = (airport) => {
const position = {lat: airport.gpsLatitude, lng: airport.gpsLongitude};
emits("map-click", {clickCoordinates: position, airport})
const airspacesUnderClick = getAirspacesAtCoordinates(position);
highlightedAirspaces.value = [];
emitMapClick({position, airport, airspaces: airspacesUnderClick})
}
</script>
@@ -169,6 +188,15 @@ const handleAirportClick = (airport) => {
:max-zoom="13"
ref="mapRef"
>
<AirspaceInfoWindow
v-if="infoWindow.active"
:info-window="infoWindow"
:highlighted-airspaces="highlightedAirspaces"
@close="infoWindow.active=false"
@add-to-route="emits('add-to-route', $event)"
@highlight-airspace="highlightedAirspaces = [$event]"
/>
<template v-for="airspace in airspaces" :key="airspace.name">
<Polygon :options="airspace" @click="handleMapClick"/>
</template>
+18 -23
View File
@@ -9,12 +9,12 @@
v-if="icon"
avatar
>
<q-icon :name="icon" />
<q-icon :name="icon"/>
</q-item-section>
<q-item-section>
<q-item-label>{{ title }}</q-item-label>
<q-item-label caption>{{ caption }}</q-item-label>
<q-item-label caption v-if="caption">{{ caption }}</q-item-label>
</q-item-section>
</q-item>
</template>
@@ -24,26 +24,21 @@ import {computed} from "vue";
import {useRoute, useRouter} from "vue-router";
const props = defineProps({
title: {
type: String,
required: true
},
caption: {
type: String,
default: ''
},
to: {
type: Object,
required: true
},
icon: {
type: String,
default: ''
}
});
title: {
type: String,
required: true
},
to: {
type: Object,
required: true
},
caption: {
type: String,
},
icon: {
type: String,
}
});
const $route = useRoute();
const $router = useRouter();
@@ -51,7 +46,7 @@ const isButtonActive = computed(() => {
const btnResolvedPath = $router.resolve(props.to).path;
if (props.to.name === 'myFlights') {
return $route.name.toLowerCase().includes("flight");
return ["myFlights", "addFlight", "flightDetail", "flightEdit"].includes($route.name);
} else {
return $route.fullPath === btnResolvedPath || $route.fullPath.startsWith(btnResolvedPath);
}
@@ -6,9 +6,15 @@ const linksList = [
{
title: 'Moje lety',
icon: "flight_takeoff",
to: {name: "myFlights"}
to: {name: "myFlights"},
},
{separator: true},
{
title: 'Moje plány',
icon: "pin_drop",
to: {name: "flightPlans"}
},
{separator: true},
{
title: 'Letadla',
+20
View File
@@ -0,0 +1,20 @@
export const useAirspaceColors = () => {
const colors = {
TMA: "#800080",
CTR: "#366e36",
PROHIBIT: "#FF0000",
RMZ: "#FFdd00",
RESTRICT: "#ff6a00",
DANGER: "#FFA500",
TRAGA: "#008000",
ATZ: "#4e7cfc",
FIR: '#ffffff',
PARA: "#000000"
}
const getColorByType = (airspaceType) => {
return colors[airspaceType] || '#ff00ff';
}
return {colors, getColorByType}
}
+4
View File
@@ -6,6 +6,10 @@ export const AIRSPACE_LIST = gql`
airspaces {
name
type
lowerLimit
lowerAltUnit
upperLimit
upperAltUnit
mapBounds {
lat
@@ -147,9 +147,9 @@ const fullscreen = ref(false);
<q-checkbox v-model="formData.isDefaultName" label="Použít automatické jméno"/>
<q-checkbox v-model="formData.isPublic" label="Zveřejnit"/>
<q-checkbox v-model="formData.isEditableByCopilot" label="Kopilot může upravovat"/>
<DatetimePicker v-model="formData.plannedTakeoffDatetime" label="Datum a čas vzletu" v-bind="commonProps"/>
<!-- <q-checkbox v-model="formData.isPublic" label="Zveřejnit"/>-->
<!-- <q-checkbox v-model="formData.isEditableByCopilot" label="Kopilot může upravovat"/>-->
<DatetimePicker v-model="formData.plannedTakeoffDatetime" label="Plánovaný datum a čas vzletu" v-bind="commonProps"/>
<ComboboxInput v-model="formData.aircraft" :options="aircrafts" label="Letadlo" v-bind="commonProps"/>
<q-input v-model.number="formData.plannedSpeed" type="number" step="5" label="Plánovaná rychlost" v-bind="commonProps">
+4 -1
View File
@@ -43,11 +43,12 @@ const routes = [
},
children: [
{path: '', name: "myFlights", component: () => import('pages/profile/flight/FlightsPage.vue')},
{path: 'nastaveni', name: "profile", component: () => import('pages/profile/ProfilePage.vue')},
{path: 'lety/novy', name: "addFlight", component: () => import('pages/profile/flight/NewFlightPage.vue')},
{path: 'lety/:id', name: "flightDetail", component: () => import('pages/profile/flight/FlightDetailPage.vue')},
{path: 'lety/:id/edit/:step?', name: "flightEdit", component: () => import('pages/profile/flight/EditFlightPage.vue')},
{path: 'nastaveni', name: "profile", component: () => import('pages/profile/ProfilePage.vue')},
{path: 'plany', name: "flightPlans", component: () => import('pages/profile/flightPlanner/FlightPlansPage.vue')},
{path: 'plany/novy', name: "newFlightPlan", component: () => import('pages/profile/flightPlanner/NewFlightPlanPage.vue')},
{path: 'plany/:id', name: "editFlightPlan", component: () => import('pages/profile/flightPlanner/FlightPlanEditPage.vue')},
@@ -56,10 +57,12 @@ const routes = [
{path: 'poi/novy', name: "addPOI", component: () => import('pages/profile/poi/NewPointOfInterestPage.vue')},
{path: 'poi/:id', name: "poiDetail", component: () => import('pages/profile/poi/PointOfInterestDetailPage.vue')},
{path: 'poi/:id/edit/:step?', name: "poiEdit", component: () => import('pages/profile/poi/EditPointOfInterestPage.vue')},
{path: 'kopiloti', name: "copilots", component: () => import('pages/profile/copilot/CopilotsPage.vue')},
{path: 'kopiloti/novy', name: "addCopilot", component: () => import('pages/profile/copilot/NewCopilotPage.vue')},
{path: 'kopiloti/:id', name: "copilotDetail", component: () => import('pages/profile/copilot/CopilotDetailPage.vue')},
{path: 'kopiloti/:id/edit/:step?', name: "copilotEdit", component: () => import('pages/profile/copilot/EditCopilotPage.vue')},
{path: 'letadla', name: "aircrafts", component: () => import('pages/profile/aircraft/AircraftsPage.vue')},
{path: 'letadla/nove', name: "addAircraft", component: () => import('pages/profile/aircraft/NewAircraftsPage.vue')},
{path: 'letadla/:id', name: "aircraftDetail", component: () => import('pages/profile/aircraft/AircraftDetailPage.vue')},