rework Menu Bar / Rework socketioStore
This commit is contained in:
44
package-lock.json
generated
44
package-lock.json
generated
@@ -13,7 +13,9 @@
|
||||
"@vue-leaflet/vue-leaflet": "^0.10.1",
|
||||
"axios": "^1.2.1",
|
||||
"leaflet": "^1.9.4",
|
||||
"leaflet-extra-markers": "^2.0.1",
|
||||
"leaflet-geosearch": "^4.2.2",
|
||||
"leaflet-routing-machine": "^3.2.12",
|
||||
"pinia": "^3.0.4",
|
||||
"quasar": "^2.16.0",
|
||||
"socket.io-client": "^4.8.3",
|
||||
@@ -1043,6 +1045,23 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/corslite": {
|
||||
"version": "0.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@mapbox/corslite/-/corslite-0.0.7.tgz",
|
||||
"integrity": "sha512-w/uS474VFjmqQ7fFWIMZINQM1BAQxDLuoJaZZIPES1BmeYpCtlh9MtbFxKGGDAsfvut8/HircIsVvEYRjQ+iMg==",
|
||||
"license": "BSD"
|
||||
},
|
||||
"node_modules/@mapbox/polyline": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@mapbox/polyline/-/polyline-0.2.0.tgz",
|
||||
"integrity": "sha512-GCddO0iw6AzOQqZgBmjEQI9Pgo40/yRgkTkikGctE01kNBN0ThWYuAnTD+hRWrAWMV6QJ0rNm4m8DAsaAXE7Pg==",
|
||||
"bin": {
|
||||
"polyline": "bin/polyline.bin.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
@@ -5729,6 +5748,14 @@
|
||||
"integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==",
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/leaflet-extra-markers": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/leaflet-extra-markers/-/leaflet-extra-markers-2.0.1.tgz",
|
||||
"integrity": "sha512-cbCo+YSgnN+tomDJRxv/vf+ZXfLymSKjDDuv7omYaOO+8oj9q9uvwytL+eViyClPuscAT3lNh339R+gr8w20qg==",
|
||||
"peerDependencies": {
|
||||
"leaflet": "^1.9.4"
|
||||
}
|
||||
},
|
||||
"node_modules/leaflet-geosearch": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/leaflet-geosearch/-/leaflet-geosearch-4.2.2.tgz",
|
||||
@@ -5739,6 +5766,17 @@
|
||||
"leaflet": "^1.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/leaflet-routing-machine": {
|
||||
"version": "3.2.12",
|
||||
"resolved": "https://registry.npmjs.org/leaflet-routing-machine/-/leaflet-routing-machine-3.2.12.tgz",
|
||||
"integrity": "sha512-HLde58G1YtD9xSIzZavJ6BPABZaV1hHeGst8ouhzuxmSC3s32NVtADT+njbIUMW1maHRCrsgTk/E4hz5QH7FrA==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@mapbox/corslite": "0.0.7",
|
||||
"@mapbox/polyline": "^0.2.0",
|
||||
"osrm-text-instructions": "^0.13.2"
|
||||
}
|
||||
},
|
||||
"node_modules/levn": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
|
||||
@@ -6305,6 +6343,12 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/osrm-text-instructions": {
|
||||
"version": "0.13.4",
|
||||
"resolved": "https://registry.npmjs.org/osrm-text-instructions/-/osrm-text-instructions-0.13.4.tgz",
|
||||
"integrity": "sha512-ge4ZTIetMQKAHKq2MwWf83ntzdJN20ndRKRaVNoZ3SkDkBNO99Qddz7r6+hrVx38I+ih6Rk5T1yslczAB6Q9Pg==",
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/p-limit": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
|
||||
|
||||
@@ -19,7 +19,9 @@
|
||||
"@vue-leaflet/vue-leaflet": "^0.10.1",
|
||||
"axios": "^1.2.1",
|
||||
"leaflet": "^1.9.4",
|
||||
"leaflet-extra-markers": "^2.0.1",
|
||||
"leaflet-geosearch": "^4.2.2",
|
||||
"leaflet-routing-machine": "^3.2.12",
|
||||
"pinia": "^3.0.4",
|
||||
"quasar": "^2.16.0",
|
||||
"socket.io-client": "^4.8.3",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-file
|
||||
|
||||
import { defineConfig } from '#q-app/wrappers';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
export default defineConfig((/* ctx */) => {
|
||||
return {
|
||||
@@ -11,7 +12,7 @@ export default defineConfig((/* ctx */) => {
|
||||
// app boot file (/src/boot)
|
||||
// --> boot files are part of "main.js"
|
||||
// https://v2.quasar.dev/quasar-cli-vite/boot-files
|
||||
boot: ['axios', 'socket'],
|
||||
boot: ['axios', 'socketio'],
|
||||
|
||||
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#css
|
||||
css: ['app.scss'],
|
||||
@@ -32,6 +33,11 @@ export default defineConfig((/* ctx */) => {
|
||||
|
||||
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#build
|
||||
build: {
|
||||
alias: {
|
||||
constants: fileURLToPath(new URL('./src/constants', import.meta.url)),
|
||||
functions: fileURLToPath(new URL('./src/functions', import.meta.url)),
|
||||
types: fileURLToPath(new URL('./src/types', import.meta.url)),
|
||||
},
|
||||
target: {
|
||||
browser: ['es2022', 'firefox115', 'chrome115', 'safari14'],
|
||||
node: 'node20',
|
||||
@@ -43,7 +49,7 @@ export default defineConfig((/* ctx */) => {
|
||||
// extendTsConfig (tsConfig) {}
|
||||
},
|
||||
|
||||
vueRouterMode: 'hash', // available values: 'hash', 'history'
|
||||
vueRouterMode: 'history', // available values: 'hash', 'history'
|
||||
// vueRouterBase,
|
||||
// vueDevtools,
|
||||
// vueOptionsAPI: false,
|
||||
@@ -65,7 +71,13 @@ export default defineConfig((/* ctx */) => {
|
||||
extendViteConf() {
|
||||
return {
|
||||
server: {
|
||||
allowedHosts: ['localhost'],
|
||||
hmr: {
|
||||
// overlay: false,
|
||||
},
|
||||
allowedHosts: [
|
||||
'localhost',
|
||||
'strixx.famor.org'
|
||||
],
|
||||
},
|
||||
};
|
||||
},
|
||||
@@ -79,6 +91,7 @@ export default defineConfig((/* ctx */) => {
|
||||
lintCommand: 'eslint -c ./eslint.config.js "./src*/**/*.{ts,js,mjs,cjs,vue}"',
|
||||
useFlatConfig: true,
|
||||
},
|
||||
overlay: false,
|
||||
},
|
||||
{ server: false },
|
||||
],
|
||||
@@ -95,6 +108,7 @@ export default defineConfig((/* ctx */) => {
|
||||
'/api': {
|
||||
target: 'http://localhost:8000',
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/api/, ''),
|
||||
},
|
||||
'/socket.io': {
|
||||
target: 'http://localhost:8000', // Your backend WebSocket server
|
||||
@@ -112,7 +126,7 @@ export default defineConfig((/* ctx */) => {
|
||||
config: {
|
||||
dark: true,
|
||||
},
|
||||
iconSet: 'material-icons', // Quasar icon set
|
||||
iconSet: 'material-icons', // Quasar icon set
|
||||
// lang: 'en-US', // Quasar language pack
|
||||
|
||||
// For special cases outside of where the auto-import strategy can have an impact
|
||||
|
||||
194
src/]
194
src/]
@@ -1,194 +0,0 @@
|
||||
<template>
|
||||
<div style="height:600px; width:800px">
|
||||
<q-toolbar class="bg-primary text-white q-my-md shadow-2">
|
||||
<q-btn flat round dense icon="menu" class="q-mr-sm" />
|
||||
<q-separator dark vertical inset />
|
||||
<q-btn stretch flat :icon="home.icon" @click="handleFavClick(home.coords)" />
|
||||
|
||||
<q-space />
|
||||
|
||||
<q-btn-dropdown stretch flat label="Favorites">
|
||||
<q-list>
|
||||
<q-item
|
||||
v-for="fav in favorites"
|
||||
:key="fav.id"
|
||||
clickable
|
||||
v-ripple
|
||||
v-close-popup
|
||||
@click="handleFavClick(fav.coords)"
|
||||
>
|
||||
<q-item-section avatar>
|
||||
<q-avatar :icon="fav.icon" color="primary" text-color="white" />
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label>{{ fav.name }}</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-btn-dropdown>
|
||||
</q-toolbar>
|
||||
<l-map @ready="onMapReady" ref="mapRef" :zoom="zoom" :center="center" style="height: 500px; width= 100%;" @click="updateMarker">
|
||||
<l-tile-layer
|
||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
layer-type="base"
|
||||
name="OpenStreetMap"
|
||||
@click="updateMarker($event.latlng)"
|
||||
></l-tile-layer>
|
||||
<l-marker :lat-lng="markerLatLng" @click="handleMarkerClick"></l-marker>
|
||||
</l-map>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useQuasar } from "quasar";
|
||||
import { Ref, ref } from "vue";
|
||||
import "leaflet/dist/leaflet.css";
|
||||
import { LMap, LTileLayer, LMarker } from "@vue-leaflet/vue-leaflet";
|
||||
import { GeoSearchControl, OpenStreetMapProvider } from 'leaflet-geosearch';
|
||||
import "leaflet-geosearch/dist/geosearch.css";
|
||||
import "leaflet/dist/leaflet.css";
|
||||
import type { Map, LeafletMouseEvent } from 'leaflet';
|
||||
import SetLocationnDialog from "components/SetLocationDialog.vue";
|
||||
import { api } from "src/boot/axios";
|
||||
|
||||
const $q = useQuasar();
|
||||
const mapRef = ref(null);
|
||||
const zoom = ref(10);
|
||||
const center: Ref<[number, number]> = ref([40.71278, -74.00594]);
|
||||
const markerLatLng: Ref<[number, number]> = ref([40.71278, -74.00594]);
|
||||
const responseMessage = ref("");
|
||||
|
||||
interface coords {
|
||||
lat: number;
|
||||
lng: number;
|
||||
}
|
||||
|
||||
interface SearchControlProps {
|
||||
provider: OpenStreetMapProvider;
|
||||
showMarker: boolean;
|
||||
autoClose: boolean;
|
||||
updateMap: boolean;
|
||||
showPopup: boolean;
|
||||
style: 'button' | 'bar';
|
||||
acceptAutoLoad: boolean;
|
||||
autoComplete: boolean;
|
||||
autoCompleteDelay: number;
|
||||
retainZoomLevel: boolean;
|
||||
animateZoom: boolean;
|
||||
keepResult: boolean;
|
||||
}
|
||||
const onMapReady = (map: Map) => {
|
||||
const provider = new OpenStreetMapProvider();
|
||||
const searchOptions: SearchControlProps = {
|
||||
provider: provider,
|
||||
showMarker: false,
|
||||
autoClose: true,
|
||||
updateMap: true,
|
||||
showPopup: true,
|
||||
style: 'bar',
|
||||
acceptAutoLoad: true,
|
||||
autoComplete: true,
|
||||
autoCompleteDelay: 250,
|
||||
retainZoomLevel: false,
|
||||
animateZoom: true,
|
||||
keepResult: true
|
||||
};
|
||||
|
||||
const searchControl = new GeoSearchControl(searchOptions);
|
||||
map.addControl(searchControl);
|
||||
};
|
||||
|
||||
function updateMarker(event: LeafletMouseEvent) {
|
||||
markerLatLng.value = [event.latlng.lat, event.latlng.lng];
|
||||
center.value = [event.latlng.lat, event.latlng.lng];
|
||||
}
|
||||
|
||||
function handleMarkerClick(event: LeafletMouseEvent) {
|
||||
$q.dialog({
|
||||
component: SetLocationnDialog,
|
||||
componentProps: {
|
||||
lat: event.latlng.lat,
|
||||
lng: event.latlng.lng
|
||||
}
|
||||
}).onOk(() => {
|
||||
console.log("Dialog confirmed");
|
||||
setLocation({ lat: event.latlng.lat, lng: event.latlng.lng });
|
||||
}).onCancel(() => {
|
||||
console.log("Dialog cancelled");
|
||||
}).onDismiss(() => {
|
||||
console.log("Dialog dismissed");
|
||||
});
|
||||
}
|
||||
|
||||
function handleFavClick(coords: coords) {
|
||||
center.value = [coords.lat, coords.lng];
|
||||
markerLatLng.value = [coords.lat, coords.lng];
|
||||
}
|
||||
|
||||
async function setLocation(coords: coords) {
|
||||
try {
|
||||
const payLoadData = {
|
||||
lat: coords.lat,
|
||||
lng: coords.lng
|
||||
};
|
||||
const { data } = await api({
|
||||
method: "post",
|
||||
url: "/set",
|
||||
data: payLoadData
|
||||
});
|
||||
console.log("Location set successfully:", data.data);
|
||||
responseMessage.value = `Location successfully set! New location: ${data.lat}, ${data.lng}`;
|
||||
$q.notify({ type: 'positive', message: responseMessage.value });
|
||||
} catch (error) {
|
||||
console.error("Error setting location:", error);
|
||||
responseMessage.value = `Failed to set location: ${error}`;
|
||||
$q.notify({ type: 'negative', message: responseMessage.value });
|
||||
}
|
||||
}
|
||||
|
||||
const home = {
|
||||
name: "Home",
|
||||
coords: {
|
||||
lat: 40.71278,
|
||||
lng: -74.00594
|
||||
},
|
||||
icon: "home"
|
||||
};
|
||||
|
||||
const favorites = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Central Park",
|
||||
coords: {
|
||||
lat: 40.785091,
|
||||
lng: -73.968285
|
||||
},
|
||||
icon: "park"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Times Square",
|
||||
coords: {
|
||||
lat: 40.758896,
|
||||
lng: -73.985130,
|
||||
},
|
||||
icon: "star"
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "Empire State Building",
|
||||
coords: {
|
||||
lat: 40.748817,
|
||||
lng: -73.985428
|
||||
},
|
||||
icon: "building"
|
||||
}
|
||||
];
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.l-map {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
||||
</style>
|
||||
@@ -1,58 +0,0 @@
|
||||
import { defineBoot } from '#q-app/wrappers';
|
||||
import type { Socket } from 'socket.io-client';
|
||||
import { io } from 'socket.io-client';
|
||||
import type { StatusUpdate } from 'src/types';
|
||||
|
||||
interface SimulationStatus {
|
||||
status: boolean;
|
||||
data: {
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
start: string;
|
||||
end?: string;
|
||||
next_move?: number;
|
||||
};
|
||||
}
|
||||
|
||||
interface SimulationRequest {
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
delay?: number;
|
||||
start?: string;
|
||||
end?: string;
|
||||
}
|
||||
|
||||
interface ServerToClientEvents {
|
||||
noArg: () => void;
|
||||
withAck: (d: string, callback: (e: number) => void) => void;
|
||||
status_update: (d: StatusUpdate) => void;
|
||||
simulation: (d: SimulationStatus) => void;
|
||||
}
|
||||
|
||||
interface ClientToServerEvents {
|
||||
message: (data: string) => void;
|
||||
connect: () => void;
|
||||
disconnect: () => void;
|
||||
set_location: (data: SimulationRequest, callback: (response: SimulationStatus) => void) => void;
|
||||
request_update: (callback: (response: { statusUpdate: StatusUpdate }) => void) => void;
|
||||
command: (
|
||||
command: string,
|
||||
callback: (response: { status: boolean; message?: string }) => void,
|
||||
) => void;
|
||||
shutdown: (
|
||||
delay: number,
|
||||
callback: (response: { success: boolean; message?: string }) => void,
|
||||
) => void;
|
||||
// ... other events
|
||||
}
|
||||
|
||||
const socket: Socket<ServerToClientEvents, ClientToServerEvents> = io('/', {
|
||||
autoConnect: true,
|
||||
transports: ['websocket'],
|
||||
});
|
||||
|
||||
export default defineBoot(({ app }) => {
|
||||
app.config.globalProperties.$socket = socket;
|
||||
});
|
||||
|
||||
export { socket };
|
||||
16
src/boot/socketio.ts
Normal file
16
src/boot/socketio.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { defineBoot } from '#q-app/wrappers';
|
||||
import type { Socket } from 'socket.io-client';
|
||||
import { io } from 'socket.io-client';
|
||||
import type { ServerToClientEvents, ClientToServerEvents } from 'components/models';
|
||||
|
||||
|
||||
const socket: Socket<ServerToClientEvents, ClientToServerEvents> = io('/', {
|
||||
autoConnect: true,
|
||||
transports: ['websocket', 'webtransport', 'polling'],
|
||||
});
|
||||
|
||||
export default defineBoot(({ app }) => {
|
||||
app.config.globalProperties.$socket = socket;
|
||||
});
|
||||
|
||||
export { socket };
|
||||
@@ -1,37 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<p>{{ title }}</p>
|
||||
<ul>
|
||||
<li v-for="todo in todos" :key="todo.id" @click="increment">
|
||||
{{ todo.id }} - {{ todo.content }}
|
||||
</li>
|
||||
</ul>
|
||||
<p>Count: {{ todoCount }} / {{ meta.totalCount }}</p>
|
||||
<p>Active: {{ active ? 'yes' : 'no' }}</p>
|
||||
<p>Clicks on todos: {{ clickCount }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import type { Todo, Meta } from './models';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
todos?: Todo[];
|
||||
meta: Meta;
|
||||
active: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
todos: () => [],
|
||||
});
|
||||
|
||||
const clickCount = ref(0);
|
||||
function increment() {
|
||||
clickCount.value += 1;
|
||||
return clickCount.value;
|
||||
}
|
||||
|
||||
const todoCount = computed(() => props.todos.length);
|
||||
</script>
|
||||
84
src/components/LRoutingMachine.vue
Normal file
84
src/components/LRoutingMachine.vue
Normal file
@@ -0,0 +1,84 @@
|
||||
<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 '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;
|
||||
}>();
|
||||
|
||||
// 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;
|
||||
|
||||
// State
|
||||
const ready = ref(false);
|
||||
const layer = ref<any>(null);
|
||||
|
||||
// Methods
|
||||
function add() {
|
||||
if (!props.mapObject) return;
|
||||
|
||||
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;
|
||||
|
||||
ready.value = true;
|
||||
}
|
||||
|
||||
// Watchers
|
||||
watch(
|
||||
() => props.mapObject,
|
||||
(val) => {
|
||||
if (!val) return;
|
||||
add();
|
||||
},
|
||||
);
|
||||
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
add();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (layer.value) {
|
||||
layer.value.remove();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div style="height: 600px; width: 800px">
|
||||
<l-map
|
||||
<div style="height: 600px; width: 800px; color: #000000">
|
||||
<L-Map
|
||||
@ready="onMapReady"
|
||||
ref="mapRef"
|
||||
:zoom="zoom"
|
||||
@@ -8,38 +8,57 @@
|
||||
style="height: 500px; width: 100%"
|
||||
@click="updateMarker"
|
||||
>
|
||||
<l-tile-layer
|
||||
<L-Tile-Layer
|
||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
layer-type="base"
|
||||
name="OpenStreetMap"
|
||||
@click="updateMarker($event.latlng)"
|
||||
></l-tile-layer>
|
||||
<l-marker :lat-lng="markerLatLng" @click="handleMarkerClick"></l-marker>
|
||||
</l-map>
|
||||
></L-Tile-Layer>
|
||||
<L-Marker :lat-lng="markerLatLng" @click="handleMarkerClick"></L-Marker>
|
||||
<L-Routing-Machine
|
||||
@routingstart="debugRoutingEvent"
|
||||
@routesfound="debugRoutingEvent"
|
||||
@routingerror="debugRoutingEvent"
|
||||
:waypoints="waypoints"
|
||||
/>
|
||||
</L-Map>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useQuasar } from "quasar";
|
||||
import { ref } from "vue";
|
||||
import "leaflet/dist/leaflet.css";
|
||||
import { LMap, LTileLayer, LMarker } from "@vue-leaflet/vue-leaflet";
|
||||
import { useQuasar } from 'quasar';
|
||||
import { ref } from 'vue';
|
||||
import { GeoSearchControl, OpenStreetMapProvider } from 'leaflet-geosearch';
|
||||
import "leaflet-geosearch/dist/geosearch.css";
|
||||
import "leaflet/dist/leaflet.css";
|
||||
import type { Map, LeafletMouseEvent } from 'leaflet';
|
||||
import { useLeafletStore } from "stores/leaflet";
|
||||
import SetLocationDialog from "components/SetLocationDialog.vue";
|
||||
import { api } from "boot/axios";
|
||||
import { storeToRefs } from 'pinia';
|
||||
import LRoutingMachine from 'components/LRoutingMachine.vue';
|
||||
import 'leaflet-geosearch/dist/geosearch.css';
|
||||
import 'leaflet/dist/leaflet.css';
|
||||
|
||||
import type { coords, SearchControlProps } from 'src/types';
|
||||
import type { Map, LeafletMouseEvent } 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 ],
|
||||
];
|
||||
|
||||
|
||||
const leafletStore = useLeafletStore();
|
||||
const { zoom, center, markerLatLng } = storeToRefs(leafletStore);
|
||||
const socketStore = useSocketioStore();
|
||||
const { zoom, center, markerLatLng } = storeToRefs(socketStore);
|
||||
const $q = useQuasar();
|
||||
const mapRef = ref(null);
|
||||
const responseMessage = ref("");
|
||||
const responseMessage = ref('');
|
||||
|
||||
const debugRoutingEvent = (event) => {
|
||||
console.log(`${event.type} event: `, event);
|
||||
};
|
||||
|
||||
const onMapReady = (map: Map) => {
|
||||
const provider = new OpenStreetMapProvider();
|
||||
@@ -55,7 +74,7 @@ const onMapReady = (map: Map) => {
|
||||
autoCompleteDelay: 250,
|
||||
retainZoomLevel: false,
|
||||
animateZoom: true,
|
||||
keepResult: true
|
||||
keepResult: true,
|
||||
};
|
||||
|
||||
const searchControl = GeoSearchControl(searchOptions);
|
||||
@@ -72,40 +91,38 @@ function handleMarkerClick(event: LeafletMouseEvent) {
|
||||
component: SetLocationDialog,
|
||||
componentProps: {
|
||||
lat: event.latlng.lat,
|
||||
lng: event.latlng.lng
|
||||
}
|
||||
}).onOk(() => {
|
||||
void setLocation({ lat: event.latlng.lat, lng: event.latlng.lng });
|
||||
console.log("Dialog confirmed");
|
||||
}).onCancel(() => {
|
||||
console.log("Dialog cancelled");
|
||||
}).onDismiss(() => {
|
||||
console.log("Dialog dismissed");
|
||||
});
|
||||
lng: event.latlng.lng,
|
||||
},
|
||||
})
|
||||
.onOk((delay: number) => {
|
||||
void setLocation({ lat: event.latlng.lat, lng: event.latlng.lng }, delay);
|
||||
console.log(
|
||||
'Confirmed location add: latitude: ' +
|
||||
event.latlng.lat +
|
||||
', longitude: ' +
|
||||
event.latlng.lng +
|
||||
', delay: ' +
|
||||
delay,
|
||||
);
|
||||
})
|
||||
.onCancel(() => {
|
||||
console.log('Dialog cancelled');
|
||||
})
|
||||
.onDismiss(() => {
|
||||
console.log('Dialog dismissed');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function setLocation(coords: coords) {
|
||||
function setLocation(coords: coords, delay: number) {
|
||||
try {
|
||||
const payLoadData = {
|
||||
latitude: coords.lat,
|
||||
longitude: coords.lng
|
||||
};
|
||||
const { data } = await api({
|
||||
method: "post",
|
||||
url: "/set",
|
||||
data: payLoadData
|
||||
});
|
||||
console.log("Location set successfully:", data.data);
|
||||
responseMessage.value = `Location successfully set! New location: ${data.lat}, ${data.lng}`;
|
||||
responseMessage.value = socketStore.simulationControl('add', delay, coords.lat, coords.lng);
|
||||
$q.notify({ type: 'positive', message: responseMessage.value });
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
console.error("Error setting location:", error.message);
|
||||
console.error('Error setting location:', error.message);
|
||||
responseMessage.value = `Failed to set location: ${error.message}`;
|
||||
} else {
|
||||
console.error("Error setting location:", error);
|
||||
console.error('Error setting location:', error);
|
||||
responseMessage.value = `Failed to set location: Unknown error`;
|
||||
}
|
||||
$q.notify({ type: 'negative', message: responseMessage.value });
|
||||
|
||||
@@ -1,106 +1,65 @@
|
||||
<script setup lang="ts">
|
||||
import { useLeafletStore } from 'stores/leaflet';
|
||||
import type { coords } from 'src/types';
|
||||
import type { Control, coords, CtrlAttr, CtrlAttrs } from 'components/models';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { socket } from 'boot/socket';
|
||||
import { socket } from 'boot/socketio';
|
||||
import ConfirmCommandDialog from 'components/ConfirmCommandDiaglog.vue';
|
||||
import { useQuasar } from 'quasar';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useSocketioStore } from 'stores/socketio';
|
||||
import { ref } from 'vue';
|
||||
import { favorites } from 'constants/favorites';
|
||||
import { controls } from 'constants/controls';
|
||||
|
||||
const route = useRoute();
|
||||
const $q = useQuasar();
|
||||
|
||||
const leafletStore = useLeafletStore();
|
||||
const socketStore = useSocketioStore();
|
||||
const { center, markerLatLng } = storeToRefs(leafletStore);
|
||||
|
||||
const home = {
|
||||
name: 'Home',
|
||||
coords: {
|
||||
lat: 40.71278,
|
||||
lng: -74.00594,
|
||||
},
|
||||
icon: 'home',
|
||||
};
|
||||
|
||||
interface Control {
|
||||
id: number;
|
||||
name: string;
|
||||
cmd: string;
|
||||
icon: string;
|
||||
confirm: boolean;
|
||||
}
|
||||
const { simulationRunning, simulationState, simulationQueueLength } = storeToRefs(socketStore);
|
||||
|
||||
const controls: Control[] = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Start Location Sim',
|
||||
cmd: 'start_location_simulation',
|
||||
icon: 'play_arrow',
|
||||
confirm: false,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'End Location Sim',
|
||||
cmd: 'end_location_simulation',
|
||||
icon: 'stop',
|
||||
confirm: true,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Shutdown',
|
||||
cmd: 'shutdown',
|
||||
icon: 'power_settings_new',
|
||||
confirm: true,
|
||||
},
|
||||
];
|
||||
const menuOpen = ref();
|
||||
|
||||
const favorites = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Central Park',
|
||||
coords: {
|
||||
lat: 40.785091,
|
||||
lng: -73.968285,
|
||||
},
|
||||
icon: 'park',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Times Square',
|
||||
coords: {
|
||||
lat: 40.758896,
|
||||
lng: -73.98513,
|
||||
},
|
||||
icon: 'star',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Empire State Building',
|
||||
coords: {
|
||||
lat: 40.748817,
|
||||
lng: -73.985428,
|
||||
},
|
||||
icon: 'building',
|
||||
},
|
||||
];
|
||||
|
||||
function handleFavClick(coords: coords) {
|
||||
center.value = [coords.lat, coords.lng];
|
||||
markerLatLng.value = [coords.lat, coords.lng];
|
||||
}
|
||||
|
||||
function handleControlClick(ctrl: Control) {
|
||||
if (ctrl.confirm) {
|
||||
function handleControlClick(cmdAttr: CtrlAttr) {
|
||||
if (cmdAttr.cnfrm) {
|
||||
$q.dialog({
|
||||
component: ConfirmCommandDialog,
|
||||
componentProps: {
|
||||
name: ctrl.name,
|
||||
name: cmdAttr.name,
|
||||
},
|
||||
})
|
||||
.onOk(() => {
|
||||
socket.emit('command', ctrl.cmd, (response) => {
|
||||
console.log(response.status);
|
||||
});
|
||||
if (cmdAttr.cmdClass === 'simulation_control') {
|
||||
try {
|
||||
const ack = socketStore.simulationControl(cmdAttr.cmd, cmdAttr.delay);
|
||||
$q.notify({ type: 'positive', message: ack });
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
console.error('Simulation Command ERROR: ', error.message);
|
||||
const ack = `Simulation Command Error: ${error.message}`;
|
||||
$q.notify({ type: 'negative', message: ack });
|
||||
} else {
|
||||
console.error('Simmulation Command Error: ', error);
|
||||
const ack = 'Simulation Command Error: Unknow error';
|
||||
$q.notify({ type: 'negative', message: ack });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ctrl.cmdClass === 'device_control') {
|
||||
socket.emit('device_control', { command: ctrl.cmd, delay: 0 }, (response) => {
|
||||
console.log(response.status, response.command);
|
||||
});
|
||||
}
|
||||
})
|
||||
.onCancel(() => {
|
||||
console.log('Dialog cancelled');
|
||||
@@ -109,9 +68,25 @@ function handleControlClick(ctrl: Control) {
|
||||
console.log('Dialog dismissed');
|
||||
});
|
||||
} else {
|
||||
socket.emit('command', ctrl.cmd, (response) => {
|
||||
console.log(response.status);
|
||||
});
|
||||
if (ctrl.cmdClass === 'simulation_control') {
|
||||
try {
|
||||
const response = socketStore.simulationControl(ctrl.cmd);
|
||||
$q.notify({ type: 'positive', message: response });
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
console.error('Error: ' + error.message);
|
||||
$q.notify({ type: 'negative', message: error.toString() });
|
||||
} else {
|
||||
console.error('Error setting location:', error);
|
||||
$q.notify({ type: 'negative', message: error.toString() });
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ctrl.cmdClass === 'device_control') {
|
||||
socket.emit('device_control', { command: ctrl.cmd, delay: 0 }, (response) => {
|
||||
console.log(response.status, response.command);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -119,49 +94,148 @@ function handleControlClick(ctrl: Control) {
|
||||
<template>
|
||||
<q-toolbar class="bg-primary text-white">
|
||||
<q-btn @click="$emit('drawer')" flat round dense icon="menu" class="q-mr-sm" />
|
||||
<q-separator dark vertical inset />
|
||||
<q-separator dark inset />
|
||||
<q-space />
|
||||
<q-btn
|
||||
stretch
|
||||
flat
|
||||
:icon="home.icon"
|
||||
@click="handleFavClick(home.coords)"
|
||||
v-if="route.name === 'Leaflet'"
|
||||
/>
|
||||
<q-btn-dropdown stretch flat label="Favorites" v-if="route.name === 'Leaflet'">
|
||||
<q-list>
|
||||
<q-item
|
||||
v-for="fav in favorites"
|
||||
:key="fav.id"
|
||||
clickable
|
||||
v-ripple
|
||||
v-close-popup
|
||||
@click="handleFavClick(fav.coords)"
|
||||
>
|
||||
<q-item-section avatar>
|
||||
<q-avatar :icon="fav.icon" color="primary" text-color="white" />
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label>{{ fav.name }}</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-btn-dropdown>
|
||||
<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">
|
||||
<q-item
|
||||
v-if="fav.coords"
|
||||
clickable
|
||||
v-ripple
|
||||
v-close-popup
|
||||
@click="handleFavClick(fav.coords)"
|
||||
>
|
||||
<q-item-section avatar>
|
||||
<q-avatar :icon="fav.icon" color="primary" text-color="white" />
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label>{{ fav.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-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label>{{ fav.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-list>
|
||||
<q-item
|
||||
v-for="(f, indx) in fav.subitems"
|
||||
:key="indx"
|
||||
clickable
|
||||
v-ripple
|
||||
v-close-popup
|
||||
@click="handleFavClick(f.coords)"
|
||||
>
|
||||
<q-item-section avatar>
|
||||
<q-avatar :icon="f.icon" color="primary" text-color="white" />
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label>{{ f.name }}</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-menu>
|
||||
</q-item>
|
||||
</template>
|
||||
</q-list>
|
||||
</q-menu>
|
||||
</q-btn>
|
||||
<q-btn-dropdown stretch flat label="Controls">
|
||||
<q-list>
|
||||
<q-item-label header>Simulation Controls</q-item-label>
|
||||
<q-item
|
||||
v-for="ctrl in controls"
|
||||
:key="ctrl.id"
|
||||
v-if="!simulationRunning"
|
||||
clickable
|
||||
v-ripple
|
||||
v-close-popup
|
||||
@click="handleControlClick(ctrl)"
|
||||
>
|
||||
@click="handleControlClick(controls.sim_start)">
|
||||
<q-item-section avatar>
|
||||
<q-avatar :icon="ctrl.icon" color="primary" text-color="white" />
|
||||
<q-avatar :icon="controls.sim_start.icon" color="primary" text-color="white" />
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label>{{ ctrl.name }}</q-item-label>
|
||||
<q-item-label> {{ controls.sim_start.name }} </q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item
|
||||
v-if="simulationState === 'RUNNING' && simulationRunning"
|
||||
clickable
|
||||
v-ripple
|
||||
v-close-popup
|
||||
@click="handleControlClick(controls.sim_pause)"
|
||||
>
|
||||
<q-item-section avatar>
|
||||
<q-avatar :icon="controls.sim_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-section>
|
||||
</q-item>
|
||||
<q-item
|
||||
v-if="simulationState === 'PAUSED'"
|
||||
clickable
|
||||
v-ripple
|
||||
v-close-popup
|
||||
@click="handleControlClick(controls.sim_resume)"
|
||||
>
|
||||
<q-item-section avatar>
|
||||
<q-avatar :icon="controls.sim_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-section>
|
||||
</q-item>
|
||||
<q-item
|
||||
v-if="simulationQueueLength > 0"
|
||||
clickable
|
||||
v-ripple
|
||||
v-close-popup
|
||||
@click="handleControlClick(controls.sim_clear)"
|
||||
>
|
||||
<q-item-section avatar>
|
||||
<q-avatar :icon="controls.sim_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-section>
|
||||
</q-item>
|
||||
<q-item
|
||||
v-if="simulationRunning"
|
||||
clickable
|
||||
v-ripple
|
||||
v-close-popup
|
||||
@click="handleControlClick(controls.sim_end)"
|
||||
>
|
||||
<q-item-section avatar>
|
||||
<q-avatar :icon="controls.sim_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-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-section avatar>
|
||||
<q-avatar :icon="controls.dev_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-section>
|
||||
</q-item>
|
||||
<q-item clickable v-ripple v-close-popup @click="handleControlClick(controls.dev_shutdown)">
|
||||
<q-item-section avatar>
|
||||
<q-avatar :icon="controls.dev_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-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
|
||||
@@ -3,9 +3,13 @@
|
||||
<q-card>
|
||||
<q-card-section class="row items-center">
|
||||
<q-avatar icon="add_location" color="primary" text-color="white" />
|
||||
<span class="text-h6"> Add Simulated Location to Queue</span>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<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-card-section>
|
||||
<q-card-actions align="right">
|
||||
<q-btn flat label="OK" color="primary" @click="onOkClick" />
|
||||
@@ -17,12 +21,14 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useDialogPluginComponent } from 'quasar';
|
||||
import { ref } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
lat: { type: Number, required: true },
|
||||
lng: { type: Number, required: true },
|
||||
});
|
||||
|
||||
const delay = ref(0);
|
||||
const latitude = props.lat;
|
||||
const longitude = props.lng;
|
||||
|
||||
|
||||
1
src/components/SimulationCommands.ts
Normal file
1
src/components/SimulationCommands.ts
Normal file
@@ -0,0 +1 @@
|
||||
import { socket } from 'boot/socketio';
|
||||
@@ -1,23 +1,76 @@
|
||||
<script setup lang="ts">
|
||||
import { socket } from 'boot/socket';
|
||||
import { useStatusStore } from 'stores/status';
|
||||
import { computed } from 'vue';
|
||||
import { socket } from 'boot/socketio';
|
||||
import { useQuasar } from 'quasar';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { useSocketioStore } from 'stores/socketio';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
const statusStore = useStatusStore();
|
||||
const socketioStore = useSocketioStore();
|
||||
const $q = useQuasar();
|
||||
const msgInput = ref('');
|
||||
const sockEvent = ref('');
|
||||
const eventArgs = ref('');
|
||||
|
||||
const socketStatus = computed(() => statusStore.statusList.socketConnected ? 'green' : 'red');
|
||||
const { sockConnected, messageList } = storeToRefs(socketioStore);
|
||||
|
||||
socket.off();
|
||||
statusStore.socketConnect();
|
||||
statusStore.bindEvents();
|
||||
const sockStatColor = computed(() => {
|
||||
return sockConnected.value ? 'green' : 'red';
|
||||
});
|
||||
|
||||
|
||||
function sendMessage() {
|
||||
const msgToSend = msgInput.value;
|
||||
console.log('Sending message... ' + msgToSend);
|
||||
socket.emit('message', msgToSend, (e, r) => {
|
||||
console.log('Message: ' + msgToSend + ' delivered: ' + e + ', response: ' + r);
|
||||
});
|
||||
msgInput.value = '';
|
||||
}
|
||||
|
||||
function handleEmit() {
|
||||
const event = sockEvent.value;
|
||||
const jsonArgs = eventArgs.value;
|
||||
socket.emit(event, jsonArgs, (resp) => {
|
||||
console.log('Server Reponse: ' + resp);
|
||||
});
|
||||
sockEvent.value = '';
|
||||
eventArgs.value = '';
|
||||
};
|
||||
|
||||
watch(
|
||||
messageList,
|
||||
(newVal: string[], oldVal: string[]) => {
|
||||
let newMsg: string;
|
||||
newMsg = newVal[newVal.length - 1];
|
||||
console.log('New message received: ', newMsg);
|
||||
console.log('Past List', oldVal);
|
||||
$q.notify(newMsg);
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
</script>
|
||||
<template>
|
||||
<div class="flex flex-center col q-ma-lg q-gutter-md">
|
||||
<q-btn round icon="webhook">
|
||||
<q-badge floating :color="socketStatus" rounded />
|
||||
</q-btn>
|
||||
<q-btn label="Connect" @click="statusStore.socketConnect" />
|
||||
<q-btn label="Disconnect" @click="statusStore.socketDisconnect" />
|
||||
<q-btn label="Emit Hello" @click="socket.emit('message', 'Hello from Vue!')" />
|
||||
<q-btn icom="webhook"><q-badge floating :color="sockStatColor" rounded></q-badge></q-btn>
|
||||
<q-btn label="Connect" @click="socketioStore.connect()" />
|
||||
<q-btn label="Disconnect" @click="socketioStore.disconnect()" />
|
||||
<q-input v-model="msgInput" filled label="Message" />
|
||||
<div>
|
||||
<q-btn label="Send" @click="sendMessage" />
|
||||
</div>
|
||||
<q-list bordered seperator v-if="socketioStore.messageList.length > 0">
|
||||
<q-item v-for="(msg, index) in socketioStore.messageList" :key="index">
|
||||
<q-item-section>{{ msg }}</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</div>
|
||||
<div class="flex flex-center col q-ma-lg q-gutter-md">
|
||||
<div class="text-h3">SocketIO Functions</div>
|
||||
<q-input v-model="sockEvent" filled label="Event" />
|
||||
<q-input v-model="eventArgs" filled label="Args" />
|
||||
<div>
|
||||
<q-btn label="Emit" @click="handleEmit" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
@@ -1,32 +1,53 @@
|
||||
<script setup lang="ts">
|
||||
import { useStatusStore } from 'stores/status';
|
||||
import { useSocketioStore } from 'stores/socketio';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
const statusStore = useStatusStore();
|
||||
const statusList = statusStore.statusList;
|
||||
|
||||
function statusDevColor(state: boolean): string {
|
||||
return state ? 'green' : 'red';
|
||||
const socketioStore = useSocketioStore();
|
||||
const {
|
||||
sockConnected,
|
||||
deviceConnected,
|
||||
tunnelConnected,
|
||||
simulationRunning,
|
||||
currentLocation,
|
||||
nextLocation,
|
||||
} = storeToRefs(socketioStore);
|
||||
function statusDevColor(state: string | boolean): string {
|
||||
if (typeof state === 'boolean') {
|
||||
return state ? 'green' : 'red';
|
||||
} else {
|
||||
switch (state) {
|
||||
case 'paused':
|
||||
return 'yellow';
|
||||
case 'running':
|
||||
return 'green';
|
||||
case 'stopped':
|
||||
return 'red';
|
||||
default:
|
||||
return 'grey';
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<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(statusList.socketConnected)" rounded class="q-mr-sm" />
|
||||
WebSocket
|
||||
<q-badge :color="statusDevColor(sockConnected)" rounded class="q-mr-sm" />
|
||||
<q-btn label="WebSocket" @click="socketioStore.toggleSock()" />
|
||||
</span>
|
||||
<span>
|
||||
<q-badge :color="statusDevColor(statusList.deviceConnected)" rounded class="q-mr-sm" />
|
||||
<q-badge :color="statusDevColor(deviceConnected)" rounded class="q-mr-sm" />
|
||||
Device Connection
|
||||
</span>
|
||||
<span>
|
||||
<q-badge :color="statusDevColor(statusList.tunnelConnected)" rounded class="q-mr-sm" />
|
||||
<q-badge :color="statusDevColor(tunnelConnected)" rounded class="q-mr-sm" />
|
||||
tunneld
|
||||
</span>
|
||||
<span>
|
||||
<q-badge :color="statusDevColor(statusList.simulationRunning)" rounded class="q-mr-sm" />
|
||||
<q-badge :color="statusDevColor(simulationRunning)" rounded class="q-mr-sm" />
|
||||
Location Simulation
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,201 @@
|
||||
export interface Todo {
|
||||
id: number;
|
||||
content: string;
|
||||
|
||||
export type SimulationCommands = "start" | "pause" | "resume" | "clear" | "end" | "add";
|
||||
|
||||
export type DeviceCommands= "start_tunnel" | "stop_tunnel" | "shutdown";
|
||||
|
||||
export interface CtrlAttrs {
|
||||
[key: string]: CtrlAttr;
|
||||
}
|
||||
|
||||
export interface CtrlAttr {
|
||||
name: string;
|
||||
cmd: string;
|
||||
cmdClass: string;
|
||||
icon: string;
|
||||
cnfrm: boolean;
|
||||
delay: number;
|
||||
}
|
||||
|
||||
export interface LocationQueue {
|
||||
[key: string]: LocationMark
|
||||
}
|
||||
|
||||
interface LocationMark {
|
||||
loc_id: string;
|
||||
latitude: number | undefined | null;
|
||||
longitude: number | undefined | null;
|
||||
delay?: number | undefined | null;
|
||||
start_time: string | undefined | null;
|
||||
end_time?: string | undefined | null ;
|
||||
}
|
||||
|
||||
export interface SimulationControlResponse {
|
||||
status: string;
|
||||
command: SimulationCommands;
|
||||
loc_id: string;
|
||||
message?: string | undefined;
|
||||
latitude?: number | undefined | null;
|
||||
longitude?: number | undefined | null;
|
||||
delay?: number | undefined | null;
|
||||
start_time?: string | undefined | null;
|
||||
end_time?: string | undefined | null;
|
||||
}
|
||||
|
||||
interface DeviceControlResponse {
|
||||
status: string;
|
||||
command: DeviceCommands;
|
||||
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 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;
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
import type { OpenStreetMapProvider } from 'leaflet-geosearch';
|
||||
export interface SearchControlProps {
|
||||
provider: OpenStreetMapProvider;
|
||||
showMarker: boolean;
|
||||
autoClose: boolean;
|
||||
updateMap: boolean;
|
||||
showPopup: boolean;
|
||||
style: 'button' | 'bar';
|
||||
acceptAutoLoad: boolean;
|
||||
autoComplete: boolean;
|
||||
autoCompleteDelay: number;
|
||||
retainZoomLevel: boolean;
|
||||
animateZoom: boolean;
|
||||
keepResult: boolean;
|
||||
}
|
||||
|
||||
export interface CurrentLocation {
|
||||
loc_id: string;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
next_move?: number | null
|
||||
}
|
||||
|
||||
export interface NextLocation {
|
||||
loc_id: string;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
time_at_location?: number | null;
|
||||
}
|
||||
|
||||
export interface ErrorFull {
|
||||
type: string;
|
||||
error: string;
|
||||
}
|
||||
|
||||
62
src/constants/controls.ts
Normal file
62
src/constants/controls.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import type { CtrlAttrs } from 'components/models';
|
||||
|
||||
export const controls: CtrlAttrs = {
|
||||
sim_start: {
|
||||
name: 'Start Location Sim',
|
||||
cmd: 'start',
|
||||
cmdClass: 'simulation_control',
|
||||
icon: 'play_arrow',
|
||||
cnfrm: false,
|
||||
delay: 0,
|
||||
},
|
||||
sim_pause: {
|
||||
name: 'Pause Location Sim',
|
||||
cmd: 'pause',
|
||||
cmdClass: 'simulation_control',
|
||||
icon: 'pause',
|
||||
cnfrm: false,
|
||||
delay: 0,
|
||||
},
|
||||
sim_resume: {
|
||||
name: 'Resume Location Simulation',
|
||||
cmd: 'resume',
|
||||
cmdClass: 'simulation_control',
|
||||
icon: 'play_arrow',
|
||||
cnfrm: false,
|
||||
delay: 0,
|
||||
},
|
||||
sim_clear: {
|
||||
name: 'Clear Location Queue',
|
||||
cmd: 'clear',
|
||||
cmdClass: 'simulation_control',
|
||||
icon: 'directions_off',
|
||||
cnfrm: false,
|
||||
delay: 0,
|
||||
},
|
||||
sim_end: {
|
||||
name: 'End Location Sim',
|
||||
cmd: 'end',
|
||||
cmdClass: 'simulation_control',
|
||||
icon: 'stop',
|
||||
cnfrm: true,
|
||||
delay: 0,
|
||||
},
|
||||
dev_shutdown: {
|
||||
name: 'Shutdown',
|
||||
cmd: 'shutdown',
|
||||
cmdClass: 'device_control',
|
||||
icon: 'power_settings_new',
|
||||
cnfrm: true,
|
||||
delay: 5,
|
||||
},
|
||||
dev_reboot: {
|
||||
name: 'Reboot',
|
||||
cmd: 'reboot',
|
||||
cmdClass: 'device_control',
|
||||
icon: 'restart_alt',
|
||||
cnfrm: true,
|
||||
delay: 5,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
79
src/constants/favorites.ts
Normal file
79
src/constants/favorites.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
export const favorites = [
|
||||
{
|
||||
name: 'Home',
|
||||
icon: 'home',
|
||||
coords: {
|
||||
lat: 40.910773020811,
|
||||
lng: -73.891069806448,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Work Places",
|
||||
icon: "work",
|
||||
subitems: [
|
||||
{
|
||||
name: 'Jeong',
|
||||
icon: 'language_korean_latin',
|
||||
coords: {
|
||||
lat: 40.76624975651346,
|
||||
lng: -73.81444335286128,
|
||||
},
|
||||
address: '35-02 150th Pl, Flushing, NY 11354',
|
||||
},
|
||||
{
|
||||
name: 'Santos',
|
||||
icon: 'rice_bowl',
|
||||
coords: {
|
||||
lat: 40.74504671877868,
|
||||
lng: -73.8880099638491,
|
||||
},
|
||||
address: '77-08 Broadway, Elmhurst, NY 11373'
|
||||
},
|
||||
{
|
||||
name: 'Natalyaa (Qns)',
|
||||
icon: 'currency_ruble',
|
||||
coords: {
|
||||
lat: 40.69644966409178,
|
||||
lng: -73.837453217826,
|
||||
},
|
||||
address: '110-14 Jamaica Ave, Richmond Hill, NY 11418',
|
||||
},
|
||||
{
|
||||
name: 'Natalyaa (Bronx)',
|
||||
icon: 'currency_ruble',
|
||||
coords: {
|
||||
lat: 40.85384419116598,
|
||||
lng: -73.86314767911834,
|
||||
},
|
||||
address: '2109 Matthews Ave, Bronx, NY 10462',
|
||||
},
|
||||
{
|
||||
name: 'Linwood Plaza',
|
||||
icon: 'dermatology',
|
||||
coords: {
|
||||
lat: 40.86141832913106,
|
||||
lng: -73.96997583196286,
|
||||
},
|
||||
address: '158 Linwood Plaza, Fort Lee, NJ 07024',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Man Mini Storage',
|
||||
icon: 'box',
|
||||
coords: {
|
||||
lat: 40.75158955085288,
|
||||
lng: -73.9328988710467,
|
||||
},
|
||||
address: '31-08 Northern Blvd, Long Island City, NY 11101',
|
||||
},
|
||||
{
|
||||
name: 'Acmd',
|
||||
icon: 'grocery',
|
||||
coords: {
|
||||
lat: 40.90930366920829,
|
||||
lng: -73.87658695470259,
|
||||
},
|
||||
address: '31-08 Northern Blvd, Long Island City, NY 11101',
|
||||
},
|
||||
];
|
||||
89
src/functions/routingControl.ts
Normal file
89
src/functions/routingControl.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { Utilities } from "@vue-leaflet/vue-leaflet";
|
||||
import type * as L from "leaflet";
|
||||
import type { IRouter, IGeocoder, LineOptions } from "leaflet-routing-machine";
|
||||
|
||||
// Props typing
|
||||
export interface RoutingControlProps {
|
||||
waypoints: L.LatLng[];
|
||||
router?: IRouter;
|
||||
plan?: any; // L.Routing.Plan (can refine if you typed it)
|
||||
fitSelectedRoutes?: string | boolean;
|
||||
lineOptions?: LineOptions;
|
||||
routeLine?: (...args: any[]) => any;
|
||||
autoRoute?: boolean;
|
||||
routeWhileDragging?: boolean;
|
||||
routeDragInterval?: number;
|
||||
waypointMode?: string;
|
||||
useZoomParameter?: boolean;
|
||||
showAlternatives?: boolean;
|
||||
altLineOptions?: LineOptions;
|
||||
}
|
||||
|
||||
// Vue-style prop definitions (still needed for runtime)
|
||||
export const routingControlProps = {
|
||||
waypoints: {
|
||||
type: Array as () => L.LatLng[],
|
||||
default: () => [],
|
||||
},
|
||||
router: {
|
||||
type: Object as () => IRouter,
|
||||
default: undefined,
|
||||
},
|
||||
plan: {
|
||||
type: Object as () => any,
|
||||
default: undefined,
|
||||
},
|
||||
fitSelectedRoutes: {
|
||||
type: [String, Boolean] as () => string | boolean,
|
||||
default: "smart",
|
||||
},
|
||||
lineOptions: {
|
||||
type: Object as () => LineOptions,
|
||||
default: undefined,
|
||||
},
|
||||
routeLine: {
|
||||
type: Function as () => (...args: any[]) => any,
|
||||
default: undefined,
|
||||
},
|
||||
autoRoute: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
routeWhileDragging: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
routeDragInterval: {
|
||||
type: Number,
|
||||
default: 500,
|
||||
},
|
||||
waypointMode: {
|
||||
type: String,
|
||||
default: "connect",
|
||||
},
|
||||
useZoomParameter: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showAlternatives: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
altLineOptions: {
|
||||
type: Object as () => LineOptions,
|
||||
default: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
// Setup function
|
||||
export const setupRoutingControl = (props: RoutingControlProps) => {
|
||||
const options = Utilities.propsToLeafletOptions(
|
||||
props,
|
||||
routingControlProps
|
||||
);
|
||||
|
||||
return {
|
||||
options,
|
||||
methods: {} as Record<string, never>,
|
||||
};
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<q-layout view="hHh Lpr fff">
|
||||
<q-layout view="hhh lpr fff">
|
||||
<q-header>
|
||||
<MenuBar @drawer="drawer = !drawer" />
|
||||
</q-header>
|
||||
@@ -16,7 +16,12 @@
|
||||
<q-scroll-area class="fit">
|
||||
<q-list>
|
||||
<template v-for="(menuItem, index) in menuList" :key="index">
|
||||
<q-item clickable :active="menuItem.route === route.name" v-ripple @click="$router.push({ name: menuItem.route })">
|
||||
<q-item
|
||||
clickable
|
||||
:active="menuItem.route === route.name"
|
||||
v-ripple
|
||||
@click="$router.push({ name: menuItem.route })"
|
||||
>
|
||||
<q-item-section avatar>
|
||||
<q-icon :name="menuItem.icon" />
|
||||
</q-item-section>
|
||||
@@ -36,12 +41,16 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import MenuBar from 'components/MenuBar.vue';
|
||||
import StatusBar from 'components/StatusBar.vue';
|
||||
import { ref } from 'vue';
|
||||
const drawer = ref(false);
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
import { useSocketioStore } from 'stores/socketio';
|
||||
|
||||
import MenuBar from 'components/MenuBar.vue';
|
||||
import StatusBar from 'components/StatusBar.vue';
|
||||
|
||||
const socketioStore = useSocketioStore();
|
||||
const drawer = ref(false);
|
||||
const route = useRoute();
|
||||
|
||||
const menuList = [
|
||||
@@ -52,10 +61,10 @@ const menuList = [
|
||||
route: 'Leaflet',
|
||||
},
|
||||
{
|
||||
icon: 'report_problem',
|
||||
label: 'Test',
|
||||
icon: 'info',
|
||||
label: 'Device Info',
|
||||
separator: false,
|
||||
route: 'Test',
|
||||
route: 'DeviceInfo',
|
||||
},
|
||||
{
|
||||
icon: 'inbox',
|
||||
@@ -101,4 +110,8 @@ const menuList = [
|
||||
route: 'ErrorNotFound',
|
||||
},
|
||||
];
|
||||
onMounted(() => {
|
||||
socketioStore.bindEvents();
|
||||
socketioStore.connect();
|
||||
});
|
||||
</script>
|
||||
|
||||
59
src/pages/DeviceInfo.vue
Normal file
59
src/pages/DeviceInfo.vue
Normal file
@@ -0,0 +1,59 @@
|
||||
<script setup lang="ts">
|
||||
import { api, axios } from 'boot/axios';
|
||||
import { ref } from 'vue';
|
||||
|
||||
const deviceInfo = ref();
|
||||
|
||||
async function fetchDeviceInfo(): Promise<void> {
|
||||
try {
|
||||
const res = await api.get('/device_info');
|
||||
console.log('DeviceInfo fetched successfully:', res.data);
|
||||
deviceInfo.value = res.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching device info:', error);
|
||||
if (axios.isCancel(error)) {
|
||||
console.log('Request canceled', error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
await fetchDeviceInfo();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-page class="row items-center justify-evenly">
|
||||
<div style="width: 70vw; white-space: normal">
|
||||
<h3>Device Info:</h3>
|
||||
<ul>
|
||||
<li v-for="(value, key) in deviceInfo" :key="key">
|
||||
<strong>{{ key }}:</strong>
|
||||
<span v-if="typeof value === 'object' && value !== null">
|
||||
<ul>
|
||||
<li v-for="(subValue, subKey) in value" :key="subKey">
|
||||
<strong>{{ subKey }}:</strong>
|
||||
<span v-if="typeof subValue === 'object' && subValue !== null">
|
||||
<ul>
|
||||
<li v-for="(subSubValue, subSubKey) in subValue" :key="subSubKey">
|
||||
<strong>{{ subSubKey }}:</strong>
|
||||
<span v-if="typeof subSubValue === 'object' && subSubValue !== null">
|
||||
<ul>
|
||||
<li v-for="(subSubSubValue, subSubSubKey) in subSubValue" :key="subSubSubKey">
|
||||
<strong>{{ subSubSubKey }}:</strong> {{ subSubSubValue }}
|
||||
</li>
|
||||
</ul>
|
||||
</span>
|
||||
<span v-else>{{ subSubValue }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</span>
|
||||
<span v-else>{{ subValue }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</span>
|
||||
<span v-else>{{ value }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</q-page>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -22,6 +22,11 @@ const routes: RouteRecordRaw[] = [
|
||||
name: 'Test',
|
||||
component: () => import('pages/TestPage.vue')
|
||||
},
|
||||
{
|
||||
path: 'device-info',
|
||||
name: 'DeviceInfo',
|
||||
component: () => import('pages/DeviceInfo.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
|
||||
233
src/stores/socketio.ts
Normal file
233
src/stores/socketio.ts
Normal file
@@ -0,0 +1,233 @@
|
||||
import { defineStore, acceptHMRUpdate } from 'pinia';
|
||||
import { socket } from 'boot/socketio';
|
||||
import type {
|
||||
CurrentLocation,
|
||||
NextLocation,
|
||||
ErrorFull,
|
||||
SimulationControl,
|
||||
SimulationCommands,
|
||||
LocationQueue,
|
||||
SimulationControlResponse,
|
||||
StatusUpdate
|
||||
} from 'components/models';
|
||||
|
||||
|
||||
|
||||
|
||||
export const useSocketioStore = defineStore('socketio', {
|
||||
state: () => {
|
||||
return {
|
||||
sockConnected: false as boolean,
|
||||
socketID: null as string | null | undefined,
|
||||
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,
|
||||
nextLocation: null as NextLocation | null,
|
||||
messageList: [''] as string[],
|
||||
errorList: [] as ErrorFull[],
|
||||
locationQueue: [] as LocationQueue[],
|
||||
leafLetZoom: 10 as number,
|
||||
|
||||
};
|
||||
},
|
||||
getters: {
|
||||
sockState: (state) => state.sockConnected,
|
||||
deviceState: (state) => state.deviceConnected,
|
||||
markerLatLng: (state) => {
|
||||
return [state.currentLocation.latitude, state.currentLocation.longitude]
|
||||
},
|
||||
center(): [number, number] {
|
||||
return this.leafletCurrentMarker
|
||||
},
|
||||
zoom: (state) => state.leafletZoom,
|
||||
},
|
||||
actions: {
|
||||
setSockStatus() {
|
||||
this.sockConnected = socket.connected;
|
||||
this.socketID = socket.id;
|
||||
},
|
||||
bindEvents() {
|
||||
this.setSockStatus();
|
||||
socket.on('connect', () => {
|
||||
this.setSockStatus();
|
||||
socket.emit('message', 'Hello from client', (e: boolean) => {
|
||||
console.log('Message delivered: ' + e);
|
||||
});
|
||||
console.log('Connected to server');
|
||||
});
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
this.setSockStatus();
|
||||
console.log('Disconnected from server');
|
||||
});
|
||||
|
||||
socket.on('message', (e: string) => {
|
||||
this.setSockStatus();
|
||||
this.messageList.push(e);
|
||||
console.log('Websock message received!');
|
||||
});
|
||||
|
||||
socket.on('error', (data: ErrorFull) => {
|
||||
this.setSockStatus();
|
||||
const errorFull = { type: data.type, error: data.error };
|
||||
this.errorList.push(errorFull);
|
||||
});
|
||||
|
||||
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 };
|
||||
});
|
||||
|
||||
socket.onAny((eventName, ...args) => {
|
||||
this.setSockStatus();
|
||||
console.log(`Received event: ${eventName}`, args);
|
||||
});
|
||||
},
|
||||
connect() {
|
||||
console.log('Connecting to server...');
|
||||
socket.connect();
|
||||
this.setSockStatus();
|
||||
},
|
||||
disconnect() {
|
||||
socket.disconnect();
|
||||
this.setSockStatus();
|
||||
},
|
||||
toggleSock() {
|
||||
this.setSockStatus();
|
||||
if (this.sockConnected) {
|
||||
socket.disconnect();
|
||||
} else {
|
||||
socket.connect();
|
||||
}
|
||||
this.setSockStatus();
|
||||
},
|
||||
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 {
|
||||
this.setSockStatus();
|
||||
switch (command) {
|
||||
case 'start':
|
||||
console.log("socketStore: got command: start");
|
||||
if (this.simulationRunning || this.simulationState == "RUNNING" || this.simulationState == "PAUSED") {
|
||||
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") {
|
||||
throw new Error(response.message);
|
||||
} else {
|
||||
this.simulationState = response.status;
|
||||
console.log(response.message);
|
||||
return response.message;
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 'pause':
|
||||
if (this.simulationState !== "RUNNING" ) {
|
||||
throw new Error('Simulation is not running');
|
||||
}
|
||||
socket.emit('simulation_control', { command: 'pause' }, (response: SimulationControlResponse) => {
|
||||
if (response.status === "error") {
|
||||
throw new Error(response.message);
|
||||
} else {
|
||||
this.simulationState = response.status;
|
||||
console.log(response.message);
|
||||
return response.message;
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 'resume':
|
||||
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)
|
||||
} else {
|
||||
this.simulationState = response.status;
|
||||
console.log(response.message)
|
||||
return response.message
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 'clear':
|
||||
if (this.simulationQueueLength== 0) {
|
||||
throw new Error('Simulation queue is empty');
|
||||
}
|
||||
if (this.simulationState == "STOPPED " || !this.simulationRunning) {
|
||||
throw new Error('Simulation is not running');
|
||||
}
|
||||
socket.emit('simulation_control', { command: 'clear' }, (response) => {
|
||||
if (response.status == 'error') {
|
||||
throw new Error(response.message);
|
||||
} else {
|
||||
this.simulationState = response.status;
|
||||
console.log(response.message);
|
||||
return response.message;
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 'end':
|
||||
if (this.simulationState == "ENDED" || !this.simulationRunning) {
|
||||
throw new Error('Simulation is already ended');
|
||||
}
|
||||
socket.emit('simulation_control', { command: 'end' }, (response) => {
|
||||
if (response.status == 'error') {
|
||||
throw new Error(response.message);
|
||||
} else {
|
||||
this.simulationState = response.status;
|
||||
console.log(response.message);
|
||||
return response.message;
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 'add':
|
||||
if (this.simulationState == "ENDED" || !this.simulationRunning) {
|
||||
throw new Error('Simulation is not running');
|
||||
}
|
||||
if (!latitude || !longitude) {
|
||||
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)
|
||||
} else {
|
||||
this.simulationState = response.status;
|
||||
console.log("response from simulate_control_add: ", response);
|
||||
const locMrk = {
|
||||
[response.loc_id]: {
|
||||
loc_id: response.loc_id,
|
||||
latitude: response.latitude,
|
||||
longitude: response.longitude,
|
||||
delay: response.delay,
|
||||
start_time: response.start_time,
|
||||
},
|
||||
};
|
||||
this.locationQueue.push(locMrk);
|
||||
return response.message;
|
||||
}
|
||||
});
|
||||
break;
|
||||
default:
|
||||
throw new Error('Invalid command');
|
||||
}
|
||||
},
|
||||
setDeviceState(state: boolean) {
|
||||
this.deviceConnected = state;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.accept(acceptHMRUpdate(useSocketioStore, import.meta.hot));
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import { acceptHMRUpdate, defineStore } from 'pinia';
|
||||
import { socket } from 'boot/socket';
|
||||
|
||||
export const useStatusStore = defineStore('status', {
|
||||
state: () => ({
|
||||
device: {},
|
||||
statusList: {
|
||||
socketConnected: false,
|
||||
deviceConnected: false,
|
||||
tunnelConnected: false,
|
||||
simulationRunning: false,
|
||||
},
|
||||
}),
|
||||
actions: {
|
||||
bindEvents() {
|
||||
socket.on('connect', () => {
|
||||
this.statusList.socketConnected = true;
|
||||
});
|
||||
socket.on('disconnect', () => {
|
||||
this.statusList.socketConnected = false;
|
||||
});
|
||||
socket.on('status_update', (data) => {
|
||||
this.$patch((state) => {
|
||||
Object.assign(state.device, data);
|
||||
});
|
||||
});
|
||||
},
|
||||
socketConnect() {
|
||||
socket.connect();
|
||||
},
|
||||
socketDisconnect() {
|
||||
socket.disconnect();
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.accept(acceptHMRUpdate(useStatusStore, import.meta.hot));
|
||||
}
|
||||
15
src/types/leaflet-routing-machine.d.ts
vendored
Normal file
15
src/types/leaflet-routing-machine.d.ts
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
declare module "leaflet-routing-machine" {
|
||||
import * as L from "leaflet";
|
||||
|
||||
export interface IRouter {}
|
||||
export interface IGeocoder {}
|
||||
export interface LineOptions extends L.PolylineOptions {}
|
||||
|
||||
export namespace Routing {
|
||||
function control(options: any): any;
|
||||
class Plan {}
|
||||
}
|
||||
|
||||
const Routing: typeof Routing;
|
||||
export default Routing;
|
||||
}
|
||||
Reference in New Issue
Block a user