lots of changes

This commit is contained in:
2026-04-14 09:53:12 -04:00
parent 27a2904bab
commit 32c4f2a835
16 changed files with 2461 additions and 1815 deletions

View File

@@ -6,7 +6,7 @@
style="height: 600px; max-width: 800px; width: 100vw"
class="rounded-borders"
>
<q-footer :class="$q.dark.isActive ? 'bg-primary' : 'bg-black'" style="height: 48px">
<q-footer :class="$q.dark.isActive ? 'bg-primary' : 'bg-black'" class="z-top" style="height: 48px">
<q-toolbar>
<q-btn
class="q-mr-sm"
@@ -49,9 +49,9 @@
</q-footer>
<q-drawer
v-model="qLocDrawer"
behavior="mobile"
show-if-above
overlay
:width="250"
:width="300"
side="left"
:breakpoint="500"
@mouseenter="miniState = false"
@@ -64,25 +64,48 @@
><span class="bold">Location Queue: </span> {{ simulationState }}</q-item-label
>
<q-separator />
<LocationMark
v-for="(key, index) in locationQueueOrder"
<div
v-for="(key, index) in locationQueueOrderFiltered"
:key="key"
:loc_id="key"
:active="
(locationQueueData as Record<string, any>)[key]?.loc_id === currentLocation?.loc_id
"
:index="index"
:isLast="index != locationQueueOrder.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"
@item-clicked="zoomToCoods"
/>
@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>
@@ -151,7 +174,7 @@
</L-Layer-Group>
<L-Layer-Group v-if="locationQueueOrder">
<L-Marker
v-for="locid in locationQueueOrder"
v-for="locid in locationQueueOrderFiltered"
:key="locid"
:icon="getCustomIcon(locid) as any"
:lat-lng="[
@@ -174,12 +197,16 @@
v-if="findMyUpdate"
:icon="fmIcon as any"
:lat-lng="[findMyUpdate.latitude ?? 0, findMyUpdate.longitude ?? 0]"
></L-Marker>
>
<L-Tooltip>
{{ findMyTimePast }}
</L-Tooltip>
</L-Marker>
<L-Circle
:fillOpacity="0.5"
:lat-lng="[findMyUpdate.latitude ?? 0, findMyUpdate.longitude ?? 0]"
:radius="findMyUpdate.horizontalAccuracy"
color="firebrick"
color="#f5bb39"
fillColor="indianred"
></L-Circle>
</L-Layer-Group>
@@ -193,33 +220,43 @@
<script lang="ts" setup>
import { useQuasar } from 'quasar';
import { computed, onMounted, reactive, ref } from 'vue';
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 } from 'leaflet-extra-markers';
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 } from '@vue-leaflet/vue-leaflet';
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 LocationMark from 'components/LocationMark.vue';
import LocationItem from 'components/LocationItem.vue';
import SetLocationDialog from 'components/SetLocationDialog.vue';
import { customRouter } from 'functions/serviceURL';
//import { reverseGeocodeRateLimited } from 'functions/reverseGeocode';
import { useRoutingEvents } from '../composables/useRoutingEvents';
import { useMarkerContextMenu } from '../composables/useMarkerContextMenu';
import type { IRouter } from 'leaflet-routing-machine';
// Types
import type {
coords,
SearchControlProps,
// NominatimAddress,
} from 'components/models';
import type { coords, SearchControlProps } from 'components/models';
import type { LeafletMouseEvent, Map } from 'leaflet';
// Stores
@@ -245,6 +282,7 @@ const {
const $q = useQuasar();
const now = ref(Date.now());
const mapRef = ref();
const responseMessage = ref('');
const miniState = ref(true);
@@ -295,11 +333,52 @@ const fmIcon = new Icon({
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
? locationQueueOrder.value.indexOf(currentLocation.value.loc_id)
? locationQueueOrderFiltered.value.indexOf(currentLocation.value.loc_id)
: 0;
const locationIndex = locationQueueOrder.value.indexOf(locid);
const locationIndex = locationQueueOrderFiltered.value.indexOf(locid);
const updatedIndex = (locationIndex - currentIndex);
if (currentLocation.value && currentLocation.value.loc_id === locid) {
return new Icon({
color: 'pink',
@@ -310,24 +389,52 @@ const getCustomIcon = (locid: string) => {
svg: PinStarPanel,
});
}
return new Icon({
color: 'blue',
accentColor: 'firebrick',
content: (locationIndex - currentIndex).toString(),
contentColor: 'white',
scale: 1,
svg: PinCirclePanel,
});
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;
}>({
waypoints: [],
router: customRouter as unknown as IRouter,
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();
@@ -355,7 +462,7 @@ async function routeToQueue() {
if (routeDirections.value && routeDirections.value.length > 0) {
for (const direction of routeDirections.value) {
if (direction.coordinates) {
await setLocation(
await addLocation(
{ lat: Number(direction.coordinates.lat), lng: Number(direction.coordinates.lng) },
direction.time,
);
@@ -415,6 +522,45 @@ const { clickedLatLng, handleMarkerClick, setStartRoute, setEndRoute } = useMark
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;
@@ -431,8 +577,8 @@ function handleAddLocation() {
// address: NomAddress,
},
})
.onOk((delay: number) => {
void setLocation({ lat: Number(latlng.lat), lng: Number(latlng.lng) }, delay);
.onOk(({ delay, address }) => {
void addLocation({ lat: Number(latlng.lat), lng: Number(latlng.lng) }, delay, address);
console.log(
'Confirmed location add: latitude: ' +
latlng.lat +
@@ -470,11 +616,18 @@ async function reverseGeocode(lat: number, lng: number) {
}
*/
async function setLocation(coords: coords, delay: number) {
async function addLocation(coords: coords, delay: number, address?: string) {
return new Promise((resolve, reject) => {
let notType: string = 'positive';
try {
const setCmdRsp = socketStore.simulationControl('add', delay, coords.lat, coords.lng);
const setCmdRsp = socketStore.simulationControl(
'add',
coords.lat,
coords.lng,
'',
delay,
address,
);
if (setCmdRsp.msg) {
responseMessage.value = setCmdRsp.msg;
}
@@ -500,7 +653,7 @@ async function setLocation(coords: coords, delay: number) {
});
}
function zoomToCoods(arg: string) {
function zoomToCoords(arg: string) {
const item = locationQueueData.value[arg];
if (!item || item.latitude == null || item.longitude == null) {
return;
@@ -510,6 +663,28 @@ function zoomToCoods(arg: string) {
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':
@@ -543,6 +718,7 @@ function zoomTo(loc: string) {
}
}
const timer = ref();
onMounted(() => {
if (findMyUpdate.value && findMyUpdate.value.latitude && findMyUpdate.value.longitude) {
leafletStore.setCenter(findMyUpdate.value.latitude, findMyUpdate.value.longitude);
@@ -551,6 +727,12 @@ onMounted(() => {
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>