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

3410
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -15,7 +15,8 @@
"postinstall": "quasar prepare" "postinstall": "quasar prepare"
}, },
"dependencies": { "dependencies": {
"@quasar/extras": "^1.16.4", "@lucide/vue": "^1.7.0",
"@quasar/extras": "^1.18.0",
"@sentry/tracing": "^7.120.4", "@sentry/tracing": "^7.120.4",
"@sentry/vue": "^10.47.0", "@sentry/vue": "^10.47.0",
"@vue-leaflet/vue-leaflet": "^0.10.1", "@vue-leaflet/vue-leaflet": "^0.10.1",
@@ -27,14 +28,14 @@
"leaflet-routing-machine": "^3.2.12", "leaflet-routing-machine": "^3.2.12",
"openrouteservice-js": "^0.4.1", "openrouteservice-js": "^0.4.1",
"pinia": "^3.0.4", "pinia": "^3.0.4",
"quasar": "^2.16.0", "quasar": "^2.19.3",
"socket.io-client": "^4.8.3", "socket.io-client": "^4.8.3",
"vue": "^3.5.22", "vue": "^3.5.22",
"vue-router": "^5.0.0" "vue-router": "^5.0.0"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.14.0", "@eslint/js": "^9.14.0",
"@quasar/app-vite": "^2.1.0", "@quasar/app-vite": "^2.6.0",
"@types/leaflet": "^1.9.21", "@types/leaflet": "^1.9.21",
"@types/leaflet-contextmenu": "^1.4.4", "@types/leaflet-contextmenu": "^1.4.4",
"@types/node": "^20.5.9", "@types/node": "^20.5.9",

View File

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

View File

@@ -3,13 +3,13 @@ import { storeToRefs } from 'pinia';
import { useSocketioStore } from 'stores/socketio'; import { useSocketioStore } from 'stores/socketio';
import { computed, onMounted, onUnmounted, ref } from 'vue'; import { computed, onMounted, onUnmounted, ref } from 'vue';
import type { NominatimResponse } from 'components/models'; import type { NominatimResponse } from 'components/models';
import { Icon, PinCirclePanel } from 'leaflet-extra-markers'; import { Icon, PinCirclePanel } from 'leaflet-extra-markers';
import FormattedAddress from 'components/FormattedAddress.vue'; import FormattedAddress from 'components/FormattedAddress.vue';
const socketStore = useSocketioStore(); const socketStore = useSocketioStore();
const { currentLocation, locationQueueOrder, simulationRunning } = storeToRefs(socketStore); const { currentLocation, locationQueueOrder, locationQueueData, simulationRunning } =
storeToRefs(socketStore);
const props = defineProps({ const props = defineProps({
address: { address: {
@@ -44,6 +44,10 @@ const props = defineProps({
type: Boolean, type: Boolean,
default: false, default: false,
}, },
isCurrentDelay: {
type: Boolean,
default: false,
},
loc_id: { loc_id: {
type: String, type: String,
required: true, required: true,
@@ -56,6 +60,10 @@ const props = defineProps({
type: Number, type: Number,
default: 0, default: 0,
}, },
status: {
type: String,
default: 'queued',
},
}); });
// Define custom events that this component can emit // Define custom events that this component can emit
@@ -69,8 +77,30 @@ function itemClicked() {
const currentTime = ref(new Date()); const currentTime = ref(new Date());
let timerId: number; let timerId: number;
const calculateDeltaT = computed(() => { function formatInAgo(seconds: number) {
let delta = ''; let delta;
if (seconds === 0) {
delta = 'now';
}
if (seconds > 0) {
const minutes: number = Math.floor(seconds / 60);
const hours: number = Math.floor(minutes / 60);
const days: number = Math.floor(hours / 24);
if (days > 0) {
delta = `in ${days} day${days > 1 ? 's' : ''}`;
} else if (hours > 0) {
delta = `in ${hours} hour${hours > 1 ? 's' : ''}`;
} else if (minutes > 0) {
delta = `in ${minutes} minute${minutes > 1 ? 's' : ''}`;
} else {
delta = `in ${seconds} second${seconds > 1 ? 's' : ''}`;
}
}
return delta;
}
const calculateDeltaTime = computed(() => {
let delta;
if (props.active) { if (props.active) {
delta = 'now'; delta = 'now';
} else if (props.end && props.end !== '') { } else if (props.end && props.end !== '') {
@@ -85,30 +115,24 @@ const calculateDeltaT = computed(() => {
if (days > 0) { if (days > 0) {
delta = `${days} day${days > 1 ? 's' : ''} ago`; delta = `${days} day${days > 1 ? 's' : ''} ago`;
} else if (hours > 0) { } else if (hours > 0) {
delta = `${hours} hour${hours > 1 ? 's' : ''} ago`; delta = `${hours} hr${hours > 1 ? 's' : ''} ago`;
} else if (minutes > 0) { } else if (minutes > 0) {
delta = `${minutes} minute${minutes > 1 ? 's' : ''} ago`; delta = `${minutes} min${minutes > 1 ? 's' : ''} ago`;
} else { } else {
delta = `${seconds} second${seconds > 1 ? 's' : ''} ago`; delta = `${seconds} sec${seconds > 1 ? 's' : ''} ago`;
} }
} else { } else {
const futureTime: Date = new Date(props.start); let startSlice;
const nowMs: number = currentTime.value.getTime(); if (currentIndex.value > 0) {
const futureMs: number = futureTime.getTime(); startSlice = currentIndex.value;
const diffMs: number = Math.abs(nowMs - futureMs);
const seconds: number = Math.floor(diffMs / 1000);
const minutes: number = Math.floor(seconds / 60);
const hours: number = Math.floor(minutes / 60);
const days: number = Math.floor(hours / 24);
if (days > 0) {
delta = `in ${days} day${days > 1 ? 's' : ''}`;
} else if (hours > 0) {
delta = `in ${hours} hour${hours > 1 ? 's' : ''}`;
} else if (minutes > 0) {
delta = `in ${minutes} minute${minutes > 1 ? 's' : ''}`;
} else { } else {
delta = `in ${seconds} second${seconds > 1 ? 's' : ''}`; startSlice = 0;
} }
const waitingFor = locationQueueOrder.value.slice(startSlice, myIndex.value + 1);
const willWait = waitingFor.reduce((sum, loc_id) => {
return sum + (locationQueueData.value[loc_id]?.delay || 0);
}, 0);
delta = formatInAgo(willWait);
} }
return delta; return delta;
}); });
@@ -124,8 +148,20 @@ const secondsToTime = computed(() => {
}); });
const secondsToTime = (seconds: number) => {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const remainingSeconds = seconds % 60;
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${remainingSeconds
.toString()
.padStart(2, '0')}`;
};
const timeToSeconds = (timeIn: string) => {
const a = timeIn.split(':');
const seconds = +a[0] * 60 * 60 + +a[1] * 60 + +a[2];
return seconds;
};
*/ */
const humanReadableDateTime = (iso: string) => { const humanReadableDateTime = (iso: string) => {
return new Date(iso).toLocaleDateString('en-US', { return new Date(iso).toLocaleDateString('en-US', {
@@ -234,14 +270,25 @@ const itemClass = computed(() => {
}); });
const customIcon = computed(() => { const customIcon = computed(() => {
return new Icon({ if (myUpdatedIndex.value > 0) {
color: 'blue', return new Icon({
accentColor: 'firebrick', color: 'blue',
content: myUpdatedIndex.value.toString(), accentColor: 'firebrick',
contentColor: 'white', content: myUpdatedIndex.value.toString(),
scale: 1, contentColor: 'white',
svg: PinCirclePanel, scale: 1,
}); svg: PinCirclePanel,
});
} else {
return new Icon({
color: 'black',
accentColor: 'grey',
content: myUpdatedIndex.value.toString(),
contentColor: 'white',
scale: 1,
svg: PinCirclePanel,
});
}
}); });
const iconElement = computed(() => { const iconElement = computed(() => {
@@ -256,14 +303,70 @@ onMounted(() => {
timerId = requestAnimationFrame(update); timerId = requestAnimationFrame(update);
}); });
const delayValue = computed({
get: () => props.delay,
set: (val) => socketStore.updateLocationMark(props.loc_id, 'delay', val),
});
const getDelayAttrs = computed(() => {
if (props.delay <= 260) {
return { units: 's', delay: props.delay, step: 10, max: 300 } as const;
} else if (props.delay <= 3600) {
return { units: 'm', delay: props.delay / 60, step: 60, max: 4500 } as const;
} else {
return { units: 'h', delay: props.delay / 3600, step: 3600, max: 21600 } as const;
}
});
const displayDelay = computed(() => {
return props.index > currentIndex.value;
});
const delayActive = computed(() => {
return props.index === currentIndex.value + 1;
});
onUnmounted(() => { onUnmounted(() => {
cancelAnimationFrame(timerId); cancelAnimationFrame(timerId);
}); });
</script> </script>
<template> <template>
<q-item v-ripple clickable :active="active" @click="itemClicked" :class="itemClass"> <q-item v-if="displayDelay" :active="delayActive">
<q-item-section avatar>
<q-icon name="access_time" color="secondary" />
</q-item-section>
<q-item-section> <q-item-section>
<q-knob
show-value
v-model="delayValue"
size="50px"
color="secondary"
track-color="dark-page"
:step="getDelayAttrs.step"
:max="getDelayAttrs.max"
>
{{ getDelayAttrs.delay }} {{ getDelayAttrs.units }}
</q-knob>
<!--
<q-slider
v-model="delayValue"
:min="0"
:step="getDelayAttrs.step"
:max="getDelayAttrs.max"
label
markers
snap
label-always
:label-value="getDelayAttrs.delay + getDelayAttrs.units"
:marker-labels="markerLabel"
color="primary"
/>
-->
</q-item-section>
</q-item>
<q-separator inset spaced v-if="displayDelay" />
<q-item v-ripple clickable :active="active" @click="itemClicked" :class="itemClass">
<q-item-section style="width: 70%">
<q-item-label> <q-item-label>
<FormattedAddress :address="props.address" /> <FormattedAddress :address="props.address" />
<q-tooltip> {{ latitude }}, {{ longitude }} </q-tooltip> <q-tooltip> {{ latitude }}, {{ longitude }} </q-tooltip>
@@ -276,16 +379,25 @@ onUnmounted(() => {
</q-item-label> </q-item-label>
<q-item-label caption lines="1" v-else> delay: {{ delay }} seconds </q-item-label> <q-item-label caption lines="1" v-else> delay: {{ delay }} seconds </q-item-label>
</q-item-section> </q-item-section>
<q-item-section side> <q-item-section
side
style="
padding: 30px 0 0 10px;
height: 100%;
width: 35%;
display: flex;
justify-content: flex-end;
align-items: center;
"
>
<q-icon v-html="iconElement.outerHTML" />
<q-item-label caption lines="1" v-if="simulationRunning"> <q-item-label caption lines="1" v-if="simulationRunning">
{{ calculateDeltaT }} {{ calculateDeltaTime }}
</q-item-label> </q-item-label>
<q-icon class="q-mt-lg" v-html="iconElement.outerHTML" />
</q-item-section> </q-item-section>
</q-item> </q-item>
<q-separator spaced inset v-if="isLast" /> <q-separator spaced inset v-if="!isLast" />
</template> </template>
<style lang="sass" scoped> <style lang="sass" scoped>
.past .past
color: gray color: gray

View File

@@ -19,8 +19,14 @@ const leafletStore = useLeafletStore();
const socketStore = useSocketioStore(); const socketStore = useSocketioStore();
const { center, markerLatLng, zoom } = storeToRefs(leafletStore); const { center, markerLatLng, zoom } = storeToRefs(leafletStore);
const { simulationRunning, simulationState, simulationQueueLength, icloudMonitor, testMode } = const {
storeToRefs(socketStore); simulationRunning,
simulationState,
simulationQueueLength,
icloudMonitor,
testMode,
gpsNoise,
} = storeToRefs(socketStore);
const menuOpen = ref(false); const menuOpen = ref(false);
const favoritesMap = favorites as Record<string, unknown>; const favoritesMap = favorites as Record<string, unknown>;
@@ -67,6 +73,13 @@ function handleTestToggle() {
} }
} }
function handleGpsNoiseToggle() {
const response = socketStore.simulationControl('gps-noise');
if (response.sts === 'error') {
$q.notify({ type: 'negative', message: response.msg ?? 'Failed to toggle test mode' });
}
}
function handleControlClick(cmdAttr: ControlAction) { function handleControlClick(cmdAttr: ControlAction) {
if (cmdAttr.cnfrm) { if (cmdAttr.cnfrm) {
$q.dialog({ $q.dialog({
@@ -216,7 +229,7 @@ function handleControlClick(cmdAttr: ControlAction) {
@click="handleFavClick(favObj.coords)" @click="handleFavClick(favObj.coords)"
> >
<q-item-section avatar> <q-item-section avatar>
<q-avatar :icon="favObj.icon" color="primary" text-color="white" /> <q-avatar :icon="favObj.icon" color="secondary" size="sm" text-color="black" />
</q-item-section> </q-item-section>
<q-item-section> <q-item-section>
<q-item-label>{{ favObj.name }}</q-item-label> <q-item-label>{{ favObj.name }}</q-item-label>
@@ -224,7 +237,7 @@ function handleControlClick(cmdAttr: ControlAction) {
</q-item> </q-item>
<q-item v-else-if="hasSubitems(favObj)" clickable v-ripple dense dark> <q-item v-else-if="hasSubitems(favObj)" clickable v-ripple dense dark>
<q-item-section avatar> <q-item-section avatar>
<q-avatar :icon="favObj.icon" color="primary" text-color="white" /> <q-avatar :icon="favObj.icon" color="secondary" size="sm" text-color="black" />
</q-item-section> </q-item-section>
<q-item-section> <q-item-section>
<q-item-label>{{ favObj.name }}</q-item-label> <q-item-label>{{ favObj.name }}</q-item-label>
@@ -245,7 +258,7 @@ function handleControlClick(cmdAttr: ControlAction) {
@click="handleFavClick(favSubObj.coords)" @click="handleFavClick(favSubObj.coords)"
> >
<q-item-section avatar> <q-item-section avatar>
<q-avatar :icon="favSubObj.icon" color="primary" text-color="white" /> <q-avatar :icon="favSubObj.icon" color="secondary" size="sm" text-color="black" />
</q-item-section> </q-item-section>
<q-item-section> <q-item-section>
<q-item-label>{{ favSubObj.name }}</q-item-label> <q-item-label>{{ favSubObj.name }}</q-item-label>
@@ -276,6 +289,21 @@ function handleControlClick(cmdAttr: ControlAction) {
<q-item-label>Test Mode</q-item-label> <q-item-label>Test Mode</q-item-label>
</q-item-section> </q-item-section>
</q-item> </q-item>
<q-item dense dark tag="label" v-ripple>
<q-item-section avatar>
<q-toggle
v-model="gpsNoise"
size="sm"
color="brown"
@update:model-value="handleGpsNoiseToggle"
dark
dense
/>
</q-item-section>
<q-item-section>
<q-item-label>GPS Noise</q-item-label>
</q-item-section>
</q-item>
<q-item <q-item
dense dense
dark dark

View File

@@ -66,7 +66,7 @@ onMounted(async () => {
}); });
function onOkClick() { function onOkClick() {
onDialogOK(delay.value); onDialogOK({delay: delay.value, address: address.value});
} }
</script> </script>
<style lang="sass" scoped> <style lang="sass" scoped>

View File

@@ -48,6 +48,7 @@ watch(
}, },
{ deep: true }, { deep: true },
); );
</script> </script>
<template> <template>
<div class="flex flex-center col q-ma-lg q-gutter-md"> <div class="flex flex-center col q-ma-lg q-gutter-md">

View File

@@ -3,13 +3,8 @@ import { useSocketioStore } from 'stores/socketio';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
const socketioStore = useSocketioStore(); const socketioStore = useSocketioStore();
const { const { sockConnected, deviceConnected, tunnelConnected, simulationRunning, icloudMonitor, testMode } =
sockConnected, storeToRefs(socketioStore);
deviceConnected,
tunnelConnected,
simulationRunning,
icloudMonitor,
} = storeToRefs(socketioStore);
function statusDevColor(state: string | boolean): string { function statusDevColor(state: string | boolean): string {
if (typeof state === 'boolean') { if (typeof state === 'boolean') {
return state ? 'green' : 'red'; return state ? 'green' : 'red';
@@ -29,53 +24,57 @@ function statusDevColor(state: string | boolean): string {
</script> </script>
<template> <template>
<q-toolbar class="bg-primary text-white"> <q-toolbar :class="testMode ? 'bg-warning text-black' : 'bg-primary text-white'">
<div class="flex col q-gutter-md align-center justify-start content-center"> <div class="flex col q-gutter-md align-center justify-start content-center">
<q-space /> <q-space />
<div style="width: 80vw" class="flex justify-end"> <div style="width: 80vw" class="flex justify-end">
<q-btn <q-btn
size="sm"
rounded rounded
push push
size="sm"
icon="settings" icon="settings"
class="q-mr-sm" class="q-mr-sm"
@click="socketioStore.toggleSock()" @click="socketioStore.toggleSock()"
> >
<q-badge :color="statusDevColor(sockConnected)" rounded floating class="q-mr-sm" /> <q-badge :color="statusDevColor(sockConnected)" rounded floating class="q-mr-sm" />
</q-btn> </q-btn>
<q-btn size="sm" rounded icon="phone_iphone" class="q-mr-sm"> <q-btn
<q-badge rounded
:color="statusDevColor(deviceConnected)" push
@click="socketioStore.requestUpdate()" size="sm"
rounded icon="phone_iphone"
floating class="q-mr-sm"
class="q-mr-sm" @click="socketioStore.requestUpdate()"
/> >
<q-badge :color="statusDevColor(deviceConnected)" rounded floating class="q-mr-sm" />
</q-btn> </q-btn>
<q-btn <q-btn
size="sm"
@click="socketioStore.requestUpdate()"
rounded rounded
push
size="sm"
icon="subway" icon="subway"
class="q-mr-sm" class="q-mr-sm"
@click="socketioStore.requestUpdate()"
> >
<q-badge :color="statusDevColor(tunnelConnected)" rounded floating class="q-mr-sm" /> <q-badge :color="statusDevColor(tunnelConnected)" rounded floating class="q-mr-sm" />
</q-btn> </q-btn>
<q-btn <q-btn
size="sm"
@click="socketioStore.requestUpdate()"
rounded rounded
push
size="sm"
icon="location_on" icon="location_on"
class="q-mr-sm" class="q-mr-sm"
@click="socketioStore.requestUpdate()"
> >
<q-badge :color="statusDevColor(simulationRunning)" rounded floating class="q-mr-sm" /> <q-badge :color="statusDevColor(simulationRunning)" rounded floating class="q-mr-sm" />
</q-btn> </q-btn>
<q-btn <q-btn
size="sm"
@click="socketioStore.requestUpdate()"
rounded rounded
push
size="sm"
icon="cloud" icon="cloud"
class="q-mr-sm" class="q-mr-sm"
@click="socketioStore.icloudMonitorControl('refresh')"
> >
<q-badge :color="statusDevColor(icloudMonitor)" rounded floating class="q-mr-sm" /> <q-badge :color="statusDevColor(icloudMonitor)" rounded floating class="q-mr-sm" />
</q-btn> </q-btn>

View File

@@ -9,7 +9,10 @@ export type SimulationCommands =
| 'clear' | 'clear'
| 'end' | 'end'
| 'add' | 'add'
| 'test-mode'; | 'test-mode'
| 'delete'
| 'next'
| 'gps-noise';
export type DeviceCommands = 'start_tunnel' | 'stop_tunnel' | 'shutdown' | 'reboot'; export type DeviceCommands = 'start_tunnel' | 'stop_tunnel' | 'shutdown' | 'reboot';
@@ -43,14 +46,21 @@ export interface DevCtrlAttr {
export interface LocationQueue { export interface LocationQueue {
[key: string]: LocationMark; [key: string]: LocationMark;
} }
export interface LocationMarkUpdateResponse {
command_status: string;
command: string;
message: string;
data: LocationMark;
}
interface LocationMark { export interface LocationMark {
loc_id: string; loc_id: string;
latitude: number | undefined | null; latitude: number | undefined | null;
longitude: number | undefined | null; longitude: number | undefined | null;
address?: string | undefined | null; address?: string | undefined | null;
delay?: number | undefined | null; delay?: number | undefined | null;
start: string | undefined | null; start: string | undefined | null;
status: string | undefined | null;
end?: string | undefined | null; end?: string | undefined | null;
} }
@@ -59,6 +69,7 @@ interface LocationMark {
export interface ServerToClientEvents { export interface ServerToClientEvents {
noArg: () => void; noArg: () => void;
withAck: (a: string, callback: (b: number) => void) => void; withAck: (a: string, callback: (b: number) => void) => void;
test_prompt: (callback: (s: string) => void) => void;
simulation_status: (c: SimulationStatus) => void; simulation_status: (c: SimulationStatus) => void;
status: (d: StatusUpdate) => void; status: (d: StatusUpdate) => void;
device_status: (d: DeviceStatus) => void; device_status: (d: DeviceStatus) => void;
@@ -66,6 +77,14 @@ export interface ServerToClientEvents {
message: (e: string) => void; message: (e: string) => void;
icloud_2fa_request: (callback: (e: number) => void) => void; icloud_2fa_request: (callback: (e: number) => void) => void;
fmf_update: (d: FindMyUpdate) => void; fmf_update: (d: FindMyUpdate) => void;
queue_data_update: (d: QueueData) => void;
location_item_update: (d: LocationItemUpdate) => void;
}
export interface LocationItemUpdate {
loc_id: string;
data: LocationMark;
} }
export interface SimulationStatus { export interface SimulationStatus {
@@ -78,6 +97,16 @@ export interface SimulationStatus {
next_move?: number; next_move?: number;
} }
export interface QueueData {
simulation_queue: {
active: boolean;
data: LocationQueue;
order: string[];
state: string | undefined | null;
worker_task: string | undefined | null;
}
}
export interface StatusUpdate { export interface StatusUpdate {
connected_clients: { [key: string]: string }; connected_clients: { [key: string]: string };
current_location: { current_location: {
@@ -103,6 +132,7 @@ export interface StatusUpdate {
[key: string]: LocationMark; [key: string]: LocationMark;
}; };
order: string[]; order: string[];
gps_noise: boolean;
state: string | undefined | null; state: string | undefined | null;
worker_task: string | undefined | null; worker_task: string | undefined | null;
}; };
@@ -167,12 +197,15 @@ export interface FindMyUpdate {
export interface ClientToServerEvents { export interface ClientToServerEvents {
message: (e: string, callback: (b: boolean, r: string) => void) => void; message: (e: string, callback: (b: boolean, r: string) => void) => void;
request_update: (callback: (response: StatusUpdate) => void) => void; request_update: (callback: (response: StatusUpdate) => void) => void;
send_test_prompt: (p: string, callback: (response: string) => void) => void;
simulation_control: ( simulation_control: (
args: { args: {
command: SimulationCommands; command: SimulationCommands;
latitude?: number | null | undefined; latitude?: number | null | undefined;
longitude?: number | null | undefined; longitude?: number | null | undefined;
delay?: number | undefined; delay?: number | null | undefined;
loc_id?: string | null | undefined;
address?: string | null | undefined;
}, },
callback: (response: SimulationControlResponse) => void, callback: (response: SimulationControlResponse) => void,
) => void; ) => void;
@@ -196,18 +229,40 @@ export interface ClientToServerEvents {
}, },
callback?: (response: iCloudMonitorResponse) => void, callback?: (response: iCloudMonitorResponse) => void,
) => void; ) => void;
location_item_update: (
args: {
loc_id: string;
key: string;
value: string | number;
},
callback?: (response: LocationMarkUpdateResponse) => void,
) => void;
} }
export interface SimulationControlResponse { export interface SimulationControlResponse {
status: string; command_status: string;
command: SimulationCommands; command: SimulationCommands;
loc_id: string; command_class: string;
data?: SimulationControlResponseData | undefined | null;
simulation_noise?: boolean;
message?: string | undefined; message?: string | undefined;
}
interface SimulationControlResponseData {
simulation_active?: boolean;
simulation_queue_state?: string;
loc_id?: string;
latitude?: number | undefined | null; latitude?: number | undefined | null;
longitude?: number | undefined | null; longitude?: number | undefined | null;
delay?: number | undefined | null; delay?: number | undefined | null;
start_time?: string | undefined | null; start_time?: string | undefined | null;
end_time?: string | undefined | null; end_time?: string | undefined | null;
status?: string | undefined | null;
next_move?: number | undefined | null;
address?: string | undefined | null;
simulation_noise?: boolean;
test_mode?: boolean;
} }
interface DeviceControlResponse { interface DeviceControlResponse {
@@ -217,11 +272,13 @@ interface DeviceControlResponse {
} }
export interface iCloudMonitorResponse { export interface iCloudMonitorResponse {
status: string; command_status: string;
command: string; command: string;
command_class: string;
icloud_monitor_enabled?: boolean | undefined | null; icloud_monitor_enabled?: boolean | undefined | null;
icloud_monitor_running?: boolean | undefined | null; icloud_monitor_running?: boolean | undefined | null;
message?: string | undefined | null; message?: string | undefined | null;
error?: string | undefined | null;
} }
// END CLIENT TO SERVER // END CLIENT TO SERVER

View File

@@ -12,14 +12,14 @@
// to match your app's branding. // to match your app's branding.
// Tip: Use the "Theme Builder" on Quasar's documentation website. // Tip: Use the "Theme Builder" on Quasar's documentation website.
$primary: #02006c; $primary: #161B36;
$secondary: #010057; $secondary: #8FA9BF;
$accent: #9c27b0; $accent: #5a728a;
$dark: #1d1d1d; $dark: #1d1d1d;
$dark-page: #03002e; $dark-page: #0B1026;
$positive: #21ba45; $positive: #4CAF50;
$negative: #c10015; $negative: #EF5350;
$info: #31ccec; $info: #42A5F5;
$warning: #f2c037; $warning: #FFCA28;

View File

@@ -1,6 +1,7 @@
import { Utilities } from '@vue-leaflet/vue-leaflet'; import { Utilities } from '@vue-leaflet/vue-leaflet';
import type { PropType } from 'vue'; import type { PropType } from 'vue';
import type { IRouter, LineOptions } from 'leaflet-routing-machine'; import type { IRouter, LineOptions } from 'leaflet-routing-machine';
import type * as L from 'leaflet';
export interface RoutingControlProps { export interface RoutingControlProps {
waypoints: unknown[]; waypoints: unknown[];
@@ -9,6 +10,9 @@ export interface RoutingControlProps {
fitSelectedRoutes?: string | boolean; fitSelectedRoutes?: string | boolean;
lineOptions?: LineOptions | undefined; lineOptions?: LineOptions | undefined;
routeLine?: ((route: unknown) => unknown) | undefined; routeLine?: ((route: unknown) => unknown) | undefined;
createMarker?:
| ((i: number, waypoint: { latLng: L.LatLng }, n: number) => L.Marker | false)
| undefined;
autoRoute?: boolean; autoRoute?: boolean;
routeWhileDragging?: boolean; routeWhileDragging?: boolean;
routeDragInterval?: number; routeDragInterval?: number;
@@ -43,6 +47,12 @@ export const routingControlProps = {
type: Function as PropType<((route: unknown) => unknown) | undefined>, type: Function as PropType<((route: unknown) => unknown) | undefined>,
default: undefined, default: undefined,
}, },
createMarker: {
type: Function as PropType<
((i: number, waypoint: { latLng: L.LatLng }, n: number) => L.Marker | false) | undefined
>,
default: undefined,
},
autoRoute: { autoRoute: {
type: Boolean, type: Boolean,
default: true, default: true,

View File

@@ -4,7 +4,7 @@ export const customRouter = openrouteserviceV2({
api_key: '', api_key: '',
profile: 'driving-car', profile: 'driving-car',
geometry_simplify: true, geometry_simplify: true,
host: '/api/proxy/ors/', host: '/api/ors/proxy/ors',
}); });
//export const customRouter = L.Routing.osrmv1({ //export const customRouter = L.Routing.osrmv1({

View File

@@ -50,8 +50,9 @@ import { useSocketioStore } from 'stores/socketio';
import MenuBar from 'components/MenuBar.vue'; import MenuBar from 'components/MenuBar.vue';
import StatusBar from 'components/StatusBar.vue'; import StatusBar from 'components/StatusBar.vue';
const socketioStore = useSocketioStore(); const socketStore = useSocketioStore();
const drawer = ref(false); const drawer = ref(false);
/* /*
const route = useRoute(); const route = useRoute();
@@ -114,7 +115,7 @@ const menuList = [
]; ];
*/ */
onMounted(() => { onMounted(() => {
socketioStore.bindEvents(); socketStore.bindEvents();
socketioStore.connect(); socketStore.connect();
}); });
</script> </script>

View File

@@ -20,7 +20,7 @@ const routes: RouteRecordRaw[] = [
{ {
path: 'test', path: 'test',
name: 'Test', name: 'Test',
component: () => import('pages/TestPage.vue') component: () => import('pages/TestPage.vue'),
}, },
{ {
path: 'device-info', path: 'device-info',

View File

@@ -12,6 +12,8 @@ import type {
FindMyUpdate, FindMyUpdate,
iCloudMonitorResponse, iCloudMonitorResponse,
SimulationStatus, SimulationStatus,
LocationMarkUpdateResponse,
LocationItemUpdate,
} from 'components/models'; } from 'components/models';
const $q = useQuasar(); const $q = useQuasar();
@@ -23,6 +25,7 @@ export const useSocketioStore = defineStore('socketio', {
sockConnected: false as boolean, sockConnected: false as boolean,
socketID: null as string | null | undefined, socketID: null as string | null | undefined,
testMode: null as boolean | undefined | null, testMode: null as boolean | undefined | null,
gpsNoise: null as boolean | undefined | null,
deviceConnected: false as boolean, deviceConnected: false as boolean,
tunnelConnected: false as boolean, tunnelConnected: false as boolean,
simulationRunning: false as boolean | string, simulationRunning: false as boolean | string,
@@ -152,7 +155,10 @@ export const useSocketioStore = defineStore('socketio', {
} }
}); });
}); });
socket.on('location_item_update', (inData: LocationItemUpdate): void => {
console.log('Location item update received, data: ', inData);
this.locationQueueData[inData.loc_id] = inData.data;
});
socket.onAny((eventName, ...args) => { socket.onAny((eventName, ...args) => {
console.log('Event received: ', eventName, ' Args: ', args, ''); console.log('Event received: ', eventName, ' Args: ', args, '');
/* if (serverToClientKnownEvents.includes(eventName)) { /* if (serverToClientKnownEvents.includes(eventName)) {
@@ -215,8 +221,8 @@ export const useSocketioStore = defineStore('socketio', {
'icloud_monitor_control', 'icloud_monitor_control',
{ command: 'start' }, { command: 'start' },
(response: iCloudMonitorResponse) => { (response: iCloudMonitorResponse) => {
fnctRtn.sts = response.status; fnctRtn.sts = response.command_status;
if (response.status == 'error') { if (response.command_status == 'ERROR') {
if (response.message) { if (response.message) {
if (debugLog) { if (debugLog) {
console.log(response.message); console.log(response.message);
@@ -227,7 +233,7 @@ export const useSocketioStore = defineStore('socketio', {
} }
throw new Error(fnctRtn.msg); throw new Error(fnctRtn.msg);
} else { } else {
fnctRtn = { sts: 'OK', msg: 'iCloud Monitor: ' + response.status }; fnctRtn = { sts: 'OK', msg: 'iCloud Monitor: ' + response.command_status };
this.icloudMonitor = true; this.icloudMonitor = true;
} }
}, },
@@ -248,8 +254,8 @@ export const useSocketioStore = defineStore('socketio', {
'icloud_monitor_control', 'icloud_monitor_control',
{ command: 'stop' }, { command: 'stop' },
(response: iCloudMonitorResponse) => { (response: iCloudMonitorResponse) => {
fnctRtn.sts = response.status; fnctRtn.sts = response.command_status;
if (response.status == 'error') { if (response.command_status == 'ERROR') {
if (response.message) { if (response.message) {
if (debugLog) { if (debugLog) {
console.log(response.message); console.log(response.message);
@@ -260,7 +266,7 @@ export const useSocketioStore = defineStore('socketio', {
} }
throw new Error(fnctRtn.msg); throw new Error(fnctRtn.msg);
} else { } else {
fnctRtn = { sts: 'OK', msg: 'iCloud Monitor: ' + response.status }; fnctRtn = { sts: 'OK', msg: 'iCloud Monitor: ' + response.command_status };
this.icloudMonitor = false; this.icloudMonitor = false;
} }
}, },
@@ -270,15 +276,43 @@ export const useSocketioStore = defineStore('socketio', {
if (debugLog) { if (debugLog) {
console.log('socketStore: got command: icloudMonitor status'); console.log('socketStore: got command: icloudMonitor status');
} }
socket.emit(
'icloud_monitor_control',
{ command: 'get' },
(response: iCloudMonitorResponse) => {
fnctRtn.sts = response.command_status;
if (response.command_status == 'error') {
if (response.message) {
if (debugLog) {
console.log(response.message);
}
fnctRtn.msg = response.message;
} else {
fnctRtn.msg = 'Error';
}
throw new Error(fnctRtn.msg);
} else {
fnctRtn = {
sts: 'OK',
msg: 'iCloud Location Requested',
};
}
this.icloudMonitor = !!(
response.icloud_monitor_enabled && response.icloud_monitor_running
);
},
);
break;
case 'refresh':
if (debugLog) { if (debugLog) {
console.log('Emitting icloud_monitor_control: status'); console.log('socketStore: got command: icloudMonitor refresh');
} }
socket.emit( socket.emit(
'icloud_monitor_control', 'icloud_monitor_control',
{ command: 'status' }, { command: 'status' },
(response: iCloudMonitorResponse) => { (response: iCloudMonitorResponse) => {
fnctRtn.sts = response.status; fnctRtn.sts = response.command_status;
if (response.status == 'error') { if (response.command_status == 'ERROR') {
if (response.message) { if (response.message) {
if (debugLog) { if (debugLog) {
console.log(response.message); console.log(response.message);
@@ -312,9 +346,11 @@ export const useSocketioStore = defineStore('socketio', {
}, },
simulationControl( simulationControl(
command: string, command: string,
delay?: number,
latitude?: number | null, latitude?: number | null,
longitude?: number | null, longitude?: number | null,
loc_id?: string | null,
delay?: number,
address?: string | null,
) { ) {
let fnctRtn: { sts: string; msg?: string | undefined } = { sts: '', msg: '' }; let fnctRtn: { sts: string; msg?: string | undefined } = { sts: '', msg: '' };
this.setSockStatus(); this.setSockStatus();
@@ -338,14 +374,14 @@ export const useSocketioStore = defineStore('socketio', {
'simulation_control', 'simulation_control',
{ command: 'start', delay: 0, latitude: null, longitude: null }, { command: 'start', delay: 0, latitude: null, longitude: null },
(response: SimulationControlResponse) => { (response: SimulationControlResponse) => {
if (response.status == 'error') { if (response.command_status == 'ERROR') {
fnctRtn = { sts: 'error', msg: response.message?.toString() }; fnctRtn = { sts: 'error', msg: response.message?.toString() };
throw new Error(response.message); throw new Error(response.message);
} else { } else {
fnctRtn = { sts: 'OK', msg: response.message?.toString() }; fnctRtn = { sts: 'OK', msg: response.message?.toString() };
this.simulationState = response.status; this.simulationState = response.data?.simulation_queue_state;
if (debugLog) { if (debugLog) {
console.log(response.message); console.log('response from simulate_control_start: ', response);
} }
// return response.message; // return response.message;
} }
@@ -357,19 +393,35 @@ export const useSocketioStore = defineStore('socketio', {
'simulation_control', 'simulation_control',
{ command: 'test-mode' }, { command: 'test-mode' },
(response: SimulationControlResponse) => { (response: SimulationControlResponse) => {
if (response.status === 'error') { if (response.command_status === 'ERROR') {
throw new Error(response.message); throw new Error(response.message);
} else { } else {
this.simulationState = response.status; this.simulationState = response.data?.simulation_queue_state
if (debugLog) { if (debugLog) {
console.log(response.message, response); console.log('response from simulate_control_test-mode: ', response);
}
return response.message;
}
},
);
break;
case 'gps-noise':
socket.emit(
'simulation_control',
{ command: 'gps-noise' },
(response: SimulationControlResponse) => {
if (response.command_status === 'ERROR') {
throw new Error(response.message);
} else {
this.gpsNoise = response.simulation_noise;
if (debugLog) {
console.log('response from simulate_control_gps-noise: ', response);
} }
return response.message; return response.message;
} }
}, },
); );
break; break;
case 'pause': case 'pause':
if (this.simulationState !== 'RUNNING') { if (this.simulationState !== 'RUNNING') {
throw new Error('Simulation is not running'); throw new Error('Simulation is not running');
@@ -378,12 +430,12 @@ export const useSocketioStore = defineStore('socketio', {
'simulation_control', 'simulation_control',
{ command: 'pause' }, { command: 'pause' },
(response: SimulationControlResponse) => { (response: SimulationControlResponse) => {
if (response.status === 'error') { if (response.command_status === 'ERROR') {
throw new Error(response.message); throw new Error(response.message);
} else { } else {
this.simulationState = response.status; this.simulationState = response.data?.simulation_queue_state;
if (debugLog) { if (debugLog) {
console.log(response.message); console.log('response from simulate_control_pause: ', response);
} }
return response.message; return response.message;
} }
@@ -395,12 +447,12 @@ export const useSocketioStore = defineStore('socketio', {
throw new Error('Simulation is not paused'); throw new Error('Simulation is not paused');
} }
socket.emit('simulation_control', { command: 'resume' }, (response) => { socket.emit('simulation_control', { command: 'resume' }, (response) => {
if (response.status == 'error') { if (response.command_status == 'ERROR') {
throw new Error(response.message); throw new Error(response.message);
} else { } else {
this.simulationState = response.status; this.simulationState = response.data?.simulation_queue_state;
if (debugLog) { if (debugLog) {
console.log(response.message); console.log('response from simulate_control_resume: ', response);
} }
return response.message; return response.message;
} }
@@ -411,12 +463,12 @@ export const useSocketioStore = defineStore('socketio', {
throw new Error('Simulation queue is empty'); throw new Error('Simulation queue is empty');
} }
socket.emit('simulation_control', { command: 'clear' }, (response) => { socket.emit('simulation_control', { command: 'clear' }, (response) => {
if (response.status == 'error') { if (response.command_status == 'ERROR') {
throw new Error(response.message); throw new Error(response.message);
} else { } else {
this.simulationState = response.status; this.simulationState = response.data?.simulation_queue_state;
if (debugLog) { if (debugLog) {
console.log(response.message); console.log('response from simulate_control_clear: ', response);
} }
return response.message; return response.message;
} }
@@ -424,35 +476,35 @@ export const useSocketioStore = defineStore('socketio', {
break; break;
case 'end': case 'end':
if (this.simulationState == 'ENDED' || !this.simulationRunning) { if (this.simulationState == 'ENDED' || !this.simulationRunning) {
throw new Error('Simulation is already ended'); throw new Error('Simulation has already ended');
} }
socket.emit('simulation_control', { command: 'end' }, (response) => { socket.emit('simulation_control', { command: 'end' }, (response) => {
if (response.status == 'error') { if (response.command_status == 'ERROR') {
throw new Error(response.message); throw new Error(response.message);
} else { } else {
this.simulationState = response.status; this.simulationState = response.data?.simulation_queue_state;
if (debugLog) { if (debugLog) {
console.log(response.message); console.log('response from simulate_control_end: ', response);
} }
return response.message; return response.message;
} }
}); });
break; break;
case 'add': case 'add':
// if (this.simulationState == 'ENDED' || !this.simulationRunning) {
// throw new Error('Simulation is not running');
// }
if (!latitude || !longitude) { if (!latitude || !longitude) {
throw new Error('latitude or longitude not set'); throw new Error('latitude or longitude not set');
} }
if (!address) {
address = "{latitude}, {longitude}"
}
socket.emit( socket.emit(
'simulation_control', 'simulation_control',
{ command: 'add', latitude: latitude, longitude: longitude, delay: delay }, { command: 'add', latitude: latitude, longitude: longitude, delay: delay, address: address },
(response) => { (response) => {
if (response.status == 'error') { if (response.command_status == 'ERROR') {
throw new Error(response.message); throw new Error(response.message);
} else { } else {
this.simulationState = response.status; this.simulationState = response.data?.simulation_queue_state;
if (debugLog) { if (debugLog) {
console.log('response from simulate_control_add: ', response); console.log('response from simulate_control_add: ', response);
} }
@@ -461,6 +513,35 @@ export const useSocketioStore = defineStore('socketio', {
}, },
); );
break; break;
case 'delete':
if (!loc_id) {
throw new Error('loc_id not set.');
}
socket.emit('simulation_control', { command: 'delete', loc_id: loc_id }, (response) => {
if (response.command_status == 'ERROR') {
throw new Error(response.message);
} else {
this.simulationState = response.data?.simulation_queue_state;
if (debugLog) {
console.log('response from simulate_control_delete: ', response);
}
return response.message;
}
});
break;
case 'next':
socket.emit('simulation_control', { command: 'next' }, (response) => {
if (response.command_status == 'ERROR') {
throw new Error(response.message);
} else {
this.simulationState = response.data?.simulation_queue_state;
if (debugLog) {
console.log('response from simulate_control_next: ', response);
}
return response.message;
}
});
break;
default: default:
fnctRtn = { sts: 'error', msg: 'Invalid command' }; fnctRtn = { sts: 'error', msg: 'Invalid command' };
throw new Error('Invalid command'); throw new Error('Invalid command');
@@ -475,6 +556,7 @@ export const useSocketioStore = defineStore('socketio', {
digestUpdate(data: StatusUpdate): void { digestUpdate(data: StatusUpdate): void {
this.deviceConnected = !!(data.udid && data.tunnel); this.deviceConnected = !!(data.udid && data.tunnel);
this.testMode = data.test_mode; this.testMode = data.test_mode;
this.gpsNoise = data.simulation_queue.gps_noise;
this.simulationRunning = data.simulation_queue.active; this.simulationRunning = data.simulation_queue.active;
this.simulationState = data.simulation_queue.state; this.simulationState = data.simulation_queue.state;
this.simulationQueueLength = data.simulation_queue.order.length; this.simulationQueueLength = data.simulation_queue.order.length;
@@ -493,6 +575,38 @@ export const useSocketioStore = defineStore('socketio', {
setDeviceState(state: boolean) { setDeviceState(state: boolean) {
this.deviceConnected = state; this.deviceConnected = state;
}, },
updateLocationMark(loc_id: string, key: string, value: string | number) {
let fnctRtn: { command_status: string, message: string } = { command_status: '', message: '' };
if (debugLog) {
console.log('socketStore: Update LocationMark request, loc_id: %s, key: %s, new value: %s', loc_id, key, value);
}
if (
!this.locationQueueData[loc_id]
) {
fnctRtn = { command_status: 'error', message: 'Location Id: ' + loc_id + ' is not in the simulation queue' };
throw new Error('loc_id ' + loc_id + 'not found');
}
if (debugLog) {
console.log('Emitting LocationItem Update');
}
socket.emit(
'location_item_update',
{ loc_id: loc_id, key: key, value: value },
(response: LocationMarkUpdateResponse) => {
if (response.command_status == 'ERROR') {
fnctRtn = { command_status: 'error', message: response.message?.toString() };
throw new Error(response.message);
} else {
fnctRtn = { command_status: 'OK', message: response.message?.toString() };
if (debugLog) {
console.log('response from backend: ', response);
}
this.locationQueueData[loc_id] = response.data;
}
},
);
return fnctRtn;
},
}, },
}); });

View File

@@ -1,7 +1,14 @@
declare module 'leaflet-routing-machine' { declare module 'leaflet-routing-machine' {
import type * as L from 'leaflet'; import type * as L from 'leaflet';
export type IRouter = Record<string, unknown>; export interface IRouter {
route(
waypoints: Array<{ latLng: L.LatLng }>,
callback: (error: unknown, routes?: unknown[]) => void,
context?: unknown,
options?: unknown,
): unknown;
}
export type IGeocoder = Record<string, unknown>; export type IGeocoder = Record<string, unknown>;
export type LineOptions = L.PolylineOptions; export type LineOptions = L.PolylineOptions;