extensive changes

This commit is contained in:
2026-03-27 17:11:13 -04:00
parent 3f3a5136eb
commit 05e63a28f1
14 changed files with 1431 additions and 508 deletions

View File

@@ -106,12 +106,12 @@ export default defineConfig((/* ctx */) => {
proxy: {
// proxy all requests starting with /api to jsonplaceholder
'/api': {
target: 'http://localhost:8000',
target: 'http://localhost:49151',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
'/socket.io': {
target: 'http://localhost:8000', // Your backend WebSocket server
target: 'http://localhost:49151', // Your backend WebSocket server
secure: false, // Set to true if using wss://
ws: true, // Enable WebSocket proxying
rewriteWsOrigin: true,
@@ -141,7 +141,7 @@ export default defineConfig((/* ctx */) => {
},
// animations: 'all', // --- includes all animations
// https://v2.quasar.dev/options/animations
animations: [],
animations: ['slideInLeft', 'slideOutLeft', 'slideOutRight'],
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#sourcefiles
// sourceFiles: {

View File

@@ -1,84 +1,73 @@
<template>
<div style="display: none"></div>
</template>
<script setup lang="ts">
import { ref, watch, onMounted, onBeforeUnmount } from 'vue';
import 'leaflet-routing-machine/dist/leaflet-routing-machine.css';
import L from 'leaflet';
import {
ref,
markRaw,
inject,
nextTick,
onMounted,
onBeforeUnmount,
useAttrs,
defineEmits,
} from 'vue';
import { routingControlProps, setupRoutingControl } from 'functions/routingControl';
import { Utilities, InjectionKeys } from '@vue-leaflet/vue-leaflet';
import 'leaflet';
import 'leaflet-routing-machine';
import 'leaflet-routing-machine/dist/leaflet-routing-machine.css';
import type { IRouter, IGeocoder, LineOptions } from 'leaflet-routing-machine';
// Props
const props = defineProps<{
mapObject?: any;
visible?: boolean;
waypoints: any[];
router?: IRouter;
plan?: L.Routing.Plan;
geocoder?: IGeocoder;
fitSelectedRoutes?: string | boolean;
lineOptions?: LineOptions;
routeLine?: Function;
autoRoute?: boolean;
routeWhileDragging?: boolean;
routeDragInterval?: number;
waypointMode?: string;
useZoomParameter?: boolean;
showAlternatives?: boolean;
altLineOptions?: LineOptions;
import type L from 'leaflet';
// ---- Emits ----
const emit = defineEmits<{
(e: 'ready', value: L.Routing.Control): void;
}>();
// Defaults
const visible = props.visible ?? true;
const fitSelectedRoutes = props.fitSelectedRoutes ?? 'smart';
const autoRoute = props.autoRoute ?? true;
const routeWhileDragging = props.routeWhileDragging ?? false;
const routeDragInterval = props.routeDragInterval ?? 500;
const waypointMode = props.waypointMode ?? 'connect';
const useZoomParameter = props.useZoomParameter ?? false;
const showAlternatives = props.showAlternatives ?? false;
// ---- Props ----
const props = defineProps(routingControlProps);
// State
const ready = ref(false);
const layer = ref<any>(null);
// ---- Attrs (for events) ----
const attrs = useAttrs();
// Methods
function add() {
if (!props.mapObject) return;
// ---- Injections ----
const { UseGlobalLeafletInjection, RegisterControlInjection } = InjectionKeys;
const { WINDOW_OR_GLOBAL, assertInject, propsBinder, remapEvents } = Utilities;
const options = {
waypoints: props.waypoints,
fitSelectedRoutes,
autoRoute,
routeWhileDragging,
routeDragInterval,
waypointMode,
useZoomParameter,
showAlternatives,
};
console.log(L.Routing);
const routingLayer = L.Routing.control(options);
routingLayer.addTo(props.mapObject);
layer.value = routingLayer;
const useGlobalLeaflet = inject(UseGlobalLeafletInjection, false);
const registerControl = assertInject(RegisterControlInjection);
ready.value = true;
}
// ---- State ----
const leafletObject = ref<L.Routing.Control | null>(null);
// Watchers
watch(
() => props.mapObject,
(val) => {
if (!val) return;
add();
},
);
// ---- Setup logic ----
const { options, methods } = setupRoutingControl(props);
// Lifecycle
onMounted(() => {
add();
onMounted(async () => {
const { routing } = useGlobalLeaflet
? (WINDOW_OR_GLOBAL as any).L
: await import('leaflet/dist/leaflet-src.esm');
const { listeners } = remapEvents(attrs);
leafletObject.value = markRaw(routing.control(options));
leafletObject.value.on(listeners);
propsBinder(methods, leafletObject.value, props);
registerControl({ leafletObject: leafletObject.value });
await nextTick();
emit('ready', leafletObject.value);
});
onBeforeUnmount(() => {
if (layer.value) {
layer.value.remove();
if (leafletObject.value) {
leafletObject.value.setWaypoints([]);
leafletObject.value.remove();
}
});
</script>

View File

@@ -1,64 +1,168 @@
<template>
<div style="height: 600px; width: 800px; color: #000000">
<div class="q-pa-md">
<q-layout
view="hHh Lpr fFf"
container
style="height: 600px; width: 100vw"
class="rounded-borders"
>
<q-footer :class="$q.dark.isActive ? 'bg-primary' : 'bg-black'" 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 label="Routing" @click="routeLayer = !routeLayer" size="sm" stretch />
</q-toolbar>
</q-footer>
<q-drawer
v-model="qLocDrawer"
show-if-above
mini-to-overlay
overlay
:width="300"
side="left"
:breakpoint="500"
:mini="miniState"
@mouseenter="miniState = false"
@mouseleave="miniState = true"
>
<q-scroll-area class="fit" :horizontal-thumb-style="{ opacity: 0 }">
<q-list padding>
<q-item-label header>Location Queue</q-item-label>
<q-separator />
<LocationMark
v-for="key in locationQueueOrder"
:key="key"
:loc_id="key"
:active="locationQueueData[key].loc_id === currentLocation.loc_id"
:start="locationQueueData[key].start"
:address="locationQueueData[key].address"
:latitude="locationQueueData[key].latitude"
:longitude="locationQueueData[key].longitude"
:end="locationQueueData[key].end"
@item-clicked="zoomToCoods"
/>
</q-list>
</q-scroll-area>
</q-drawer>
<q-page-container>
<q-page>
<div style="height: 560px; width: 90vw; color: #000000">
<L-Map
@ready="onMapReady"
ref="mapRef"
:zoom="zoom"
:center="center"
style="height: 500px; width: 100%"
:zoom="zoom"
style="height: 550px; width: 100%"
@click="updateMarker"
@ready="onMapReady"
>
<L-Tile-Layer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
layer-type="base"
name="OpenStreetMap"
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
@click="updateMarker($event.latlng)"
></L-Tile-Layer>
<L-Marker :lat-lng="markerLatLng" @click="handleMarkerClick"></L-Marker>
<L-Routing-Machine
<L-Layer-Group>
<L-Marker v-if="markerLatLng" :lat-lng="markerLatLng" @click="handleMarkerClick">
</L-Marker>
</L-Layer-Group>
<L-Layer-Group v-if="locationQueueOrder">
<L-Marker
v-for="locid in locationQueueOrder"
:key="locid"
:icon="getCustomIcon(locid)"
:lat-lng="[locationQueueData[locid].latitude, locationQueueData[locid].longitude]"
>
</L-Marker>
</L-Layer-Group>
<L-Layer-Group v-if="routeLayer">
<LRoutingMachine
v-bind="routingOptions"
@routingstart="debugRoutingEvent"
@routesfound="debugRoutingEvent"
@routingerror="debugRoutingEvent"
:waypoints="waypoints"
/>
</L-Layer-Group>
<L-Layer-Group v-if="findMyUpdate">
<L-Marker
v-if="findMyUpdate"
:icon="fmIcon"
:lat-lng="[findMyUpdate.latitude, findMyUpdate.longitude]"
></L-Marker>
<L-Circle
:fillOpacity="0.5"
:lat-lng="[findMyUpdate.latitude, findMyUpdate.longitude]"
:radius="findMyUpdate.horizontalAccuracy"
color="firebrick"
fillColor="indianred"
></L-Circle>
</L-Layer-Group>
</L-Map>
</div>
</q-page>
</q-page-container>
</q-layout>
</div>
</template>
<script setup lang="ts">
<script lang="ts" setup>
import { useQuasar } from 'quasar';
import { ref } from 'vue';
import { computed, onMounted, reactive, ref } from 'vue';
import { GeoSearchControl, OpenStreetMapProvider } from 'leaflet-geosearch';
import LRoutingMachine from 'components/LRoutingMachine.vue';
import 'leaflet-routing-machine/dist/leaflet-routing-machine.css';
import { Icon, PinCirclePanel, PinStarPanel } from 'leaflet-extra-markers';
import 'leaflet-geosearch/dist/geosearch.css';
import 'leaflet/dist/leaflet.css';
import LRoutingMachine from 'components/LRoutingMachine.vue';
import LocationMark from 'components/LocationMark.vue';
import type { coords, SearchControlProps } from 'src/types';
import type { Map, LeafletMouseEvent } from 'leaflet';
import type { LeafletMouseEvent, Map } from 'leaflet';
import { storeToRefs } from 'pinia';
import { useSocketioStore } from 'stores/socketio';
import { useLeafletStore } from 'stores/leaflet';
import SetLocationDialog from 'components/SetLocationDialog.vue';
import { LMap, LMarker, LTileLayer } from '@vue-leaflet/vue-leaflet';
const waypoints = [
[ 38.7436056, -9.2304153 ],
[ 38.7436056, -0.131281 ],
];
import { LCircle, LLayerGroup, LMap, LMarker, LTileLayer } from '@vue-leaflet/vue-leaflet';
import { favorites } from 'constants/favorites';
const leafletStore = useLeafletStore();
const socketStore = useSocketioStore();
const { zoom, center, markerLatLng } = storeToRefs(socketStore);
const $q = useQuasar();
const mapRef = ref(null);
const responseMessage = ref('');
const { zoom, center, markerLatLng, qLocDrawer } = storeToRefs(leafletStore);
const debugRoutingEvent = (event) => {
console.log(`${event.type} event: `, event);
};
const socketStore = useSocketioStore();
const { currentLocation, nextLocation, locationQueueData, locationQueueOrder, findMyUpdate } =
storeToRefs(socketStore);
const $q = useQuasar();
const mapRef = ref();
const responseMessage = ref('');
const routeStart = ref(null);
const routeEnd = ref(null);
const routeLayer = ref(false);
const miniState = ref(true);
const onMapReady = (map: Map) => {
const provider = new OpenStreetMapProvider();
@@ -81,6 +185,48 @@ const onMapReady = (map: Map) => {
map.addControl(searchControl);
};
const fmIcon = new Icon({
color: 'indianred',
accentColor: 'firebrick',
content: 'FM',
contentColor: 'white',
scale: 1,
svg: PinCirclePanel,
});
const getCustomIcon = (locid: string) => {
const locationIndex = locationQueueOrder.value.indexOf(locid);
if (currentLocation.value && currentLocation.value.loc_id === locid) {
return new Icon({
color: 'pink',
accentColor: 'black',
content: '*',
contentColor: 'black',
scale: 1.5,
svg: PinStarPanel,
});
}
return new Icon({
color: 'blue',
accentColor: 'firebrick',
content: locationIndex.toString(),
contentColor: 'white',
scale: 1,
svg: PinCirclePanel,
});
};
const routingOptions = reactive({
waypoints: [
[40.910773020811, -73.891069806448],
[40.90930366920829, -73.87658695470259],
],
});
const debugRoutingEvent = (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];
@@ -114,10 +260,17 @@ function handleMarkerClick(event: LeafletMouseEvent) {
}
function setLocation(coords: coords, delay: number) {
let notType: string = 'positive';
try {
responseMessage.value = socketStore.simulationControl('add', delay, coords.lat, coords.lng);
$q.notify({ type: 'positive', message: responseMessage.value });
const setCmdRsp = socketStore.simulationControl('add', delay, coords.lat, coords.lng);
if (setCmdRsp.sts === 'error') {
notType = 'negative';
}
if (setCmdRsp.msg) {
responseMessage.value = setCmdRsp.msg;
}
} catch (error: unknown) {
notType = 'negative';
if (error instanceof Error) {
console.error('Error setting location:', error.message);
responseMessage.value = `Failed to set location: ${error.message}`;
@@ -125,14 +278,65 @@ function setLocation(coords: coords, delay: number) {
console.error('Error setting location:', error);
responseMessage.value = `Failed to set location: Unknown error`;
}
$q.notify({ type: 'negative', message: responseMessage.value });
} finally {
$q.notify({ type: notType, message: responseMessage.value });
}
}
function zoomToCoods(arg: string) {
leafletStore.setCenter(
locationQueueData.value[arg].latitude,
locationQueueData.value[arg].longitude,
);
leafletStore.setZoom(50);
qLocDrawer.value = false;
}
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);
}
$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);
}
$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);
}
$q.notify({ type: 'negative', message: 'Next Location not available' });
break;
default:
$q.notify({ type: 'negative', message: 'Invalid location' });
break;
}
}
onMounted(() => {
if (findMyUpdate.value && findMyUpdate.value.latitude && findMyUpdate.value.longitude) {
leafletStore.setCenter(findMyUpdate.value.latitude, findMyUpdate.value.longitude);
} else {
leafletStore.setCenter(favorites.home.coords.lat, favorites.home.coords.lng);
}
});
</script>
<style>
.l-map {
height: 100vh;
width: 100vw;
}
<style lang="sass">
.l-map
height: 100vh
width: 100vw
.q-item.q-router-link--active, .q-item--active
background-color: $accent
color: $primary
</style>

View File

@@ -0,0 +1,226 @@
<script setup lang="ts">
import { storeToRefs } from 'pinia';
import { useSocketioStore } from 'stores/socketio';
import { computed, onMounted, onUnmounted, ref } from 'vue';
const socketStore = useSocketioStore();
const { currentLocation, locationQueueOrder } = storeToRefs(socketStore);
const props = defineProps({
address: {
type: String,
required: true,
},
latitude: {
type: Number,
required: true,
},
longitude: {
type: Number,
required: true,
},
icon: {
type: String,
default: 'location_on',
},
start: {
type: String,
required: true,
},
end: {
type: String,
required: false,
},
delay: {
type: Number,
default: 0,
},
active: {
type: Boolean,
default: false,
},
loc_id: {
type: String,
required: true,
},
});
// Define custom events that this component can emit
const emit = defineEmits(['item-clicked']);
function itemClicked() {
const param1 = props.loc_id;
emit('item-clicked', param1);
}
const currentTime = ref(new Date());
let timerId: number;
const calculateDeltaT = computed(() => {
let delta = '';
if (props.active) {
delta = 'now';
} else if (props.end && props.end !== '') {
const pastTime: Date = new Date(props.end);
const nowMs: number = currentTime.value.getTime();
const pastMs: number = pastTime.getTime();
const diffMs: number = Math.abs(nowMs - pastMs);
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 = `${days} day${days > 1 ? 's' : ''} ago`;
} else if (hours > 0) {
delta = `${hours} hour${hours > 1 ? 's' : ''} ago`;
} else if (minutes > 0) {
delta = `${minutes} minute${minutes > 1 ? 's' : ''} ago`;
} else {
delta = `${seconds} second${seconds > 1 ? 's' : ''} ago`;
}
} else {
const futureTime: Date = new Date(props.start);
const nowMs: number = currentTime.value.getTime();
const futureMs: number = futureTime.getTime();
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 {
delta = `in ${seconds} second${seconds > 1 ? 's' : ''}`;
}
}
return delta;
});
const humanReadableDateTime = (iso: string) => {
return new Date(iso).toLocaleDateString('en-US', {
// year: 'numeric',
// month: 'long',
// day: 'numeric',
hour: '2-digit',
minute: '2-digit',
});
};
const stateAbbrevMap: Record<string, string> = {
Alabama: 'AL',
Alaska: 'AK',
Arizona: 'AZ',
Arkansas: 'AR',
California: 'CA',
Colorado: 'CO',
Connecticut: 'CT',
Delaware: 'DE',
Florida: 'FL',
Georgia: 'GA',
Hawaii: 'HI',
Idaho: 'ID',
Illinois: 'IL',
Indiana: 'IN',
Iowa: 'IA',
Kansas: 'KS',
Kentucky: 'KY',
Louisiana: 'LA',
Maine: 'ME',
Maryland: 'MD',
Massachusetts: 'MA',
Michigan: 'MI',
Minnesota: 'MN',
Mississippi: 'MS',
Missouri: 'MO',
Montana: 'MT',
Nebraska: 'NE',
Nevada: 'NV',
'New Hampshire': 'NH',
'New Jersey': 'NJ',
'New Mexico': 'NM',
'New York': 'NY',
'North Carolina': 'NC',
'North Dakota': 'ND',
Ohio: 'OH',
Oklahoma: 'OK',
Oregon: 'OR',
Pennsylvania: 'PA',
'Rhode Island': 'RI',
'South Carolina': 'SC',
'South Dakota': 'SD',
Tennessee: 'TN',
Texas: 'TX',
Utah: 'UT',
Vermont: 'VT',
Virginia: 'VA',
Washington: 'WA',
'West Virginia': 'WV',
Wisconsin: 'WI',
Wyoming: 'WY',
};
function formatAddress(input: string): string {
const parts = input.split(',').map((p) => p.trim());
if (parts.length < 5) return input; // fallback safety
const streetNumber = parts[0];
const streetName = parts[1];
const zip = parts.at(-2) ?? '';
const stateFull = parts.at(-3) ?? '';
const cityRaw = parts.at(-5) ?? '';
const city = cityRaw.replace(/^City of\s+/i, '');
const state = stateAbbrevMap[stateFull] ?? stateFull;
return `${streetNumber} ${streetName}, ${city}, ${state} ${zip}`;
}
onMounted(() => {
const update = () => {
currentTime.value = new Date();
timerId = requestAnimationFrame(update);
};
timerId = requestAnimationFrame(update);
});
onUnmounted(() => {
cancelAnimationFrame(timerId);
});
</script>
<template>
<q-item v-ripple clickable :active="active" @click="itemClicked">
<q-item-section>
<q-item-label
>{{ formatAddress(address) }}
<q-tooltip> {{ latitude }}, {{ longitude }} </q-tooltip>
</q-item-label>
<q-item-label caption lines="1" v-if="start">
start: {{ humanReadableDateTime(start) }}
</q-item-label>
<q-item-label caption lines="1" v-if="end">
end: {{ humanReadableDateTime(end) }}
</q-item-label>
</q-item-section>
<q-item-section side top>
<q-item-label caption lines="1">
{{ calculateDeltaT }}
</q-item-label>
<q-item-section avatar class="q-pt-md">
<q-btn dense color="primary" round icon="location_on" class="q-ml-md">
<q-badge color="accent" floating>{{
active ? '*' : locationQueueOrder.indexOf(loc_id)
}}</q-badge>
</q-btn>
</q-item-section>
</q-item-section>
</q-item>
</template>
<style scoped></style>

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import { useLeafletStore } from 'stores/leaflet';
import type { Control, coords, CtrlAttr, CtrlAttrs } from 'components/models';
import type { coords, CtrlAttrs } from 'components/models';
import { storeToRefs } from 'pinia';
import { socket } from 'boot/socketio';
import ConfirmCommandDialog from 'components/ConfirmCommandDiaglog.vue';
@@ -18,18 +18,16 @@ const leafletStore = useLeafletStore();
const socketStore = useSocketioStore();
const { center, markerLatLng } = storeToRefs(leafletStore);
const { simulationRunning, simulationState, simulationQueueLength } = storeToRefs(socketStore);
const { simulationRunning, simulationState, simulationQueueLength, icloudMonitor, testMode } = storeToRefs(socketStore);
const menuOpen = ref();
function handleFavClick(coords: coords) {
center.value = [coords.lat, coords.lng];
markerLatLng.value = [coords.lat, coords.lng];
}
function handleControlClick(cmdAttr: CtrlAttr) {
function handleControlClick(cmdAttr) {
if (cmdAttr.cnfrm) {
$q.dialog({
component: ConfirmCommandDialog,
@@ -38,25 +36,33 @@ function handleControlClick(cmdAttr: CtrlAttr) {
},
})
.onOk(() => {
if (cmdAttr.cmdClass === 'simulation_control') {
if (cmdAttr.cmdClass === 'sim_cntrl_class') {
let notType: string = 'positive';
let notMsg: string = '';
try {
const ack = socketStore.simulationControl(cmdAttr.cmd, cmdAttr.delay);
$q.notify({ type: 'positive', message: ack });
const ack = socketStore.simulationControl(cmdAttr['cmd'], cmdAttr.delay);
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);
const ack = `Simulation Command Error: ${error.message}`;
$q.notify({ type: 'negative', message: ack });
notMsg = `Simulation Command Error: ${error.message}`;
} else {
console.error('Simmulation Command Error: ', error);
const ack = 'Simulation Command Error: Unknow error';
$q.notify({ type: 'negative', message: ack });
console.error('Simulation Command Error: ', error);
notMsg = 'Simulation Command Error: Unknow error';
}
} finally {
$q.notify({ type: notType, message: notMsg });
}
}
if (ctrl.cmdClass === 'device_control') {
socket.emit('device_control', { command: ctrl.cmd, delay: 0 }, (response) => {
if (cmdAttr.cmdClass === 'dev_cntrl_class') {
socket.emit('device_control', { command: cmdAttr.cmd, delay: 0 }, (response) => {
console.log(response.status, response.command);
});
}
@@ -68,22 +74,57 @@ function handleControlClick(cmdAttr: CtrlAttr) {
console.log('Dialog dismissed');
});
} else {
if (ctrl.cmdClass === 'simulation_control') {
if (cmdAttr.cmdClass === 'sim_cntrl_class') {
let notType: string = 'positive';
let notMsg: string = '';
try {
const response = socketStore.simulationControl(ctrl.cmd);
$q.notify({ type: 'positive', message: response });
const ack = socketStore.simulationControl(cmdAttr.cmd, cmdAttr.delay);
if (ack.sts === 'error') {
notType = 'negative';
}
if (ack.msg) {
notMsg = ack.msg;
}
} catch (error: unknown) {
notType = 'negative';
if (error instanceof Error) {
console.error('Error: ' + error.message);
$q.notify({ type: 'negative', message: error.toString() });
console.error('Simulation Command ERROR: ', error.message);
notMsg = `Simulation Command Error: ${error.message}`;
} else {
console.error('Error setting location:', error);
$q.notify({ type: 'negative', message: error.toString() });
console.error('Simulation Command Error: ', error);
notMsg = 'Simulation Command Error: Unknow error';
}
} finally {
$q.notify({ type: notType, message: notMsg });
}
}
if (cmdAttr.cmdClass === 'icloud-monitor_cntrl_class') {
let notType: string = 'positive';
let notMsg: string = '';
try {
const ack = socketStore.icloudMonitorControl(cmdAttr.cmd);
if (ack.sts === 'error') {
notType = 'negative';
}
if (ctrl.cmdClass === 'device_control') {
socket.emit('device_control', { command: ctrl.cmd, delay: 0 }, (response) => {
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 });
}
}
if (cmdAttr.cmdClass === 'dev_cntrl_class') {
socket.emit('device_control', { command: cmdAttr.cmd, delay: 0 }, (response) => {
console.log(response.status, response.command);
});
}
@@ -92,53 +133,59 @@ function handleControlClick(cmdAttr: CtrlAttr) {
</script>
<template>
<q-toolbar class="bg-primary text-white">
<q-btn @click="$emit('drawer')" flat round dense icon="menu" class="q-mr-sm" />
<q-toolbar :class="testMode ? 'bg-warning text-black' : 'bg-primary text-white'">
<q-btn @click="$emit('drawer'); leafletStore.toggleQLocDrawer()" flat round dense icon="menu" class="q-mr-sm" />
<q-separator dark inset />
<q-space />
<q-btn :icon-right="menuOpen ? 'arrow_drop_up' : 'arrow_drop_down'" stretch flat label="Favorites" v-if="route.name === 'Leaflet'">
<q-btn
:icon-right="menuOpen ? 'arrow_drop_up' : 'arrow_drop_down'"
stretch
flat
label="Favorites"
v-if="route.name === 'Leaflet'"
>
<q-menu @show="menuOpen = true" @hide="menuOpen = false" anchor="bottom end" self="top end">
<q-list>
<template v-for="fav, index) in favorites" :key="index">
<template v-for="(favObj, favId) in favorites" :key="favId">
<q-item
v-if="fav.coords"
v-if="favObj.coords"
clickable
v-ripple
v-close-popup
@click="handleFavClick(fav.coords)"
@click="handleFavClick(favObj.coords)"
>
<q-item-section avatar>
<q-avatar :icon="fav.icon" color="primary" text-color="white" />
<q-avatar :icon="favObj.icon" color="primary" text-color="white" />
</q-item-section>
<q-item-section>
<q-item-label>{{ fav.name }}</q-item-label>
<q-item-label>{{ favObj.name }}</q-item-label>
</q-item-section>
</q-item>
<q-item v-else clickable v-ripple>
<q-item-section avatar>
<q-avatar :icon="fav.icon" color="primary" text-color="white" />
<q-avatar :icon="favObj.icon" color="primary" text-color="white" />
</q-item-section>
<q-item-section>
<q-item-label>{{ fav.name }}</q-item-label>
<q-item-label>{{ favObj.name }}</q-item-label>
</q-item-section>
<q-item-section side>
<q-icon name="keyboard_arrow_right" />
</q-item-section>
<q-menu anchor="top end" self="top start">
<q-menu anchor="bottom start" self="bottom end">
<q-list>
<q-item
v-for="(f, indx) in fav.subitems"
:key="indx"
v-for="(favSubObj, favSubId) in favObj.subitems"
:key="favSubId"
clickable
v-ripple
v-close-popup
@click="handleFavClick(f.coords)"
@click="handleFavClick(favSubObj.coords)"
>
<q-item-section avatar>
<q-avatar :icon="f.icon" color="primary" text-color="white" />
<q-avatar :icon="favSubObj.icon" color="primary" text-color="white" />
</q-item-section>
<q-item-section>
<q-item-label>{{ f.name }}</q-item-label>
<q-item-label>{{ favSubObj.name }}</q-item-label>
</q-item-section>
</q-item>
</q-list>
@@ -156,12 +203,13 @@ function handleControlClick(cmdAttr: CtrlAttr) {
clickable
v-ripple
v-close-popup
@click="handleControlClick(controls.sim_start)">
@click="handleControlClick(controls.simulation.start)"
>
<q-item-section avatar>
<q-avatar :icon="controls.sim_start.icon" color="primary" text-color="white" />
<q-avatar :icon="controls.simulation.start.icon" color="primary" text-color="white" />
</q-item-section>
<q-item-section>
<q-item-label> {{ controls.sim_start.name }} </q-item-label>
<q-item-label> {{ controls.simulation.start.name }} </q-item-label>
</q-item-section>
</q-item>
<q-item
@@ -169,13 +217,13 @@ function handleControlClick(cmdAttr: CtrlAttr) {
clickable
v-ripple
v-close-popup
@click="handleControlClick(controls.sim_pause)"
@click="handleControlClick(controls.simulation.pause)"
>
<q-item-section avatar>
<q-avatar :icon="controls.sim_pause.icon" color="primary" text-color="white" />
<q-avatar :icon="controls.simulation.pause.icon" color="primary" text-color="white" />
</q-item-section>
<q-item-section>
<q-item-label> {{ controls.sim_pause.name }} </q-item-label>
<q-item-label> {{ controls.simulation.pause.name }} </q-item-label>
</q-item-section>
</q-item>
<q-item
@@ -183,27 +231,27 @@ function handleControlClick(cmdAttr: CtrlAttr) {
clickable
v-ripple
v-close-popup
@click="handleControlClick(controls.sim_resume)"
@click="handleControlClick(controls.simulation.resume)"
>
<q-item-section avatar>
<q-avatar :icon="controls.sim_resume.icon" color="primary" text-color="white" />
<q-avatar :icon="controls.simulation.resume.icon" color="primary" text-color="white" />
</q-item-section>
<q-item-section>
<q-item-label> {{ controls.sim_resume.name }} </q-item-label>
<q-item-label> {{ controls.simulation.resume.name }} </q-item-label>
</q-item-section>
</q-item>
<q-item
v-if="simulationQueueLength > 0"
v-if="simulationQueueLength && simulationQueueLength > 0"
clickable
v-ripple
v-close-popup
@click="handleControlClick(controls.sim_clear)"
@click="handleControlClick(controls.simulation.clear)"
>
<q-item-section avatar>
<q-avatar :icon="controls.sim_clear.icon" color="primary" text-color="white" />
<q-avatar :icon="controls.simulation.clear.icon" color="primary" text-color="white" />
</q-item-section>
<q-item-section>
<q-item-label> {{ controls.sim_clear.name }} </q-item-label>
<q-item-label> {{ controls.simulation.clear.name }} </q-item-label>
</q-item-section>
</q-item>
<q-item
@@ -211,31 +259,75 @@ function handleControlClick(cmdAttr: CtrlAttr) {
clickable
v-ripple
v-close-popup
@click="handleControlClick(controls.sim_end)"
@click="handleControlClick(controls.simulation.end)"
>
<q-item-section avatar>
<q-avatar :icon="controls.sim_end.icon" color="primary" text-color="white" />
<q-avatar :icon="controls.simulation.end.icon" color="primary" text-color="white" />
</q-item-section>
<q-item-section>
<q-item-label> {{ controls.sim_end.name }} </q-item-label>
<q-item-label> {{ controls.simulation.end.name }} </q-item-label>
</q-item-section>
</q-item>
<q-separator spaced />
<q-item-label header>iCloud Monitor Controls</q-item-label>
<q-item
v-if="!icloudMonitor"
clickable
v-ripple
v-close-popup
@click="handleControlClick(controls.icloudmonitor.start)"
>
<q-item-section avatar>
<q-avatar
v-if="icloudMonitor"
:icon="controls.icloudmonitor.start.icon"
color="primary"
text-color="white"
/>
</q-item-section>
<q-item-section>
<q-item-label> {{ controls.icloudmonitor.start.name }} </q-item-label>
</q-item-section>
</q-item>
<q-item
clickable
v-ripple
v-close-popup
@click="handleControlClick(controls.icloudmonitor.stop)"
>
<q-item-section avatar>
<q-avatar :icon="controls.icloudmonitor.stop.icon" color="primary" text-color="white" />
</q-item-section>
<q-item-section>
<q-item-label> {{ controls.icloudmonitor.stop.name }} </q-item-label>
</q-item-section>
</q-item>
<q-separator spaced />
<q-item-label header>Device Controls</q-item-label>
<q-item clickable v-ripple v-close-popup @click="handleControlClick(controls.dev_reboot)">
<q-item
clickable
v-ripple
v-close-popup
@click="handleControlClick(controls.device.reboot)"
>
<q-item-section avatar>
<q-avatar :icon="controls.dev_reboot.icon" color="primary" text-color="white" />
<q-avatar :icon="controls.device.reboot.icon" color="primary" text-color="white" />
</q-item-section>
<q-item-section>
<q-item-label> {{ controls.dev_reboot.name }} </q-item-label>
<q-item-label> {{ controls.device.reboot.name }} </q-item-label>
</q-item-section>
</q-item>
<q-item clickable v-ripple v-close-popup @click="handleControlClick(controls.dev_shutdown)">
<q-item
clickable
v-ripple
v-close-popup
@click="handleControlClick(controls.device.shutdown)"
>
<q-item-section avatar>
<q-avatar :icon="controls.dev_shutdown.icon" color="primary" text-color="white" />
<q-avatar :icon="controls.device.shutdown.icon" color="primary" text-color="white" />
</q-item-section>
<q-item-section>
<q-item-label> {{ controls.dev_shutdown.name }} </q-item-label>
<q-item-label> {{ controls.device.shutdown.name }} </q-item-label>
</q-item-section>
</q-item>
</q-list>

View File

@@ -9,7 +9,14 @@
<span class="q-ml-sm">
Are you sure you want to set location to {{ latitude }}, {{ longitude }} ?
</span>
<q-input dense v-model="delay" autofocus @keyup.enter="onOkClick" label="Delay (seconds)" type="number" />
<q-input
dense
v-model="delay"
autofocus
@keyup.enter="onOkClick"
label="Delay (seconds)"
type="number"
/>
</q-card-section>
<q-card-actions align="right">
<q-btn flat label="OK" color="primary" @click="onOkClick" />
@@ -37,6 +44,6 @@ defineEmits([...useDialogPluginComponent.emits]);
const { dialogRef: dlgRef, onDialogOK, onDialogCancel } = useDialogPluginComponent();
function onOkClick() {
onDialogOK();
onDialogOK(delay.value);
}
</script>

View File

@@ -8,6 +8,7 @@ const {
deviceConnected,
tunnelConnected,
simulationRunning,
icloudMonitor,
currentLocation,
nextLocation,
} = storeToRefs(socketioStore);
@@ -33,23 +34,54 @@ function statusDevColor(state: string | boolean): string {
<q-toolbar class="bg-primary text-white">
<div class="flex col q-gutter-md align-center justify-start content-center">
<q-space />
<span>Status:</span>
<span>
<q-badge :color="statusDevColor(sockConnected)" rounded class="q-mr-sm" />
<q-btn label="WebSocket" @click="socketioStore.toggleSock()" />
</span>
<span>
<q-badge :color="statusDevColor(deviceConnected)" rounded class="q-mr-sm" />
Device Connection
</span>
<span>
<q-badge :color="statusDevColor(tunnelConnected)" rounded class="q-mr-sm" />
tunneld
</span>
<span>
<q-badge :color="statusDevColor(simulationRunning)" rounded class="q-mr-sm" />
Location Simulation
</span>
<div style="width: 80vw" class="flex justify-end">
<q-btn
size="sm"
rounded
push
icon="settings"
class="q-mr-sm"
@click="socketioStore.toggleSock()"
>
<q-badge :color="statusDevColor(sockConnected)" rounded floating class="q-mr-sm" />
</q-btn>
<q-btn size="sm" rounded icon="phone_iphone" class="q-mr-sm">
<q-badge
:color="statusDevColor(deviceConnected)"
@click="socketioStore.requestUpdate()"
rounded
floating
class="q-mr-sm"
/>
</q-btn>
<q-btn
size="sm"
@click="socketioStore.toggleTunneld()"
rounded
icon="subway"
class="q-mr-sm"
>
<q-badge :color="statusDevColor(tunnelConnected)" rounded floating class="q-mr-sm" />
</q-btn>
<q-btn
size="sm"
@click="socketioStore.requestUpdate()"
rounded
icon="location_on"
class="q-mr-sm"
>
<q-badge :color="statusDevColor(simulationRunning)" rounded floating class="q-mr-sm" />
</q-btn>
<q-btn
size="sm"
@click="socketioStore.requestUpdate()"
rounded
icon="cloud"
class="q-mr-sm"
>
<q-badge :color="statusDevColor(icloudMonitor)" rounded floating class="q-mr-sm" />
</q-btn>
</div>
</div>
</q-toolbar>
</template>

View File

@@ -0,0 +1,41 @@
<template>
<q-dialog ref="dlgRef" persistent>
<q-card>
<q-card-section class="row items-center">
<q-avatar icon="password_2" color="primary" text-color="white" />
<span class="q-ml-sm"> iCloud Account requires Two-factor authentication.</span>
</q-card-section>
<q-card-section>
<q-input
dense
v-model="code"
mask="######"
autofocus
@keyup.enter="onOkClick"
label="Delay (seconds)"
type="number"
:rules="[(val) => val.length === 6 || 'Must be 6 digits']"
/>
</q-card-section>
<q-card-actions align="right">
<q-btn flat label="OK" color="primary" @click="onOkClick" />
<q-btn flat label="Cancel" color="primary" @click="onDialogCancel" />
</q-card-actions>
</q-card>
</q-dialog>
</template>
<script setup lang="ts">
import { useDialogPluginComponent } from 'quasar';
import { ref } from 'vue';
const code = ref('');
defineEmits([...useDialogPluginComponent.emits]);
const { dialogRef: dlgRef, onDialogOK, onDialogCancel } = useDialogPluginComponent();
function onOkClick() {
onDialogOK(code.value);
}
</script>

View File

@@ -1,12 +1,14 @@
export type SimulationCommands = "start" | "pause" | "resume" | "clear" | "end" | "add";
export type DeviceCommands= "start_tunnel" | "stop_tunnel" | "shutdown";
export interface CtrlAttrs {
[key: string]: CtrlAttr;
}
export type SimulationCommands = 'start' | 'pause' | 'resume' | 'clear' | 'end' | 'add';
export type DeviceCommands = 'start_tunnel' | 'stop_tunnel' | 'shutdown' | 'reboot';
export type TunnelCommands = 'start' | 'start-watcher' | 'end-watcher' | 'shutdown' | 'restart' | 'clear' | 'cancel' ;
export interface CtrlAttr {
name: string;
cmd: string;
@@ -16,6 +18,15 @@ export interface CtrlAttr {
delay: number;
}
export interface DevCtrlAttr {
name: string;
cmd: DeviceCommands;
cmdClass: 'device_control';
icon: string;
cnfrm: boolean;
delay: number;
}
export interface LocationQueue {
[key: string]: LocationMark
}
@@ -24,9 +35,155 @@ interface LocationMark {
loc_id: string;
latitude: number | undefined | null;
longitude: number | undefined | null;
address?: string | undefined | null;
delay?: number | undefined | null;
start_time: string | undefined | null;
end_time?: string | undefined | null ;
start?: string | undefined | null;
end?: string | undefined | null ;
}
// SERVER TO CLIENT
export interface ServerToClientEvents {
noArg: () => void;
withAck: (a: string, callback: (b: number) => void) => void;
simulation_status: (c: SimulationStatus) => void;
status: (d: StatusUpdate) => void;
device_status: (d: DeviceStatus) => void;
error: (data: ErrorFull) => void;
message: (e: string) => void;
icloud_2fa_request: (callback: (e: number) => void) => void;
fmf_update: (d: FindMyUpdate) => void;
}
interface SimulationStatus {
status: boolean;
data: {
latitude: number;
longitude: number;
start: string;
end?: string;
next_move?: number;
};
}
export interface StatusUpdate {
connected_clients: { [key: string]: string };
current_location: {
loc_id: string;
latitude: number | undefined | null;
longitude: number | undefined | null;
start?: string | undefined | null;
};
device_name: string | undefined | null;
fmf_location: FindMyUpdate | undefined | null;
icloud: {
consumer_queue: number | undefined | null;
consumer_task: string | undefined | null;
monitor_enabled: boolean;
monitor_task: string | undefined | null;
monitor_running: boolean;
};
next_move?: number | undefined | null;
set_location_enabled: boolean;
simulation_queue: {
active: boolean;
data: {
[key: string]: LocationMark;
};
order: string[];
state: string | undefined | null;
worker_task: string | undefined | null;
};
test_mode: boolean;
tunnel: string | undefined | null;
tunnel_watcher_running: boolean;
device_count?: number | undefined | null;
udid?: string | null;
product_version?: string | null | undefined;
phone_number?: string | null | undefined;
developer_mode_enabled?: boolean | undefined | null;
ddi_mounted?: boolean;
rsd_address?: string | null | undefined;
rsd_port?: number | undefined | null;
lockdown_trusted_port?: number | undefined | null;
lockdown_untrusted_port?: number | undefined | null;
lockdown_trusted_reachable?: boolean | undefined | null;
lockdown_untrusted_reachable?: boolean | undefined | null;
dtservicehub_reachable?: boolean | undefined | null;
}
interface DeviceStatus {
device_connected: boolean;
device_count: number;
udid?: string | null;
device_name?: string | null;
product_version?: string | null;
phone_number?: string | null;
developer_mode_enabled?: boolean;
ddi_mounted?: boolean;
rsd_address?: string | null;
rsd_port?: number;
lockdown_trusted_port?: number;
lockdown_untrusted_port?: number;
lockdown_trusted_reachable?: boolean;
lockdown_untrusted_reachable?: boolean;
dtservicehub_reachable?: boolean;
}
export interface ErrorFull {
type: string;
error: string;
}
export interface FindMyUpdate {
altitude: number;
batteryLevel: number;
deviceDisplayName: string;
deviceStatus: number;
horizontalAccuracy: number;
latitude: number;
longitude: number;
name: string;
timeStamp: number;
verticalAccuracy: number;
}
// END SERVER TO CLIENT
// CLIENT TO SERVER
export interface ClientToServerEvents {
message: (e: string, callback: (b: boolean, r: string) => void) => void;
request_update: (callback: (response: StatusUpdate) => void) => void;
simulation_control: (
args: {
command: SimulationCommands;
latitude?: number | null | undefined;
longitude?: number | null | undefined;
delay?: number | undefined;
},
callback: (response: SimulationControlResponse) => void,
) => void;
device_control: (
args: {
command: DeviceCommands;
delay?: number;
},
callback?: (response: DeviceControlResponse) => void,
) => void;
tunnel_control: (
args: {
command: TunnelCommands;
delay?: number;
},
callback?: (response: DeviceControlResponse) => void,
) => void;
icloud_monitor_control: (
args: {
command: string;
},
callback?: (response: iCloudMonitorResponse) => void,
) => void;
}
export interface SimulationControlResponse {
@@ -47,119 +204,20 @@ interface DeviceControlResponse {
delay?: number;
}
interface StatusUpdate {
simulation_active: boolean;
set_location_enabled: boolean;
queue: number,
latitude: number | undefined | null;
longitude: number | undefined | null;
next_move?: number | undefined | null;
queue_list: LocationQueue[]
queue_state: string | undefined | null;
queue_status: boolean;
simulation_task: string | undefined | null;
test_mode: boolean
tunnel: string | undefined | null;
device_count?: number | undefined | null;
udid?: string | null;
device_name?: string | null | undefined;
product_version?: string | null | undefined;
phone_number?: string | null | undefined;
developer_mode_enabled?: boolean | undefined | null;
ddi_mounted?: boolean ;
rsd_address?: string | null | undefined;
rsd_port?: number | undefined | null;
lockdown_trusted_port?: number | undefined | null;
lockdown_untrusted_port?: number | undefined | null;
lockdown_trusted_reachable?: boolean | undefined | null;
lockdown_untrusted_reachable?: boolean | undefined | null;
dtservicehub_reachable?: boolean | undefined | null
export interface iCloudMonitorResponse {
status: string;
command: string;
icloud_monitor_enabled?: boolean | undefined | null;
icloud_monitor_running?: boolean | undefined | null;
message?: string | undefined | null;
}
export interface ServerToClientEvents {
noArg: () => void;
withAck: (a: string, callback: (b: number) => void) => void;
simulationStatus: (c: SimulationStatus) => void;
status: (d: StatusUpdate) => void;
device_status: (d: DeviceStatus) => void;
error: (data: ErrorFull) => void;
message: (e: string) => void;
}
export interface ClientToServerEvents {
message: (e: string, callback: (b: boolean, r: string) => void) => void;
simulation_control: (
args: {
command: SimulationCommands,
latitude?: number | null | undefined,
longitude?: number | null | undefined,
delay?: number | undefined
},
callback: (response: SimulationControlResponse) => void
) => void;
device_control: (
args: {
command: DeviceCommands,
delay?: number
},
callback?: (response: DeviceControlResponse) => void
) => void;
}
interface SimulationStatus {
status: boolean;
data: {
latitude: number;
longitude: number;
start: string;
end?: string;
next_move?: number;
};
}
// END CLIENT TO SERVER
export interface Meta {
totalCount: number;
}
interface DeviceStatus {
device_connected: boolean;
device_count: number;
udid?: string | null;
device_name?: string | null;
product_version?: string | null;
phone_number?: string | null;
developer_mode_enabled?: boolean;
ddi_mounted?: boolean;
rsd_address?: string | null;
rsd_port?: number;
lockdown_trusted_port?: number;
lockdown_untrusted_port?: number;
lockdown_trusted_reachable?: boolean;
lockdown_untrusted_reachable?: boolean;
dtservicehub_reachable?: boolean;
}
export type Control = DeviceControl | SimulationControl;
export interface DeviceControl {
id: number;
name: string;
cmd: DeviceCommands
cmdClass: "device_control"
icon: string;
confirm: boolean;
}
export interface SimulationControl {
id: number;
name: string;
cmd: SimulationCommands;
cmdClass: 'simulation_control';
icon: string;
confirm: boolean;
}
export interface coords {
lat: number;
lng: number;
@@ -183,9 +241,11 @@ export interface SearchControlProps {
export interface CurrentLocation {
loc_id: string;
latitude: number;
longitude: number;
next_move?: number | null
latitude: number| null | undefined;
longitude: number| null | undefined;
// start_time?: string | null | undefined;
// end_time?: string | null | undefined
next_move?: number | null | undefined;
}
export interface NextLocation {
@@ -195,7 +255,4 @@ export interface NextLocation {
time_at_location?: number | null;
}
export interface ErrorFull {
type: string;
error: string;
}

View File

@@ -1,62 +1,82 @@
import type { CtrlAttrs } from 'components/models';
export const controls: CtrlAttrs = {
sim_start: {
export const controls = {
simulation: {
start: {
name: 'Start Location Sim',
cmd: 'start',
cmdClass: 'simulation_control',
cmdClass: 'sim_cntrl_class',
icon: 'play_arrow',
cnfrm: false,
delay: 0,
},
sim_pause: {
pause: {
name: 'Pause Location Sim',
cmd: 'pause',
cmdClass: 'simulation_control',
cmdClass: 'sim_cntrl_class',
icon: 'pause',
cnfrm: false,
delay: 0,
},
sim_resume: {
resume: {
name: 'Resume Location Simulation',
cmd: 'resume',
cmdClass: 'simulation_control',
cmdClass: 'sim_cntrl_class',
icon: 'play_arrow',
cnfrm: false,
delay: 0,
},
sim_clear: {
clear: {
name: 'Clear Location Queue',
cmd: 'clear',
cmdClass: 'simulation_control',
cmdClass: 'sim_cntrl_class',
icon: 'directions_off',
cnfrm: false,
delay: 0,
},
sim_end: {
end: {
name: 'End Location Sim',
cmd: 'end',
cmdClass: 'simulation_control',
cmdClass: 'sim_cntrl_class',
icon: 'stop',
cnfrm: true,
delay: 0,
},
dev_shutdown: {
},
device: {
shutdown: {
name: 'Shutdown',
cmd: 'shutdown',
cmdClass: 'device_control',
cmdClass: 'dev_cntrl_class',
icon: 'power_settings_new',
cnfrm: true,
delay: 5,
},
dev_reboot: {
reboot: {
name: 'Reboot',
cmd: 'reboot',
cmdClass: 'device_control',
cmdClass: 'dev_cntrl_class',
icon: 'restart_alt',
cnfrm: true,
delay: 5,
}
},
icloudmonitor: {
start: {
name: 'Start iCloud Monitor',
cmd: 'start',
cmdClass: 'icloud-monitor_cntrl_class',
icon: 'play_arrow',
cnfrm: false,
delay: 0,
},
stop: {
name: 'Stop iCloud Monitor',
cmd: 'stop',
cmdClass: 'icloud-monitor_cntrl_class',
icon: 'stop',
cnfrm: false,
delay: 0,
}
}
};

View File

@@ -1,5 +1,5 @@
export const favorites = [
{
export const favorites = {
home: {
name: 'Home',
icon: 'home',
coords: {
@@ -7,11 +7,11 @@ export const favorites = [
lng: -73.891069806448,
},
},
{
name: "Work Places",
icon: "work",
subitems: [
{
work_places: {
name: 'Work Places',
icon: 'work',
subitems: {
jeong: {
name: 'Jeong',
icon: 'language_korean_latin',
coords: {
@@ -20,17 +20,17 @@ export const favorites = [
},
address: '35-02 150th Pl, Flushing, NY 11354',
},
{
santos: {
name: 'Santos',
icon: 'rice_bowl',
coords: {
lat: 40.74504671877868,
lng: -73.8880099638491,
},
address: '77-08 Broadway, Elmhurst, NY 11373'
address: '77-08 Broadway, Elmhurst, NY 11373',
},
{
name: 'Natalyaa (Qns)',
natalya_qns: {
name: 'Natalya (Qns)',
icon: 'currency_ruble',
coords: {
lat: 40.69644966409178,
@@ -38,8 +38,8 @@ export const favorites = [
},
address: '110-14 Jamaica Ave, Richmond Hill, NY 11418',
},
{
name: 'Natalyaa (Bronx)',
natalya_bx: {
name: 'Natalya (Bronx)',
icon: 'currency_ruble',
coords: {
lat: 40.85384419116598,
@@ -47,7 +47,7 @@ export const favorites = [
},
address: '2109 Matthews Ave, Bronx, NY 10462',
},
{
office: {
name: 'Linwood Plaza',
icon: 'dermatology',
coords: {
@@ -56,9 +56,9 @@ export const favorites = [
},
address: '158 Linwood Plaza, Fort Lee, NJ 07024',
},
],
},
{
},
strg: {
name: 'Man Mini Storage',
icon: 'box',
coords: {
@@ -67,8 +67,8 @@ export const favorites = [
},
address: '31-08 Northern Blvd, Long Island City, NY 11101',
},
{
name: 'Acmd',
acme: {
name: 'Acme',
icon: 'grocery',
coords: {
lat: 40.90930366920829,
@@ -76,4 +76,4 @@ export const favorites = [
},
address: '31-08 Northern Blvd, Long Island City, NY 11101',
},
];
};

View File

@@ -1,15 +1,15 @@
import { Utilities } from "@vue-leaflet/vue-leaflet";
import type * as L from "leaflet";
import type { IRouter, IGeocoder, LineOptions } from "leaflet-routing-machine";
import { Utilities } from '@vue-leaflet/vue-leaflet';
import type L from 'leaflet';
import type { IRouter, IGeocoder, LineOptions } from 'leaflet-routing-machine';
// Props typing
// ---- Props typing ----
export interface RoutingControlProps {
waypoints: L.LatLng[];
waypoints: L.Routing.Waypoint[];
router?: IRouter;
plan?: any; // L.Routing.Plan (can refine if you typed it)
plan?: L.Routing.Plan;
fitSelectedRoutes?: string | boolean;
lineOptions?: LineOptions;
routeLine?: (...args: any[]) => any;
routeLine?: (route: any) => L.Layer;
autoRoute?: boolean;
routeWhileDragging?: boolean;
routeDragInterval?: number;
@@ -19,30 +19,30 @@ export interface RoutingControlProps {
altLineOptions?: LineOptions;
}
// Vue-style prop definitions (still needed for runtime)
// ---- Vue-compatible prop definition ----
export const routingControlProps = {
waypoints: {
type: Array as () => L.LatLng[],
type: Array as () => L.Routing.Waypoint[],
default: () => [],
},
router: {
type: Object as () => IRouter,
type: Object as () => IRouter | undefined,
default: undefined,
},
plan: {
type: Object as () => any,
type: Object as () => L.Routing.Plan | undefined,
default: undefined,
},
fitSelectedRoutes: {
type: [String, Boolean] as () => string | boolean,
default: "smart",
type: [String, Boolean] as unknown as () => string | boolean,
default: 'smart',
},
lineOptions: {
type: Object as () => LineOptions,
type: Object as () => LineOptions | undefined,
default: undefined,
},
routeLine: {
type: Function as () => (...args: any[]) => any,
type: Function as unknown as () => ((route: any) => L.Layer) | undefined,
default: undefined,
},
autoRoute: {
@@ -59,7 +59,7 @@ export const routingControlProps = {
},
waypointMode: {
type: String,
default: "connect",
default: 'connect',
},
useZoomParameter: {
type: Boolean,
@@ -70,17 +70,17 @@ export const routingControlProps = {
default: false,
},
altLineOptions: {
type: Object as () => LineOptions,
type: Object as () => LineOptions | undefined,
default: undefined,
},
};
// Setup function
// ---- Setup function ----
export const setupRoutingControl = (props: RoutingControlProps) => {
const options = Utilities.propsToLeafletOptions(
props,
routingControlProps
);
routingControlProps,
) as L.Routing.RoutingControlOptions;
return {
options,

View File

@@ -1,19 +1,36 @@
import { defineStore, acceptHMRUpdate } from 'pinia';
import { favorites } from 'constants/favorites'
interface State {
zoom: number
center: [number, number]
markerLatLng: [number, number]
center: [number, number] | [null, null] | null
markerLatLng: [number, number] | [null, null] | null
qLocDrawer: boolean
}
export const useLeafletStore = defineStore('leaflet', {
state: (): State => {
return {
zoom: 10,
center: [40.71278, -74.00594],
markerLatLng: [40.71278, -74.00594],
center: [favorites.home.coords.lat, favorites.home.coords.lng],
markerLatLng: null,
qLocDrawer: false,
}
},
actions: {
setCenter(lat: number, lng: number) {
this.center = [lat, lng];
},
setZoom(zoom: number) {
this.zoom = zoom;
},
setMarkerLatLng(lat: number, lng: number) {
this.markerLatLng = [lat, lng];
},
toggleQLocDrawer() {
this.qLocDrawer = !this.qLocDrawer
}
}
})
if (import.meta.hot) {

View File

@@ -1,48 +1,61 @@
import { defineStore, acceptHMRUpdate } from 'pinia';
import { socket } from 'boot/socketio';
import { useQuasar } from 'quasar';
import iCloudCodeDialog from 'components/iCloudCodeDialog.vue';
import type {
CurrentLocation,
NextLocation,
ErrorFull,
SimulationControl,
SimulationCommands,
LocationQueue,
SimulationControlResponse,
StatusUpdate
StatusUpdate,
FindMyUpdate,
iCloudMonitorResponse,
} from 'components/models';
const $q = useQuasar();
const debugLog: boolean = true;
export const useSocketioStore = defineStore('socketio', {
state: () => {
return {
sockConnected: false as boolean,
socketID: null as string | null | undefined,
testMode: null as boolean | undefined | null,
deviceConnected: false as boolean,
tunnelConnected: false as boolean,
simulationRunning: false as boolean | string,
simulationState: null as string | null | undefined,
simulationQueneLength: 0 as number,
currentLocation: null as CurrentLocation | null,
simulationQueueLength: 0 as number | null | undefined,
currentLocation: null as CurrentLocation | null | undefined,
nextLocation: null as NextLocation | null,
messageList: [''] as string[],
errorList: [] as ErrorFull[],
locationQueue: [] as LocationQueue[],
leafLetZoom: 10 as number,
locationQueueData: {} as LocationQueue,
locationQueueOrder: [] as string[],
leafletZoom: 10 as number,
icloudMonitor: false as boolean,
findMyUpdate: null as FindMyUpdate | null | undefined,
};
},
getters: {
sockState: (state) => state.sockConnected,
deviceState: (state) => state.deviceConnected,
markerLatLng: (state) => {
return [state.currentLocation.latitude, state.currentLocation.longitude]
lMarkerLatLng: (state): [number, number] => {
if (
state.currentLocation == null ||
!state.currentLocation.latitude ||
!state.currentLocation.longitude
) {
return [0, 0];
}
return [state.currentLocation.latitude, state.currentLocation.longitude];
},
center(): [number, number] {
return this.leafletCurrentMarker
lCenter(): [number, number] {
return this.lMarkerLatLng;
},
zoom: (state) => state.leafletZoom,
lZoom: (state): number => state.leafletZoom,
},
actions: {
setSockStatus() {
@@ -54,9 +67,13 @@ export const useSocketioStore = defineStore('socketio', {
socket.on('connect', () => {
this.setSockStatus();
socket.emit('message', 'Hello from client', (e: boolean) => {
if (debugLog) {
console.log('Message delivered: ' + e);
}
});
if (debugLog) {
console.log('Connected to server');
}
});
socket.on('disconnect', () => {
@@ -67,31 +84,79 @@ export const useSocketioStore = defineStore('socketio', {
socket.on('message', (e: string) => {
this.setSockStatus();
this.messageList.push(e);
if (debugLog) {
console.log('Websock message received!');
}
});
socket.on('error', (data: ErrorFull) => {
this.setSockStatus();
const errorFull = { type: data.type, error: data.error };
this.errorList.push(errorFull);
console.error('Error Received: ', data);
});
socket.on('status', (data: StatusUpdate): void => {
console.log("StatusUpdate received: ", data);
this.simulationRunning = data.simulation_active;
this.simulationState = data.queue_state;
this.simulationQueneLength = data.quene_length;
this.tunnelConnected = !!data.tunnel;
this.currentLocation = { loc_id: data.loc_id, latitude: data.latitude, longitude: data.longitude, next_move: data.next_mode };
if (debugLog) {
console.log('StatusUpdate received: ', data);
}
this.digestUpdate(data);
});
socket.on('fmf_update', (data: FindMyUpdate): void => {
if (debugLog) {
console.log('event: fmf_update received: ', data);
}
this.findMyUpdate = data;
});
socket.on('icloud_2fa_request', (callback) => {
if (debugLog) {
console.log('iCloud 2FA Request');
}
$q.dialog({
component: iCloudCodeDialog,
})
.onOk((code: number) => {
if (callback && typeof callback === 'function') {
callback(code);
}
})
.onCancel(() => {
if (debugLog) {
console.log('Dialog cancelled');
}
})
.onDismiss(() => {
if (debugLog) {
console.log('Dialog dismissed');
}
});
});
socket.onAny((eventName, ...args) => {
this.setSockStatus();
console.log(`Received event: ${eventName}`, args);
console.log('Event received: ', eventName, ' Args: ', args, '');
/* if (serverToClientKnownEvents.includes(eventName)) {
console.log('Known event received: ', eventName, ' Args: ', args, '');
} else {
console.log(
'Known events: ',
serverToClientKnownEvents,
' Received Event: ',
eventName,
' Args: ',
args,
'',
);
console.log(`Received UNKNOWN Event: ${eventName}`, args);
}
*/
});
},
connect() {
if (debugLog) {
console.log('Connecting to server...');
}
socket.connect();
this.setSockStatus();
},
@@ -108,63 +173,202 @@ export const useSocketioStore = defineStore('socketio', {
}
this.setSockStatus();
},
setSimulationRunning(isRunning: boolean, states: string ): void {
setSimulationRunning(isRunning: boolean, states: string): void {
this.setSockStatus();
this.simulationState = states;
this.simulationRunning = isRunning;
},
simulationControl(command: SimulationCommands, delay?: number, latitude?: number | null, longitude?: number | null): string | never {
icloudMonitorControl(command: string) {
let fnctRtn: { sts: string; msg?: string | undefined } = { sts: '', msg: '' };
switch (command) {
case 'start':
if (debugLog) {
console.log('socketStore: got command: icloudMonitor start');
}
if (this.icloudMonitor) {
fnctRtn = { sts: 'error', msg: 'iCloud Monitor is already running' };
throw new Error('iCloud Monitor is already running');
}
if (debugLog) {
console.log('Emitting icloud_monitor_control: start');
}
socket.emit(
'icloud_monitor_control',
{ command: 'start' },
(response: iCloudMonitorResponse) => {
fnctRtn.sts = response.status;
if (response.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 Monitor: ' + response.status };
this.icloudMonitor = true;
}
});
break;
case 'stop':
if (debugLog) {
console.log('socketStore: got command: icloudMonitor stop');
}
if (!this.icloudMonitor) {
fnctRtn = { sts: 'error', msg: 'iCloud Monitor is not running' };
throw new Error('iCloud Monitor is not running');
}
if (debugLog) {
console.log('Emitting icloud_monitor_control: stop');
}
socket.emit(
'icloud_monitor_control',
{ command: 'stop' },
(response: iCloudMonitorResponse) => {
fnctRtn.sts = response.status;
if (response.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 Monitor: ' + response.status };
this.icloudMonitor = false;
}
});
break;
case 'status':
if (debugLog) {
console.log('socketStore: got command: icloudMonitor status');
}
if (debugLog) {
console.log('Emitting icloud_monitor_control: status');
}
socket.emit(
'icloud_monitor_control',
{ command: 'status' },
(response: iCloudMonitorResponse) => {
fnctRtn.sts = response.status;
if (response.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 Monitor Enabled: ' +
response.icloud_monitor_enabled +
'iCloud Monitor Running: ' +
response.icloud_monitor_running
};
}
this.icloudMonitor = !!(response.icloud_monitor_enabled && response.icloud_monitor_running);
});
break;
default:
fnctRtn = { sts: 'error', msg: 'Invalid command' };
throw new Error('Invalid command');
}
return fnctRtn;
},
simulationControl(
command: string,
delay?: number,
latitude?: number | null,
longitude?: number | null,
) {
let fnctRtn: { sts: string; msg?: string | undefined } = { sts: '', msg: '' };
this.setSockStatus();
switch (command) {
case 'start':
console.log("socketStore: got command: start");
if (this.simulationRunning || this.simulationState == "RUNNING" || this.simulationState == "PAUSED") {
if (debugLog) {
console.log('socketStore: got command: start');
}
if (
this.simulationRunning ||
this.simulationState == 'RUNNING' ||
this.simulationState == 'PAUSED'
) {
fnctRtn = { sts: 'error', msg: 'Simulation is already running' };
throw new Error('Simulation is already running' + this.simulationState);
}
console.log("Emmitting simulation_control: start")
socket.emit('simulation_control', { command: 'start', delay: 0, latitude: null, longitude: null}, (response: SimulationControlResponse) => {
if (response.status == "error") {
if (debugLog) {
console.log('Emitting simulation_control: start');
}
socket.emit(
'simulation_control',
{ command: 'start', delay: 0, latitude: null, longitude: null },
(response: SimulationControlResponse) => {
if (response.status == 'error') {
fnctRtn = { sts: 'error', msg: response.message?.toString() };
throw new Error(response.message);
} else {
fnctRtn = { sts: 'OK', msg: response.message?.toString() };
this.simulationState = response.status;
if (debugLog) {
console.log(response.message);
return response.message;
}
});
// return response.message;
}
},
);
break;
case 'pause':
if (this.simulationState !== "RUNNING" ) {
if (this.simulationState !== 'RUNNING') {
throw new Error('Simulation is not running');
}
socket.emit('simulation_control', { command: 'pause' }, (response: SimulationControlResponse) => {
if (response.status === "error") {
socket.emit(
'simulation_control',
{ command: 'pause' },
(response: SimulationControlResponse) => {
if (response.status === 'error') {
throw new Error(response.message);
} else {
this.simulationState = response.status;
if (debugLog) {
console.log(response.message);
}
return response.message;
}
});
},
);
break;
case 'resume':
if (this.simulationState !== "PAUSED") {
if (this.simulationState !== 'PAUSED') {
throw new Error('Simulation is not paused');
}
socket.emit('simulation_control', { command: 'resume' }, (response) => {
if (response.status == "error") {
throw new Error(response.message)
if (response.status == 'error') {
throw new Error(response.message);
} else {
this.simulationState = response.status;
console.log(response.message)
return response.message
if (debugLog) {
console.log(response.message);
}
return response.message;
}
});
break;
case 'clear':
if (this.simulationQueueLength== 0) {
if (this.simulationQueueLength == 0) {
throw new Error('Simulation queue is empty');
}
if (this.simulationState == "STOPPED " || !this.simulationRunning) {
if (this.simulationState == 'STOPPED ' || !this.simulationRunning) {
throw new Error('Simulation is not running');
}
socket.emit('simulation_control', { command: 'clear' }, (response) => {
@@ -172,13 +376,15 @@ export const useSocketioStore = defineStore('socketio', {
throw new Error(response.message);
} else {
this.simulationState = response.status;
if (debugLog) {
console.log(response.message);
}
return response.message;
}
});
break;
case 'end':
if (this.simulationState == "ENDED" || !this.simulationRunning) {
if (this.simulationState == 'ENDED' || !this.simulationRunning) {
throw new Error('Simulation is already ended');
}
socket.emit('simulation_control', { command: 'end' }, (response) => {
@@ -186,24 +392,31 @@ export const useSocketioStore = defineStore('socketio', {
throw new Error(response.message);
} else {
this.simulationState = response.status;
if (debugLog) {
console.log(response.message);
}
return response.message;
}
});
break;
case 'add':
if (this.simulationState == "ENDED" || !this.simulationRunning) {
throw new Error('Simulation is not running');
}
// if (this.simulationState == 'ENDED' || !this.simulationRunning) {
// throw new Error('Simulation is not running');
// }
if (!latitude || !longitude) {
throw new Error ("latitude or longitude not set");
throw new Error('latitude or longitude not set');
}
socket.emit('simulation_control',{ command: 'add', latitude: latitude, longitude: longitude, delay: delay }, (response) => {
if (response.status == "error") {
throw new Error(response.message)
socket.emit(
'simulation_control',
{ command: 'add', latitude: latitude, longitude: longitude, delay: delay },
(response) => {
if (response.status == 'error') {
throw new Error(response.message);
} else {
this.simulationState = response.status;
console.log("response from simulate_control_add: ", response);
if (debugLog) {
console.log('response from simulate_control_add: ', response);
}
const locMrk = {
[response.loc_id]: {
loc_id: response.loc_id,
@@ -213,14 +426,39 @@ export const useSocketioStore = defineStore('socketio', {
start_time: response.start_time,
},
};
this.locationQueue.push(locMrk);
return response.message;
}
});
},
);
break;
default:
fnctRtn = { sts: 'error', msg: 'Invalid command' };
throw new Error('Invalid command');
}
return fnctRtn;
},
requestUpdate(): void {
socket.emit('request_update', (response: StatusUpdate) => {
this.digestUpdate(response);
});
},
digestUpdate(data: StatusUpdate): void {
this.deviceConnected = !!(data.udid && data.tunnel);
this.testMode = data.test_mode;
this.simulationRunning = data.simulation_queue.active;
this.simulationState = data.simulation_queue.state;
this.simulationQueueLength = data.simulation_queue.order.length;
this.tunnelConnected = !!data.tunnel;
this.icloudMonitor = data.icloud.monitor_enabled;
this.currentLocation = {
loc_id: data.current_location.loc_id,
latitude: data.current_location.latitude,
longitude: data.current_location.longitude,
next_move: data.next_move,
};
this.locationQueueData = data.simulation_queue.data;
this.locationQueueOrder = data.simulation_queue.order;
this.findMyUpdate = data.fmf_location;
},
setDeviceState(state: boolean) {
this.deviceConnected = state;