lots of changes
This commit is contained in:
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user