757 lines
24 KiB
Vue
757 lines
24 KiB
Vue
<template>
|
|
<div class="">
|
|
<q-layout
|
|
view="hHh Lpr fFf"
|
|
container
|
|
style="height: 600px; max-width: 800px; width: 100vw"
|
|
class="rounded-borders"
|
|
>
|
|
<q-footer :class="$q.dark.isActive ? 'bg-primary' : 'bg-black'" class="z-top" style="height: 48px">
|
|
<q-toolbar>
|
|
<q-btn
|
|
class="q-mr-sm"
|
|
dense
|
|
flat
|
|
icon="menu"
|
|
round
|
|
size="sm"
|
|
@click="qLocDrawer = !qLocDrawer"
|
|
/>
|
|
<q-btn-dropdown flat label="Zoom To" size="sm" stretch>
|
|
<q-list>
|
|
<q-item v-close-popup v-ripple clickable @click="zoomTo('fmLoc')">
|
|
<q-item-section>
|
|
<q-item-label>Find My Location</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
<q-item v-close-popup v-ripple clickable @click="zoomTo('simLoc')">
|
|
<q-item-section>
|
|
<q-item-label>Sim Location</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
</q-list>
|
|
</q-btn-dropdown>
|
|
<q-btn-dropdown label="Routing" size="sm" stretch v-if="routeSet.start && routeSet.end">
|
|
<q-list>
|
|
<q-item v-close-popup v-ripple clickable @click="routeToQueue">
|
|
<q-item-section>
|
|
<q-item-label>Add Route to Sim Queue</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
<q-item v-close-popup v-ripple clickable @click="clearRoute">
|
|
<q-item-section>
|
|
<q-item-label>clearRoute</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
</q-list>
|
|
</q-btn-dropdown>
|
|
</q-toolbar>
|
|
</q-footer>
|
|
<q-drawer
|
|
v-model="qLocDrawer"
|
|
behavior="mobile"
|
|
show-if-above
|
|
:width="300"
|
|
side="left"
|
|
:breakpoint="500"
|
|
@mouseenter="miniState = false"
|
|
@mouseleave="miniState = true"
|
|
class="leafletDrawer"
|
|
>
|
|
<q-scroll-area class="fit" :horizontal-thumb-style="{ opacity: '50' }">
|
|
<q-list padding>
|
|
<q-item-label header
|
|
><span class="bold">Location Queue: </span> {{ simulationState }}</q-item-label
|
|
>
|
|
<q-separator />
|
|
<div
|
|
v-for="(key, index) in locationQueueOrderFiltered"
|
|
:key="key"
|
|
@contextmenu.prevent="onDrawerContextMenu($event, key)"
|
|
>
|
|
<LocationItem
|
|
:loc_id="key"
|
|
:active="
|
|
(locationQueueData as Record<string, any>)[key]?.loc_id ===
|
|
currentLocation?.loc_id
|
|
"
|
|
:isCurrentDelay="
|
|
(locationQueueData as Record<string, any>)[key]?.loc_id ===
|
|
locationQueueOrderFiltered[index + 1]
|
|
"
|
|
:index="index"
|
|
:isLast="index == locationQueueOrderFiltered.length - 1"
|
|
:start="(locationQueueData as Record<string, any>)[key]?.start ?? ''"
|
|
:address="(locationQueueData as Record<string, any>)[key]?.address ?? ''"
|
|
:latitude="(locationQueueData as Record<string, any>)[key]?.latitude ?? 0"
|
|
:longitude="(locationQueueData as Record<string, any>)[key]?.longitude ?? 0"
|
|
:delay="(locationQueueData as Record<string, any>)[key]?.delay ?? 0"
|
|
:end="(locationQueueData as Record<string, any>)[key]?.end ?? undefined"
|
|
:status="(locationQueueData as Record<string, any>)[key]?.status ?? undefined"
|
|
@item-clicked="zoomToCoords"
|
|
/>
|
|
</div>
|
|
</q-list>
|
|
</q-scroll-area>
|
|
<q-menu ref="contextMenu" context-menu touch-position>
|
|
<q-list>
|
|
<q-item clickable ripple v-close-popup @click="handleDrawerContextMenu('zoom')">
|
|
<q-item-section label="Zoom to Location"> Zoom </q-item-section>
|
|
</q-item>
|
|
<q-item clickable ripple v-close-popup @click="handleDrawerContextMenu('delete')">
|
|
<q-item-section label="Zoom to Location"> Delete </q-item-section>
|
|
</q-item>
|
|
<q-item clickable ripple v-close-popup @click="handleDrawerContextMenu('gp')">
|
|
<q-item-section label="Zoom to Location"> Go Now </q-item-section>
|
|
</q-item>
|
|
</q-list>
|
|
</q-menu>
|
|
</q-drawer>
|
|
|
|
<q-page-container>
|
|
<q-page>
|
|
<div style="height: 550px; width: 100vw; max-width: 800px; color: #000000">
|
|
<L-Map
|
|
id="map"
|
|
ref="mapRef"
|
|
:center="safeCenter"
|
|
:zoom="zoom"
|
|
style="height: 550px; width: 100vw; max-width: 800px"
|
|
@click="updateMarker"
|
|
@ready="onMapReady"
|
|
>
|
|
<L-Tile-Layer
|
|
layer-type="base"
|
|
name="OpenStreetMap"
|
|
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
|
></L-Tile-Layer>
|
|
<L-Layer-Group>
|
|
<L-Marker
|
|
v-if="safeMarkerLatLng"
|
|
:lat-lng="safeMarkerLatLng"
|
|
@click="handleMarkerClick"
|
|
>
|
|
<L-Popup
|
|
:options="{ closeOnClick: true, closeButton: false, className: 'marker-popup' }"
|
|
>
|
|
<q-list dense seperator style="min-width: 100px" class="bg-grey-10">
|
|
<q-item clickable v-ripple @click="handleAddLocation">
|
|
<q-item-section avatar>
|
|
<q-avatar
|
|
icon="add_location"
|
|
color="primary"
|
|
text-color="white"
|
|
size="sm"
|
|
/>
|
|
</q-item-section>
|
|
<q-item-section>Add Location</q-item-section>
|
|
</q-item>
|
|
<q-item clickable v-ripple @click="setStartRoute">
|
|
<q-item-section avatar>
|
|
<q-avatar
|
|
icon="add_location"
|
|
color="primary"
|
|
text-color="white"
|
|
size="sm"
|
|
/>
|
|
</q-item-section>
|
|
<q-item-section>Set Route Start</q-item-section>
|
|
</q-item>
|
|
<q-item clickable v-ripple @click="setEndRoute">
|
|
<q-item-section avatar>
|
|
<q-avatar
|
|
icon="add_location"
|
|
color="primary"
|
|
text-color="white"
|
|
size="sm"
|
|
/>
|
|
</q-item-section>
|
|
<q-item-section>Set Route End</q-item-section>
|
|
</q-item>
|
|
</q-list>
|
|
</L-Popup>
|
|
</L-Marker>
|
|
</L-Layer-Group>
|
|
<L-Layer-Group v-if="locationQueueOrder">
|
|
<L-Marker
|
|
v-for="locid in locationQueueOrderFiltered"
|
|
:key="locid"
|
|
:icon="getCustomIcon(locid) as any"
|
|
:lat-lng="[
|
|
(locationQueueData as Record<string, any>)[locid]?.latitude ?? 0,
|
|
(locationQueueData as Record<string, any>)[locid]?.longitude ?? 0,
|
|
]"
|
|
>
|
|
</L-Marker>
|
|
</L-Layer-Group>
|
|
<L-Layer-Group v-if="routeSet.start && routeSet.end">
|
|
<LRoutingMachine
|
|
v-bind="routingOptions"
|
|
@routingstart="debugRoutingEvent"
|
|
@routesfound="handleRoutesFound"
|
|
@routingerror="debugRoutingEvent"
|
|
/>
|
|
</L-Layer-Group>
|
|
<L-Layer-Group v-if="findMyUpdate">
|
|
<L-Marker
|
|
v-if="findMyUpdate"
|
|
:icon="fmIcon as any"
|
|
:lat-lng="[findMyUpdate.latitude ?? 0, findMyUpdate.longitude ?? 0]"
|
|
>
|
|
<L-Tooltip>
|
|
{{ findMyTimePast }}
|
|
</L-Tooltip>
|
|
</L-Marker>
|
|
<L-Circle
|
|
:fillOpacity="0.5"
|
|
:lat-lng="[findMyUpdate.latitude ?? 0, findMyUpdate.longitude ?? 0]"
|
|
:radius="findMyUpdate.horizontalAccuracy"
|
|
color="#f5bb39"
|
|
fillColor="indianred"
|
|
></L-Circle>
|
|
</L-Layer-Group>
|
|
</L-Map>
|
|
</div>
|
|
</q-page>
|
|
</q-page-container>
|
|
</q-layout>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import { useQuasar } from 'quasar';
|
|
import { computed, markRaw, onMounted, onUnmounted, reactive, ref } from 'vue';
|
|
|
|
// Leaflet imports
|
|
import { GeoSearchControl, OpenStreetMapProvider } from 'leaflet-geosearch';
|
|
import 'leaflet-routing-machine/dist/leaflet-routing-machine.css';
|
|
import {
|
|
Icon,
|
|
PinCirclePanel,
|
|
PinStarPanel,
|
|
PinTriangle,
|
|
PinSquare,
|
|
ChipCircle,
|
|
} from 'leaflet-extra-markers';
|
|
import 'leaflet-geosearch/dist/geosearch.css';
|
|
import 'leaflet/dist/leaflet.css';
|
|
import {
|
|
LCircle,
|
|
LLayerGroup,
|
|
LMap,
|
|
LMarker,
|
|
LPopup,
|
|
LTileLayer,
|
|
LTooltip,
|
|
} from '@vue-leaflet/vue-leaflet';
|
|
import * as LeafLet from 'leaflet';
|
|
|
|
// Custom Components
|
|
import LRoutingMachine from 'components/LRoutingMachine.vue';
|
|
import LocationItem from 'components/LocationItem.vue';
|
|
import SetLocationDialog from 'components/SetLocationDialog.vue';
|
|
import { customRouter } from 'functions/serviceURL';
|
|
import { useRoutingEvents } from '../composables/useRoutingEvents';
|
|
import { useMarkerContextMenu } from '../composables/useMarkerContextMenu';
|
|
import type { IRouter } from 'leaflet-routing-machine';
|
|
|
|
// Types
|
|
import type { coords, SearchControlProps } from 'components/models';
|
|
import type { LeafletMouseEvent, Map } from 'leaflet';
|
|
|
|
// Stores
|
|
import { storeToRefs } from 'pinia';
|
|
import { useSocketioStore } from 'stores/socketio';
|
|
import { useLeafletStore } from 'stores/leaflet';
|
|
|
|
import { favorites } from 'constants/favorites';
|
|
|
|
const leafletStore = useLeafletStore();
|
|
const { zoom, center, markerLatLng, qLocDrawer, routeSet, routeDirections } =
|
|
storeToRefs(leafletStore);
|
|
|
|
const socketStore = useSocketioStore();
|
|
const {
|
|
currentLocation,
|
|
nextLocation,
|
|
locationQueueData,
|
|
locationQueueOrder,
|
|
findMyUpdate,
|
|
simulationState,
|
|
} = storeToRefs(socketStore);
|
|
|
|
const $q = useQuasar();
|
|
|
|
const now = ref(Date.now());
|
|
const mapRef = ref();
|
|
const responseMessage = ref('');
|
|
const miniState = ref(true);
|
|
const safeCenter = computed<[number, number]>(() => {
|
|
const lat = center.value?.[0];
|
|
const lng = center.value?.[1];
|
|
if (typeof lat === 'number' && typeof lng === 'number') {
|
|
return [lat, lng];
|
|
}
|
|
return [favorites.home.coords.lat, favorites.home.coords.lng];
|
|
});
|
|
const safeMarkerLatLng = computed<[number, number] | null>(() => {
|
|
const lat = markerLatLng.value?.[0];
|
|
const lng = markerLatLng.value?.[1];
|
|
if (typeof lat === 'number' && typeof lng === 'number') {
|
|
return [lat, lng];
|
|
}
|
|
return null;
|
|
});
|
|
|
|
const onMapReady = (map: Map) => {
|
|
const provider = new OpenStreetMapProvider();
|
|
const searchOptions: SearchControlProps = {
|
|
provider: provider,
|
|
showMarker: false,
|
|
autoClose: true,
|
|
updateMap: true,
|
|
showPopup: true,
|
|
style: 'button',
|
|
acceptAutoLoad: true,
|
|
autoComplete: true,
|
|
autoCompleteDelay: 250,
|
|
retainZoomLevel: false,
|
|
animateZoom: true,
|
|
keepResult: true,
|
|
};
|
|
|
|
const searchControl = GeoSearchControl(searchOptions);
|
|
map.addControl(searchControl);
|
|
};
|
|
|
|
const fmIcon = new Icon({
|
|
color: 'indianred',
|
|
accentColor: 'firebrick',
|
|
content: 'FM',
|
|
contentColor: 'white',
|
|
scale: 1,
|
|
svg: PinCirclePanel,
|
|
});
|
|
|
|
const startIcon = new Icon({
|
|
color: '#006838',
|
|
accentColor: 'rgba(0,0,0,0.25)',
|
|
content: 'S',
|
|
contentColor: 'white',
|
|
scale: 1,
|
|
svg: PinTriangle,
|
|
});
|
|
|
|
const endIcon = new Icon({
|
|
color: '#a23337',
|
|
accentColor: 'rgba(0,0,0,0.25)',
|
|
content: 'E',
|
|
contentColor: 'white',
|
|
scale: 1,
|
|
svg: PinSquare,
|
|
});
|
|
|
|
const intermediateIcon = new Icon({
|
|
color: 'cornflowerblue',
|
|
accentColor: 'rgba(0,0,0,0.25)',
|
|
contentColor: 'white',
|
|
svg: ChipCircle,
|
|
scale: 1,
|
|
});
|
|
|
|
type RoutingWaypoint = {
|
|
latLng: LeafLet.LatLng;
|
|
};
|
|
|
|
const locationQueueOrderFiltered = computed(() => {
|
|
if (locationQueueOrder.value) {
|
|
return locationQueueOrder.value.filter(
|
|
(loc_id) => locationQueueData.value[loc_id]?.status !== 'deleted',
|
|
);
|
|
} else {
|
|
return [];
|
|
}
|
|
});
|
|
|
|
const getCustomIcon = (locid: string) => {
|
|
const currentIndex = currentLocation.value
|
|
? locationQueueOrderFiltered.value.indexOf(currentLocation.value.loc_id)
|
|
: 0;
|
|
const locationIndex = locationQueueOrderFiltered.value.indexOf(locid);
|
|
const updatedIndex = (locationIndex - currentIndex);
|
|
if (currentLocation.value && currentLocation.value.loc_id === locid) {
|
|
return new Icon({
|
|
color: 'pink',
|
|
accentColor: 'black',
|
|
content: '*',
|
|
contentColor: 'black',
|
|
scale: 1.5,
|
|
svg: PinStarPanel,
|
|
});
|
|
}
|
|
if(updatedIndex > 0) {
|
|
return new Icon({
|
|
color: 'blue',
|
|
accentColor: 'firebrick',
|
|
content: updatedIndex.toString(),
|
|
contentColor: 'white',
|
|
scale: 1,
|
|
svg: PinCirclePanel,
|
|
});
|
|
} else {
|
|
return new Icon({
|
|
color: 'black',
|
|
accentColor: 'grey',
|
|
content: updatedIndex.toString(),
|
|
contentColor: 'white',
|
|
scale: 1,
|
|
svg: PinCirclePanel,
|
|
});
|
|
}
|
|
};
|
|
|
|
const routingOptions = reactive<{
|
|
waypoints: LeafLet.LatLng[];
|
|
router: IRouter;
|
|
routeWhileDragging: boolean;
|
|
createMarker: (i: number, waypoint: RoutingWaypoint, n: number) => LeafLet.Marker | false;
|
|
}>({
|
|
createMarker: function (i, waypoint, n) {
|
|
let icon;
|
|
if (i === 0) {
|
|
icon = startIcon;
|
|
} else if (i === n - 1) {
|
|
icon = endIcon;
|
|
} else {
|
|
icon = intermediateIcon;
|
|
}
|
|
return LeafLet.marker(waypoint.latLng, {
|
|
draggable: true,
|
|
icon,
|
|
contextmenu: false,
|
|
contextmenuItems: [],
|
|
});
|
|
},
|
|
routeWhileDragging: true,
|
|
router: markRaw(customRouter),
|
|
waypoints: [],
|
|
});
|
|
|
|
const { handleRoutesFound } = useRoutingEvents();
|
|
|
|
const debugRoutingEvent = (event: Event) => {
|
|
console.log(`${event.type} event: `, event);
|
|
};
|
|
|
|
function updateMarker(event: LeafletMouseEvent) {
|
|
markerLatLng.value = [event.latlng.lat, event.latlng.lng];
|
|
center.value = [event.latlng.lat, event.latlng.lng];
|
|
}
|
|
|
|
function closeAllPopups() {
|
|
if (mapRef.value) {
|
|
// Access the Leaflet map instance directly
|
|
mapRef.value.leafletObject.closePopup();
|
|
}
|
|
}
|
|
|
|
const delay = (ms: number) => new Promise((res) => setTimeout(res, ms));
|
|
|
|
async function routeToQueue() {
|
|
if (routeSet.value.start && routeSet.value.end && routeDirections) {
|
|
if (routeDirections.value && routeDirections.value.length > 0) {
|
|
for (const direction of routeDirections.value) {
|
|
if (direction.coordinates) {
|
|
await addLocation(
|
|
{ lat: Number(direction.coordinates.lat), lng: Number(direction.coordinates.lng) },
|
|
direction.time,
|
|
);
|
|
}
|
|
await delay(1000);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/* function routeToQueue() {
|
|
console.log('routeToQueue');
|
|
if (routeSet.value.start && routeSet.value.end && routeSegments) {
|
|
console.log('routeToQueue: start: ', routeSet.value.start);
|
|
setLocation(
|
|
{ lat: Number(routeSet.value.start.lat), lng: Number(routeSet.value.start.lng) },
|
|
0,
|
|
);
|
|
if (routeSegments.value) {
|
|
routeSegments.value.forEach((segment: routeSegments) => {
|
|
console.log('routeToQueue: segment: ', segment);
|
|
setLocation(
|
|
{ lat: Number(segment.toCoordinates.lat), lng: Number(segment.toCoordinates.lng) },
|
|
segment.timeSeconds,
|
|
);
|
|
});
|
|
}
|
|
console.log('routeToQueue: end: ', routeSet.value.end);
|
|
setLocation({ lat: Number(routeSet.value.end.lat), lng: Number(routeSet.value.end.lng) }, 0);
|
|
}
|
|
}
|
|
*/
|
|
function clearRoute() {
|
|
void leafletStore.clearRouteSegments;
|
|
routingOptions.waypoints = [];
|
|
routeSet.value.start = { lat: null, lng: null };
|
|
routeSet.value.end = { lat: null, lng: null };
|
|
$q.notify({ type: 'positive', message: 'Route cleared' });
|
|
}
|
|
|
|
const updateRoute = () => {
|
|
const waypoints: LeafLet.LatLng[] = [];
|
|
const start = routeSet.value.start;
|
|
const end = routeSet.value.end;
|
|
if (start && typeof start.lat === 'number' && typeof start.lng === 'number') {
|
|
waypoints.push(LeafLet.latLng(start.lat, start.lng));
|
|
}
|
|
if (end && typeof end.lat === 'number' && typeof end.lng === 'number') {
|
|
waypoints.push(LeafLet.latLng(end.lat, end.lng));
|
|
}
|
|
|
|
routingOptions.waypoints = waypoints;
|
|
};
|
|
|
|
const { clickedLatLng, handleMarkerClick, setStartRoute, setEndRoute } = useMarkerContextMenu(
|
|
routeSet,
|
|
updateRoute,
|
|
closeAllPopups,
|
|
);
|
|
|
|
const selectedItem = ref();
|
|
const contextMenu = ref();
|
|
|
|
function onDrawerContextMenu(e: MouseEvent, item: string) {
|
|
console.log('onDrawerContextMenu: ', item);
|
|
selectedItem.value = item;
|
|
}
|
|
function handleDrawerContextMenu(command: string) {
|
|
let notType: string = 'positive';
|
|
let notMsg: string = '';
|
|
switch (command) {
|
|
case 'zoom':
|
|
zoomTo(selectedItem.value);
|
|
break;
|
|
case 'delete':
|
|
try {
|
|
const ack = socketStore.simulationControl('delete', 0, selectedItem.value);
|
|
if (ack.sts === 'error') {
|
|
notType = 'negative';
|
|
}
|
|
if (ack.msg) {
|
|
notMsg = ack.msg;
|
|
}
|
|
} catch (error: unknown) {
|
|
notType = 'negative';
|
|
if (error instanceof Error) {
|
|
console.error('Simulation Command ERROR: ', error.message);
|
|
notMsg = `Simulation Command Error: ${error.message}`;
|
|
} else {
|
|
console.error('Simulation Command Error: ', error);
|
|
notMsg = 'Simulation Command Error: Unknow error';
|
|
}
|
|
} finally {
|
|
$q.notify({ type: notType, message: notMsg });
|
|
}
|
|
}
|
|
$q.notify(`context menu: ${command} ${selectedItem.value}`);
|
|
}
|
|
|
|
function handleAddLocation() {
|
|
if (clickedLatLng.value) {
|
|
const latlng = clickedLatLng.value;
|
|
closeAllPopups();
|
|
$q.notify(`add location...${latlng.toString()}`);
|
|
// reverseGeocode(latlng.lat, latlng.lng)
|
|
// .then((data) => {
|
|
// const NomAddress = data.address as unknown as NominatimAddress;
|
|
$q.dialog({
|
|
component: SetLocationDialog,
|
|
componentProps: {
|
|
lat: Number(latlng.lat),
|
|
lng: Number(latlng.lng),
|
|
// address: NomAddress,
|
|
},
|
|
})
|
|
.onOk(({ delay, address }) => {
|
|
void addLocation({ lat: Number(latlng.lat), lng: Number(latlng.lng) }, delay, address);
|
|
console.log(
|
|
'Confirmed location add: latitude: ' +
|
|
latlng.lat +
|
|
', longitude: ' +
|
|
latlng.lng +
|
|
', delay: ' +
|
|
delay,
|
|
);
|
|
})
|
|
.onCancel(() => {
|
|
console.log('Dialog cancelled');
|
|
})
|
|
.onDismiss(() => {
|
|
console.log('Dialog dismissed');
|
|
});
|
|
// })
|
|
// .catch((error) => {
|
|
// console.error('Error fetching reverse geocode:', error);
|
|
// });
|
|
}
|
|
}
|
|
/*
|
|
async function reverseGeocode(lat: number, lng: number) {
|
|
loading.value = true;
|
|
try {
|
|
const response = await reverseGeocodeRateLimited(lat, lng);
|
|
console.log('reverse geocode response: ', response);
|
|
return response;
|
|
} catch (error) {
|
|
console.error('Error fetching reverse geocode:', error);
|
|
throw error;
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
}
|
|
*/
|
|
|
|
async function addLocation(coords: coords, delay: number, address?: string) {
|
|
return new Promise((resolve, reject) => {
|
|
let notType: string = 'positive';
|
|
try {
|
|
const setCmdRsp = socketStore.simulationControl(
|
|
'add',
|
|
coords.lat,
|
|
coords.lng,
|
|
'',
|
|
delay,
|
|
address,
|
|
);
|
|
if (setCmdRsp.msg) {
|
|
responseMessage.value = setCmdRsp.msg;
|
|
}
|
|
if (setCmdRsp.sts === 'error') {
|
|
notType = 'negative';
|
|
reject(new Error(setCmdRsp.msg || 'Unknown error'));
|
|
}
|
|
resolve(setCmdRsp);
|
|
} catch (error: unknown) {
|
|
notType = 'negative';
|
|
if (error instanceof Error) {
|
|
console.error('Error setting location:', error.message);
|
|
responseMessage.value = `Failed to set location: ${error.message}`;
|
|
reject(error);
|
|
} else {
|
|
console.error('Error setting location:', error);
|
|
responseMessage.value = `Failed to set location: Unknown error`;
|
|
reject(new Error('Unknown error'));
|
|
}
|
|
} finally {
|
|
$q.notify({ type: notType, message: responseMessage.value });
|
|
}
|
|
});
|
|
}
|
|
|
|
function zoomToCoords(arg: string) {
|
|
const item = locationQueueData.value[arg];
|
|
if (!item || item.latitude == null || item.longitude == null) {
|
|
return;
|
|
}
|
|
leafletStore.setCenter(item.latitude, item.longitude);
|
|
leafletStore.setZoom(50);
|
|
qLocDrawer.value = false;
|
|
}
|
|
|
|
const findMyTimePast = computed(() => {
|
|
if (findMyUpdate.value) {
|
|
const diffInMs = Math.abs(now.value - findMyUpdate.value.timeStamp);
|
|
const seconds = Math.floor((diffInMs / 1000) % 60);
|
|
const minutes = Math.floor((diffInMs / (1000 * 60)) % 60);
|
|
const hours = Math.floor((diffInMs / (1000 * 60 * 60)) % 24);
|
|
const days = Math.floor(diffInMs / (1000 * 60 * 60 * 24));
|
|
|
|
if (days > 1) {
|
|
return days + ' days, ' + hours + ' hours, ' + minutes + 'minutes, ' + seconds + 'seconds ago'
|
|
} else if (hours > 1) {
|
|
return hours + ' hours, ' + minutes + 'minutes, ' + seconds + 'seconds ago'
|
|
} else if (minutes > 1) {
|
|
return minutes + ' minutes, ' + seconds + ' seconds ago';
|
|
} else {
|
|
return seconds + ' seconds ago'
|
|
}
|
|
} else {
|
|
return 'Find My Location not available';
|
|
}
|
|
});
|
|
|
|
function zoomTo(loc: string) {
|
|
switch (loc) {
|
|
case 'fmLoc':
|
|
if (findMyUpdate.value && findMyUpdate.value.latitude && findMyUpdate.value.longitude) {
|
|
leafletStore.setCenter(findMyUpdate.value.latitude, findMyUpdate.value.longitude);
|
|
} else {
|
|
$q.notify({ type: 'negative', message: 'Find My Location not available' });
|
|
}
|
|
break;
|
|
case 'simLoc':
|
|
if (
|
|
currentLocation.value &&
|
|
currentLocation.value.latitude &&
|
|
currentLocation.value.longitude
|
|
) {
|
|
leafletStore.setCenter(currentLocation.value.latitude, currentLocation.value.longitude);
|
|
} else {
|
|
$q.notify({ type: 'negative', message: 'Simulation Location not available' });
|
|
}
|
|
break;
|
|
case 'nextLoc':
|
|
if (nextLocation.value && nextLocation.value.latitude && nextLocation.value.longitude) {
|
|
leafletStore.setCenter(nextLocation.value.latitude, nextLocation.value.longitude);
|
|
} else {
|
|
$q.notify({ type: 'negative', message: 'Next Location not available' });
|
|
}
|
|
break;
|
|
default:
|
|
$q.notify({ type: 'negative', message: 'Invalid location' });
|
|
break;
|
|
}
|
|
}
|
|
|
|
const timer = ref();
|
|
onMounted(() => {
|
|
if (findMyUpdate.value && findMyUpdate.value.latitude && findMyUpdate.value.longitude) {
|
|
leafletStore.setCenter(findMyUpdate.value.latitude, findMyUpdate.value.longitude);
|
|
} else {
|
|
console.log('favorites: home: ', favorites.home.coords.lat, favorites.home.coords.lng);
|
|
leafletStore.setCenter(favorites.home.coords.lat, favorites.home.coords.lng);
|
|
}
|
|
socketStore.requestUpdate();
|
|
timer.value = setInterval(() => {
|
|
now.value = Date.now();
|
|
}, 1000);
|
|
});
|
|
onUnmounted(() => {
|
|
clearInterval(timer.value);
|
|
});
|
|
</script>
|
|
|
|
<style lang="sass">
|
|
.l-map
|
|
height: 100vh
|
|
width: 100vw
|
|
|
|
.q-item.q-router-link--active, .q-item--active
|
|
background-color: $accent
|
|
color: $primary
|
|
|
|
.leafletDrawer
|
|
background-color: $dark
|
|
color: $dark
|
|
|
|
.marker-popup .leaflet-popup-content-wrapper, .marker-popup .leaflet-popup-tip
|
|
background-color: $dark
|
|
.marker-popup .leaflet-popup-content
|
|
margin: 13px 10px 13px 10px
|
|
</style>
|