add EditFavoriteDialog component and simloc_logo.svg
split socketio strore into seperate stores added all routing points to sim add vuedraggable built icon picker for favorites and more
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 859 B After Width: | Height: | Size: 409 B |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 759 B |
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 3.1 KiB |
@@ -20,15 +20,15 @@ export default defineConfig((/* ctx */) => {
|
|||||||
// https://github.com/quasarframework/quasar/tree/dev/extras
|
// https://github.com/quasarframework/quasar/tree/dev/extras
|
||||||
extras: [
|
extras: [
|
||||||
// 'ionicons-v4',
|
// 'ionicons-v4',
|
||||||
// 'mdi-v7',
|
'mdi-v7',
|
||||||
// 'fontawesome-v6',
|
// 'fontawesome-v6',
|
||||||
// 'eva-icons',
|
// 'eva-icons',
|
||||||
// 'themify',
|
// 'themify',
|
||||||
// 'line-awesome',
|
// 'line-awesome',
|
||||||
// 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both!
|
// 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both!
|
||||||
|
|
||||||
'roboto-font', // optional, you are not bound to it
|
// 'roboto-font', // optional, you are not bound to it
|
||||||
'material-icons', // optional, you are not bound to it
|
// 'material-icons', // optional, you are not bound to it
|
||||||
],
|
],
|
||||||
|
|
||||||
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#build
|
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#build
|
||||||
@@ -151,7 +151,8 @@ export default defineConfig((/* ctx */) => {
|
|||||||
config: {
|
config: {
|
||||||
dark: true,
|
dark: true,
|
||||||
},
|
},
|
||||||
iconSet: 'material-icons', // Quasar icon set
|
// iconSet: 'material-icons', // Quasar icon set
|
||||||
|
iconSet: 'mdi-v7',
|
||||||
// lang: 'en-US', // Quasar language pack
|
// lang: 'en-US', // Quasar language pack
|
||||||
|
|
||||||
// For special cases outside of where the auto-import strategy can have an impact
|
// For special cases outside of where the auto-import strategy can have an impact
|
||||||
|
|||||||
15
src/assets/mdi-v7-icons.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
[
|
||||||
|
"mdi-dark",
|
||||||
|
"mdi-flip-h",
|
||||||
|
"mdi-flip-v",
|
||||||
|
"mdi-inactive",
|
||||||
|
"mdi-light",
|
||||||
|
"mdi-rotate-135",
|
||||||
|
"mdi-rotate-180",
|
||||||
|
"mdi-rotate-225",
|
||||||
|
"mdi-rotate-270",
|
||||||
|
"mdi-rotate-315",
|
||||||
|
"mdi-rotate-45",
|
||||||
|
"mdi-rotate-90",
|
||||||
|
"mdi-spin"
|
||||||
|
]
|
||||||
BIN
src/assets/simloc_logo.png
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
1978
src/assets/simloc_logo.svg
Normal file
|
After Width: | Height: | Size: 538 KiB |
BIN
src/assets/simloc_logo2.png
Normal file
|
After Width: | Height: | Size: 145 KiB |
218
src/components/EditFavoriteDialog.vue
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
<template>
|
||||||
|
<q-dialog ref="dlgRef" persistent>
|
||||||
|
<q-card class="bg-dark text-grey-1 add-loc-card q-pa-sm" v-if="operation == 'clear'">
|
||||||
|
<q-toolbar>
|
||||||
|
<q-avatar icon="mdi-star" color="secondary" text-color="yellow" />
|
||||||
|
<q-toolbar-title>Clear Favorite</q-toolbar-title>
|
||||||
|
</q-toolbar>
|
||||||
|
<q-card-section class="q-ml-lg">
|
||||||
|
Are you sure you want to clear favorite {{ formData.name }}?
|
||||||
|
</q-card-section>
|
||||||
|
<q-separator />
|
||||||
|
<q-card-actions align="right">
|
||||||
|
<q-btn flat label="OK" @click="onOkClick" />
|
||||||
|
<q-btn flat label="Cancel" @click="onDialogCancel" />
|
||||||
|
</q-card-actions>
|
||||||
|
</q-card>
|
||||||
|
<q-card class="bg-dark text-grey-1 add-loc-card q-pa-sm" v-else>
|
||||||
|
<q-toolbar>
|
||||||
|
<q-avatar icon="mdi-star" color="secondary" text-color="yellow" />
|
||||||
|
<q-toolbar-title v-if="!isFavorite && operation == 'set'"
|
||||||
|
>Save Location as Favorite</q-toolbar-title
|
||||||
|
>
|
||||||
|
<q-toolbar-title v-else-if="!isFavorite && operation == 'edit'"
|
||||||
|
>Edit Favorite</q-toolbar-title
|
||||||
|
>
|
||||||
|
</q-toolbar>
|
||||||
|
<q-card-section class="q-ml-lg">
|
||||||
|
<q-input
|
||||||
|
color="secondary"
|
||||||
|
v-model="formData.name"
|
||||||
|
label="Favorite Name"
|
||||||
|
autofocus
|
||||||
|
@keyup.enter="onOkClick"
|
||||||
|
style="max-width: 250px"
|
||||||
|
/>
|
||||||
|
<div class="flex col q-col-gutter-md">
|
||||||
|
<q-select
|
||||||
|
color="accent"
|
||||||
|
v-model="formData.category"
|
||||||
|
:options="categories"
|
||||||
|
label="Category"
|
||||||
|
use-input
|
||||||
|
new-value-mode="add-unique"
|
||||||
|
style="max-width: 150px"
|
||||||
|
>
|
||||||
|
<template v-slot:no-option>
|
||||||
|
<q-item>
|
||||||
|
<q-item-section class="text-grey"> No results or loading... </q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</template>
|
||||||
|
</q-select>
|
||||||
|
<q-select
|
||||||
|
color="accent"
|
||||||
|
v-model="formData.icon"
|
||||||
|
:options="filteredIcons"
|
||||||
|
option-label="name"
|
||||||
|
option-value="icon"
|
||||||
|
emit-value
|
||||||
|
map-options
|
||||||
|
use-input
|
||||||
|
clearable
|
||||||
|
label="Select Icon"
|
||||||
|
@filter="iconFilterFcn"
|
||||||
|
style="max-width: 150px"
|
||||||
|
>
|
||||||
|
<template v-slot:append>
|
||||||
|
<q-icon
|
||||||
|
:name="formData.icon ? formData.icon : 'mdi-library'"
|
||||||
|
class="cursor-pointer"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-slot:option="scope">
|
||||||
|
<q-item v-bind="scope.itemProps">
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon :name="scope.opt.icon" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>{{ scope.opt.name }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</template>
|
||||||
|
</q-select>
|
||||||
|
</div>
|
||||||
|
<div class="flex col q-col-gutter-md">
|
||||||
|
<q-input
|
||||||
|
v-model="formData.longitude"
|
||||||
|
label="Longitude"
|
||||||
|
style="max-width: 150px"
|
||||||
|
color="secondary"
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
<q-input
|
||||||
|
v-model="formData.latitude"
|
||||||
|
label="Latitude"
|
||||||
|
style="max-width: 150px"
|
||||||
|
color="secondary"
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
|
<q-separator />
|
||||||
|
<q-card-actions align="right">
|
||||||
|
<q-btn flat label="OK" @click="onOkClick" />
|
||||||
|
<q-btn flat label="Cancel" @click="onDialogCancel" />
|
||||||
|
</q-card-actions>
|
||||||
|
</q-card>
|
||||||
|
</q-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useFavoriteStore } from 'stores/favorite';
|
||||||
|
import { useDialogPluginComponent } from 'quasar';
|
||||||
|
import type { Favorite } from 'components/models';
|
||||||
|
import { computed, onMounted, reactive, ref } from 'vue';
|
||||||
|
import { reverseGeocodeRateLimited } from 'functions/reverseGeocodeSocket';
|
||||||
|
import iconList from '@quasar/extras/mdi-v7/icons.json';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
lat: number;
|
||||||
|
lng: number;
|
||||||
|
isFavorite: boolean;
|
||||||
|
operation: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const favoriteStore = useFavoriteStore();
|
||||||
|
|
||||||
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
|
defineEmits([...useDialogPluginComponent.emits]);
|
||||||
|
|
||||||
|
const { dialogRef: dlgRef, onDialogOK, onDialogCancel } = useDialogPluginComponent();
|
||||||
|
|
||||||
|
const iconsList = ref(iconList);
|
||||||
|
|
||||||
|
interface FormattedIcon {
|
||||||
|
name: string;
|
||||||
|
icon: string;
|
||||||
|
}
|
||||||
|
const iconsListFormatted: FormattedIcon[] = iconsList.value.map((str) => {
|
||||||
|
const name = str
|
||||||
|
.replace(/^[a-z]+/, '')
|
||||||
|
.replace(/([A-Z])/g, ' $1')
|
||||||
|
.trim();
|
||||||
|
const icon = str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
|
||||||
|
return { name, icon };
|
||||||
|
});
|
||||||
|
|
||||||
|
const filteredIcons = ref(iconsListFormatted.slice(0, 100));
|
||||||
|
const icon = ref();
|
||||||
|
const name = ref();
|
||||||
|
const address = ref();
|
||||||
|
const category = ref();
|
||||||
|
const categories = computed({
|
||||||
|
get: () => favoriteStore.categories,
|
||||||
|
set: (value) => {
|
||||||
|
favoriteStore.categories = value;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const formData: Favorite = reactive({
|
||||||
|
name: name.value,
|
||||||
|
longitude: props.lng,
|
||||||
|
latitude: props.lat,
|
||||||
|
category: category.value,
|
||||||
|
icon: icon.value,
|
||||||
|
}) as Favorite;
|
||||||
|
const loading = ref(true);
|
||||||
|
|
||||||
|
function iconFilterFcn(val: string, update: (callback: () => void) => void) {
|
||||||
|
update(() => {
|
||||||
|
if (val === '') {
|
||||||
|
filteredIcons.value = iconsListFormatted.slice(0, 100);
|
||||||
|
} else {
|
||||||
|
const needle = val.toLowerCase();
|
||||||
|
filteredIcons.value = iconsListFormatted
|
||||||
|
.filter(v =>
|
||||||
|
v.name.toLowerCase().includes(needle) ||
|
||||||
|
v.icon.toLowerCase().includes(needle)
|
||||||
|
)
|
||||||
|
.slice(0, 100);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onOkClick() {
|
||||||
|
if (props.isFavorite && props.operation === 'clear') {
|
||||||
|
favoriteStore.favoriteControl({ command: 'delete', favorite: formData });
|
||||||
|
}
|
||||||
|
favoriteStore.favoriteControl({ command: 'set', favorite: formData });
|
||||||
|
onDialogOK({ operation: props.operation, data: formData });
|
||||||
|
}
|
||||||
|
|
||||||
|
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
const response = await reverseGeocodeRateLimited(props.lat, props.lng);
|
||||||
|
console.log('reverse geocode response: ', response);
|
||||||
|
address.value = response.address;
|
||||||
|
if (response.favorite) {
|
||||||
|
formData.name = response.favorite.name;
|
||||||
|
formData.category = response.favorite.category;
|
||||||
|
formData.icon = response.favorite.icon;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching reverse geocode:', error);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
await sleep(500);
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style lang="sass" scoped>
|
||||||
|
.add-loc-card
|
||||||
|
width: 100%
|
||||||
|
max-width: 450px
|
||||||
|
</style>
|
||||||
@@ -1,30 +1,24 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<div>
|
||||||
<div style="white-space: pre-line">
|
<div style="white-space: pre-line">
|
||||||
{{ formattedAddressLine1 }}
|
{{ formattedAddressLine1 }}
|
||||||
<q-inner-loading v-if="loading">
|
|
||||||
<q-spinner-dots color="primary" />
|
|
||||||
</q-inner-loading>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{{ formattedAddressLine2 }}
|
{{ formattedAddressLine2 }}
|
||||||
<q-inner-loading v-if="loading">
|
</div>
|
||||||
<q-spinner-dots color="primary" />
|
|
||||||
</q-inner-loading>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref } from 'vue';
|
import { computed } from 'vue';
|
||||||
import type { NominatimResponse } from 'components/models';
|
import type { NominatimResponse } from 'components/models';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps<{
|
||||||
address: Object as () => NominatimResponse,
|
address?: NominatimResponse | undefined;
|
||||||
});
|
}>();
|
||||||
|
|
||||||
const loading = ref(false);
|
|
||||||
|
|
||||||
const formattedAddressLine1 = computed(() => {
|
const formattedAddressLine1 = computed(() => {
|
||||||
if (!loading.value && props.address) {
|
if (props.address) {
|
||||||
const place = [props.address.leisure, props.address.shop].filter(Boolean);
|
const place = [props.address.leisure, props.address.shop].filter(Boolean);
|
||||||
const addy = [props.address.house_number, props.address.road].filter(Boolean).join(' ');
|
const addy = [props.address.house_number, props.address.road].filter(Boolean).join(' ');
|
||||||
return [place, addy].filter(Boolean).join('\n');
|
return [place, addy].filter(Boolean).join('\n');
|
||||||
@@ -34,15 +28,11 @@ const formattedAddressLine1 = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const formattedAddressLine2 = computed(() => {
|
const formattedAddressLine2 = computed(() => {
|
||||||
if (!loading.value && props.address) {
|
if (props.address) {
|
||||||
const town: string = props.address.city
|
const town = props.address.city ?? props.address.village ?? props.address.town;
|
||||||
? props.address.city
|
|
||||||
: props.address.village
|
|
||||||
? props.address.village
|
|
||||||
: ' ';
|
|
||||||
const stateAbbr = stateAbbrevMap[props.address.state] ?? props.address.state ?? ' ';
|
const stateAbbr = stateAbbrevMap[props.address.state] ?? props.address.state ?? ' ';
|
||||||
const formAddress: string = town + ', ' + stateAbbr + ' ' + props.address.postcode;
|
const cityState = [town, stateAbbr].filter(Boolean).join(', ');
|
||||||
return formAddress;
|
return [cityState, props.address.postcode].filter(Boolean).join(' ');
|
||||||
} else {
|
} else {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
class="q-mr-sm"
|
class="q-mr-sm"
|
||||||
dense
|
dense
|
||||||
flat
|
flat
|
||||||
icon="menu"
|
icon="mdi-menu"
|
||||||
round
|
round
|
||||||
size="sm"
|
size="sm"
|
||||||
@click="qLocDrawer = !qLocDrawer"
|
@click="qLocDrawer = !qLocDrawer"
|
||||||
@@ -35,11 +35,16 @@
|
|||||||
</q-item>
|
</q-item>
|
||||||
</q-list>
|
</q-list>
|
||||||
</q-btn-dropdown>
|
</q-btn-dropdown>
|
||||||
<q-btn-dropdown label="Routing" size="sm" stretch v-if="routeSet.start && routeSet.end">
|
<q-btn-dropdown label="Routing" size="sm" stretch v-if="routeIsSet">
|
||||||
<q-list>
|
<q-list>
|
||||||
<q-item v-close-popup v-ripple clickable @click="routeToQueue">
|
<q-item v-close-popup v-ripple clickable @click="routeToQueue">
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label>Add Route to Sim Queue</q-item-label>
|
<q-item-label>Route Waypoints to SimQ</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item v-close-popup v-ripple clickable @click="routeAllPointsToQueue">
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>Route to SimQ</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item v-close-popup v-ripple clickable @click="clearRoute">
|
<q-item v-close-popup v-ripple clickable @click="clearRoute">
|
||||||
@@ -53,35 +58,43 @@
|
|||||||
</q-footer>
|
</q-footer>
|
||||||
<q-drawer
|
<q-drawer
|
||||||
v-model="qLocDrawer"
|
v-model="qLocDrawer"
|
||||||
behavior="mobile"
|
|
||||||
show-if-above
|
|
||||||
:width="300"
|
:width="300"
|
||||||
side="left"
|
side="left"
|
||||||
:breakpoint="500"
|
:breakpoint="500"
|
||||||
@mouseenter="miniState = false"
|
|
||||||
@mouseleave="miniState = true"
|
|
||||||
class="leafletDrawer"
|
class="leafletDrawer"
|
||||||
>
|
>
|
||||||
<q-scroll-area class="fit" :horizontal-thumb-style="{ opacity: '50' }">
|
<div class="full-height column no-wrap">
|
||||||
<q-list padding>
|
<q-list padding>
|
||||||
<q-item-label header
|
<q-item class="q-gutter-x-md">
|
||||||
set service dns forwarding options cname=qbittorrent.famor.org,docker.famor.org
|
<q-item-section avatar>
|
||||||
><span class="bold">Location Queue: </span> {{ simulationState }}</q-item-label
|
<q-item-label
|
||||||
|
><span class="text-weight-bolder text-accent text-h6">Queue: </span><span :class="(simulationRunning)? 'text-positive' : 'text-negative'">{{ toTitleCase(simulationState) }}</span></q-item-label
|
||||||
>
|
>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-select
|
||||||
|
v-model="showItems"
|
||||||
|
:options="drawerOptions"
|
||||||
|
label="Items to Show"
|
||||||
|
dense
|
||||||
|
style="max-width: 125px"
|
||||||
|
/>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
<q-separator />
|
<q-separator />
|
||||||
|
<q-scroll-area :horizontal-thumb-style="{ opacity: '50' }" class="col">
|
||||||
|
<q-list padding>
|
||||||
<VueDraggable
|
<VueDraggable
|
||||||
ref="el"
|
ref="el"
|
||||||
v-model="locationQueueOrderFiltered"
|
v-model="locationQueueOrderFiltered"
|
||||||
handle=".drag-handle"
|
handle=".drag-handle"
|
||||||
filter=".undraggable"
|
:isDisabled="disableDraggable"
|
||||||
>
|
>
|
||||||
<div
|
<LocationItem
|
||||||
v-for="(key, index) in locationQueueOrderFiltered"
|
v-for="(key, index) in locationQueueOrderFiltered"
|
||||||
:class="isDraggable(key)"
|
:class="isDraggable(key)"
|
||||||
:key="key"
|
:key="key"
|
||||||
@contextmenu.prevent="onDrawerContextMenu($event, key)"
|
|
||||||
>
|
|
||||||
<LocationItem
|
|
||||||
:loc_id="key"
|
:loc_id="key"
|
||||||
:active="
|
:active="
|
||||||
(locationQueueData as Record<string, any>)[key]?.loc_id ===
|
(locationQueueData as Record<string, any>)[key]?.loc_id ===
|
||||||
@@ -101,8 +114,9 @@
|
|||||||
:end="(locationQueueData as Record<string, any>)[key]?.end ?? undefined"
|
:end="(locationQueueData as Record<string, any>)[key]?.end ?? undefined"
|
||||||
:status="(locationQueueData as Record<string, any>)[key]?.status ?? undefined"
|
:status="(locationQueueData as Record<string, any>)[key]?.status ?? undefined"
|
||||||
@item-clicked="zoomToCoords"
|
@item-clicked="zoomToCoords"
|
||||||
|
@contextmenu.prevent="onDrawerContextMenu($event, key)"
|
||||||
|
active-class="text-orange"
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</VueDraggable>
|
</VueDraggable>
|
||||||
</q-list>
|
</q-list>
|
||||||
</q-scroll-area>
|
</q-scroll-area>
|
||||||
@@ -119,6 +133,7 @@
|
|||||||
</q-item>
|
</q-item>
|
||||||
</q-list>
|
</q-list>
|
||||||
</q-menu>
|
</q-menu>
|
||||||
|
</div>
|
||||||
</q-drawer>
|
</q-drawer>
|
||||||
|
|
||||||
<q-page-container>
|
<q-page-container>
|
||||||
@@ -151,8 +166,8 @@
|
|||||||
<q-item clickable v-ripple @click="handleAddLocation">
|
<q-item clickable v-ripple @click="handleAddLocation">
|
||||||
<q-item-section avatar>
|
<q-item-section avatar>
|
||||||
<q-avatar
|
<q-avatar
|
||||||
icon="add_location"
|
icon="mdi-map-marker"
|
||||||
color="primary"
|
color="accent"
|
||||||
text-color="white"
|
text-color="white"
|
||||||
size="sm"
|
size="sm"
|
||||||
/>
|
/>
|
||||||
@@ -161,25 +176,36 @@
|
|||||||
</q-item>
|
</q-item>
|
||||||
<q-item clickable v-ripple @click="setStartRoute">
|
<q-item clickable v-ripple @click="setStartRoute">
|
||||||
<q-item-section avatar>
|
<q-item-section avatar>
|
||||||
<q-avatar
|
<q-avatar color="accent" size="sm">
|
||||||
icon="add_location"
|
<q-icon class="small-icon" v-html="iconElement(startIcon).outerHTML" />
|
||||||
color="primary"
|
</q-avatar>
|
||||||
text-color="white"
|
|
||||||
size="sm"
|
|
||||||
/>
|
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-item-section>Set Route Start</q-item-section>
|
<q-item-section>Set Route Start</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item clickable v-ripple @click="setEndRoute">
|
<q-item clickable v-ripple @click="setEndRoute">
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-avatar color="accent" size="sm">
|
||||||
|
<q-icon class="small-icon" v-html="iconElement(endIcon).outerHTML" />
|
||||||
|
</q-avatar>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>Set Route End</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item clickable v-ripple @click="handleFavorite('set')" v-if="!isFavorite">
|
||||||
<q-item-section avatar>
|
<q-item-section avatar>
|
||||||
<q-avatar
|
<q-avatar
|
||||||
icon="add_location"
|
icon="mdi-star-outline"
|
||||||
color="primary"
|
color="accent"
|
||||||
text-color="white"
|
text-color="yellow"
|
||||||
size="sm"
|
size="sm"
|
||||||
/>
|
/>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-item-section>Set Route End</q-item-section>
|
<q-item-section>Set Favorite</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item clickable v-ripple @click="handleFavorite('clear')" v-else>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-avatar icon="mdi-star" color="accent" text-color="yellow" size="sm" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>Clear Favorite</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
</q-list>
|
</q-list>
|
||||||
</L-Popup>
|
</L-Popup>
|
||||||
@@ -197,7 +223,7 @@
|
|||||||
>
|
>
|
||||||
</L-Marker>
|
</L-Marker>
|
||||||
</L-Layer-Group>
|
</L-Layer-Group>
|
||||||
<L-Layer-Group v-if="routeSet.start && routeSet.end">
|
<L-Layer-Group v-if="routeSet.start || routeSet.end">
|
||||||
<LRoutingMachine
|
<LRoutingMachine
|
||||||
v-bind="routingOptions"
|
v-bind="routingOptions"
|
||||||
@routingstart="debugRoutingEvent"
|
@routingstart="debugRoutingEvent"
|
||||||
@@ -205,12 +231,48 @@
|
|||||||
@routingerror="debugRoutingEvent"
|
@routingerror="debugRoutingEvent"
|
||||||
/>
|
/>
|
||||||
</L-Layer-Group>
|
</L-Layer-Group>
|
||||||
<L-Layer-Group v-if="findMyUpdate">
|
<L-Layer-Group v-if="findMyUpdate && showFindMy">
|
||||||
<L-Marker
|
<L-Marker
|
||||||
v-if="findMyUpdate"
|
v-if="findMyUpdate"
|
||||||
:icon="fmIcon as any"
|
:icon="fmIcon as any"
|
||||||
:lat-lng="[findMyUpdate.latitude ?? 0, findMyUpdate.longitude ?? 0]"
|
:lat-lng="[findMyUpdate.latitude ?? 0, findMyUpdate.longitude ?? 0]"
|
||||||
>
|
>
|
||||||
|
<L-Popup
|
||||||
|
:options="{ closeOnClick: true, closeButton: false, className: 'marker-popup' }"
|
||||||
|
>
|
||||||
|
<q-list dense seperator style="min-width: 100px" class="bg-grey-10">
|
||||||
|
<q-item clickable v-ripple @click="handleAddLocation">
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-avatar
|
||||||
|
icon="add_location"
|
||||||
|
color="primary"
|
||||||
|
text-color="white"
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>Add Location</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item clickable v-ripple @click="setStartRoute">
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-avatar size="sm">
|
||||||
|
<q-icon v-html="iconElement(startIcon).outerHTML" />
|
||||||
|
</q-avatar>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>Set Route Start</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item clickable v-ripple @click="setEndRoute">
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-avatar
|
||||||
|
icon="add_location"
|
||||||
|
color="primary"
|
||||||
|
text-color="white"
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>Set Route End</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</L-Popup>
|
||||||
<L-Tooltip>
|
<L-Tooltip>
|
||||||
{{ findMyTimePast }}
|
{{ findMyTimePast }}
|
||||||
</L-Tooltip>
|
</L-Tooltip>
|
||||||
@@ -266,10 +328,12 @@ import * as LeafLet from 'leaflet';
|
|||||||
import LRoutingMachine from 'components/LRoutingMachine.vue';
|
import LRoutingMachine from 'components/LRoutingMachine.vue';
|
||||||
import LocationItem from 'components/LocationItem.vue';
|
import LocationItem from 'components/LocationItem.vue';
|
||||||
import SetLocationDialog from 'components/SetLocationDialog.vue';
|
import SetLocationDialog from 'components/SetLocationDialog.vue';
|
||||||
|
import EditFavoriteDialog from 'components/EditFavoriteDialog.vue';
|
||||||
import { customRouter } from 'functions/serviceURL';
|
import { customRouter } from 'functions/serviceURL';
|
||||||
import { useRoutingEvents } from '../composables/useRoutingEvents';
|
import { useRoutingEvents } from '../composables/useRoutingEvents';
|
||||||
import { useMarkerContextMenu } from '../composables/useMarkerContextMenu';
|
import { useMarkerContextMenu } from '../composables/useMarkerContextMenu';
|
||||||
import type { IRouter } from 'leaflet-routing-machine';
|
import type { IRouter } from 'leaflet-routing-machine';
|
||||||
|
import { reverseGeocodeRateLimited } from 'functions/reverseGeocodeSocket';
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import type { coords, SearchControlProps } from 'components/models';
|
import type { coords, SearchControlProps } from 'components/models';
|
||||||
@@ -277,31 +341,30 @@ import type { LeafletMouseEvent, Map } from 'leaflet';
|
|||||||
|
|
||||||
// Stores
|
// Stores
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useSocketioStore } from 'stores/socketio';
|
import { useSocketStore } from 'stores/socket';
|
||||||
|
import { useSimulationStore } from 'stores/simulation';
|
||||||
import { useLeafletStore } from 'stores/leaflet';
|
import { useLeafletStore } from 'stores/leaflet';
|
||||||
|
import { useIcloudStore } from 'stores/icloud';
|
||||||
import { favorites } from 'constants/favorites';
|
import { favorites } from 'constants/favorites';
|
||||||
|
|
||||||
|
const socketStore = useSocketStore();
|
||||||
|
|
||||||
const leafletStore = useLeafletStore();
|
const leafletStore = useLeafletStore();
|
||||||
const { zoom, center, markerLatLng, qLocDrawer, routeSet, routeDirections } =
|
const { zoom, center, markerLatLng, qLocDrawer, routeSet, routeDirections, routeCoordinates } =
|
||||||
storeToRefs(leafletStore);
|
storeToRefs(leafletStore);
|
||||||
|
|
||||||
const socketStore = useSocketioStore();
|
const simulationStore = useSimulationStore();
|
||||||
const {
|
const { currentLocation, locationQueueData, locationQueueOrder, simulationState, simulationRunning } =
|
||||||
currentLocation,
|
storeToRefs(simulationStore);
|
||||||
nextLocation,
|
|
||||||
locationQueueData,
|
const icloudStore = useIcloudStore();
|
||||||
locationQueueOrder,
|
const { findMyUpdate } = storeToRefs(icloudStore);
|
||||||
findMyUpdate,
|
|
||||||
simulationState,
|
|
||||||
} = storeToRefs(socketStore);
|
|
||||||
|
|
||||||
const $q = useQuasar();
|
const $q = useQuasar();
|
||||||
|
|
||||||
const now = ref(Date.now());
|
const now = ref(Date.now());
|
||||||
const mapRef = ref();
|
const mapRef = ref();
|
||||||
const responseMessage = ref('');
|
const responseMessage = ref('');
|
||||||
const miniState = ref(true);
|
|
||||||
const safeCenter = computed<[number, number]>(() => {
|
const safeCenter = computed<[number, number]>(() => {
|
||||||
const lat = center.value?.[0];
|
const lat = center.value?.[0];
|
||||||
const lng = center.value?.[1];
|
const lng = center.value?.[1];
|
||||||
@@ -319,6 +382,12 @@ const safeMarkerLatLng = computed<[number, number] | null>(() => {
|
|||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const drawerOptions = ['All', 'Done', 'Queued', 'Deleted'];
|
||||||
|
const showItems = ref('All');
|
||||||
|
const disableDraggable = computed(() => {
|
||||||
|
return showItems.value === 'All';
|
||||||
|
});
|
||||||
|
|
||||||
const onMapReady = (map: Map) => {
|
const onMapReady = (map: Map) => {
|
||||||
const provider = new OpenStreetMapProvider();
|
const provider = new OpenStreetMapProvider();
|
||||||
const searchOptions: SearchControlProps = {
|
const searchOptions: SearchControlProps = {
|
||||||
@@ -358,6 +427,10 @@ const startIcon = new Icon({
|
|||||||
svg: PinTriangle,
|
svg: PinTriangle,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const iconElement = (icon: Icon) => {
|
||||||
|
return icon.createIcon();
|
||||||
|
};
|
||||||
|
|
||||||
const endIcon = new Icon({
|
const endIcon = new Icon({
|
||||||
color: '#a23337',
|
color: '#a23337',
|
||||||
accentColor: 'rgba(0,0,0,0.25)',
|
accentColor: 'rgba(0,0,0,0.25)',
|
||||||
@@ -390,20 +463,22 @@ const locationQueueOrderFiltered = computed({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
set: (val) => {
|
set: (val) => {
|
||||||
socketStore.updateLocationQueueOrder(val);
|
simulationStore.updateLocationQueueOrder(val);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const isDraggable = (locid: string) => {
|
const isDraggable = (locid: string) => {
|
||||||
const currentIndex = currentLocation.value ? locationQueueOrder.value.indexOf(currentLocation.loc_id) : 0;
|
const currentIndex = currentLocation.value
|
||||||
|
? locationQueueOrderFiltered.value.indexOf(currentLocation.value.loc_id)
|
||||||
|
: 0;
|
||||||
const myIndex = locationQueueOrder.value.indexOf(locid);
|
const myIndex = locationQueueOrder.value.indexOf(locid);
|
||||||
const myUpdatedIndex = myIndex - currentIndex;
|
const myUpdatedIndex = myIndex - currentIndex;
|
||||||
if ( myUpdatedIndex > 0 ) {
|
if (myUpdatedIndex > 1) {
|
||||||
return 'draggable';
|
return 'draggable';
|
||||||
} else {
|
} else {
|
||||||
return 'undraggable';
|
return 'undraggable';
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const getCustomIcon = (locid: string) => {
|
const getCustomIcon = (locid: string) => {
|
||||||
const currentIndex = currentLocation.value
|
const currentIndex = currentLocation.value
|
||||||
@@ -497,6 +572,7 @@ async function routeToQueue() {
|
|||||||
await addLocation(
|
await addLocation(
|
||||||
{ lat: Number(direction.coordinates.lat), lng: Number(direction.coordinates.lng) },
|
{ lat: Number(direction.coordinates.lat), lng: Number(direction.coordinates.lng) },
|
||||||
direction.time,
|
direction.time,
|
||||||
|
true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
@@ -504,6 +580,24 @@ async function routeToQueue() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function routeAllPointsToQueue() {
|
||||||
|
if (routeSet.value.start && routeSet.value.end && routeCoordinates) {
|
||||||
|
if (routeCoordinates.value && routeCoordinates.value.length > 0) {
|
||||||
|
for (const coord of routeCoordinates.value) {
|
||||||
|
if (coord) {
|
||||||
|
await addLocation(
|
||||||
|
{ lat: Number(coord.lat), lng: Number(coord.lng) },
|
||||||
|
coord.timeFromPrev,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await delay(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* function routeToQueue() {
|
/* function routeToQueue() {
|
||||||
console.log('routeToQueue');
|
console.log('routeToQueue');
|
||||||
if (routeSet.value.start && routeSet.value.end && routeSegments) {
|
if (routeSet.value.start && routeSet.value.end && routeSegments) {
|
||||||
@@ -548,15 +642,26 @@ const updateRoute = () => {
|
|||||||
routingOptions.waypoints = waypoints;
|
routingOptions.waypoints = waypoints;
|
||||||
};
|
};
|
||||||
|
|
||||||
const { clickedLatLng, handleMarkerClick, setStartRoute, setEndRoute } = useMarkerContextMenu(
|
const { isFavorite, handleMarkerClick, clickedLatLng, setStartRoute, setEndRoute } =
|
||||||
routeSet,
|
useMarkerContextMenu(routeSet, updateRoute, closeAllPopups);
|
||||||
updateRoute,
|
|
||||||
closeAllPopups,
|
const routeIsSet = computed(() => {
|
||||||
|
return (
|
||||||
|
routeSet.value.start.lat &&
|
||||||
|
routeSet.value.start.lng &&
|
||||||
|
routeSet.value.end.lat &&
|
||||||
|
routeSet.value.end.lng
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const selectedItem = ref();
|
const selectedItem = ref();
|
||||||
const contextMenu = ref();
|
const contextMenu = ref();
|
||||||
|
|
||||||
|
function toTitleCase(word: string | null | undefined) {
|
||||||
|
if (!word) return '';
|
||||||
|
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
function onDrawerContextMenu(e: MouseEvent, item: string) {
|
function onDrawerContextMenu(e: MouseEvent, item: string) {
|
||||||
console.log('onDrawerContextMenu: ', item);
|
console.log('onDrawerContextMenu: ', item);
|
||||||
selectedItem.value = item;
|
selectedItem.value = item;
|
||||||
@@ -570,7 +675,7 @@ function handleDrawerContextMenu(command: string) {
|
|||||||
break;
|
break;
|
||||||
case 'delete':
|
case 'delete':
|
||||||
try {
|
try {
|
||||||
const ack = socketStore.simulationControl({
|
const ack = simulationStore.simulationControl({
|
||||||
command: 'delete',
|
command: 'delete',
|
||||||
latitude: 0,
|
latitude: 0,
|
||||||
longitude: 0,
|
longitude: 0,
|
||||||
@@ -606,24 +711,66 @@ function handleDrawerContextMenu(command: string) {
|
|||||||
$q.notify(`context menu: ${command} ${selectedItem.value}`);
|
$q.notify(`context menu: ${command} ${selectedItem.value}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleFavorite(operation: string) {
|
||||||
|
if (clickedLatLng.value) {
|
||||||
|
const latlng = clickedLatLng.value;
|
||||||
|
closeAllPopups();
|
||||||
|
$q.notify(`${operation} favorite...${latlng.toString()}`);
|
||||||
|
$q.dialog({
|
||||||
|
component: EditFavoriteDialog,
|
||||||
|
componentProps: {
|
||||||
|
lat: Number(latlng.lat),
|
||||||
|
lng: Number(latlng.lng),
|
||||||
|
isFavorite: isFavorite.value,
|
||||||
|
operation: operation,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.onOk(({ operation, lat, lng, name, category, icon }) => {
|
||||||
|
// void setFavorite({ lat: Number(lat), lng: Number(lng) }, name, category, icon);
|
||||||
|
console.log(
|
||||||
|
'Confirmed favorite ' +
|
||||||
|
operation +
|
||||||
|
': ' +
|
||||||
|
lat +
|
||||||
|
', ' +
|
||||||
|
lng +
|
||||||
|
', name: ' +
|
||||||
|
name +
|
||||||
|
', category: ' +
|
||||||
|
category +
|
||||||
|
', icon: ' +
|
||||||
|
icon,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.onCancel(() => {
|
||||||
|
console.log('Dialog cancelled');
|
||||||
|
})
|
||||||
|
.onDismiss(() => {
|
||||||
|
console.log('Dialog dismissed');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handleAddLocation() {
|
function handleAddLocation() {
|
||||||
if (clickedLatLng.value) {
|
if (clickedLatLng.value) {
|
||||||
const latlng = clickedLatLng.value;
|
const latlng = clickedLatLng.value;
|
||||||
closeAllPopups();
|
closeAllPopups();
|
||||||
$q.notify(`add location...${latlng.toString()}`);
|
$q.notify(`add location...${latlng.toString()}`);
|
||||||
// reverseGeocode(latlng.lat, latlng.lng)
|
|
||||||
// .then((data) => {
|
|
||||||
// const NomAddress = data.address as unknown as NominatimAddress;
|
|
||||||
$q.dialog({
|
$q.dialog({
|
||||||
component: SetLocationDialog,
|
component: SetLocationDialog,
|
||||||
componentProps: {
|
componentProps: {
|
||||||
lat: Number(latlng.lat),
|
lat: Number(latlng.lat),
|
||||||
lng: Number(latlng.lng),
|
lng: Number(latlng.lng),
|
||||||
// address: NomAddress,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.onOk(({ delay, address }) => {
|
.onOk(({ delay, address }) => {
|
||||||
void addLocation({ lat: Number(latlng.lat), lng: Number(latlng.lng) }, delay, address);
|
const revGeo = !address;
|
||||||
|
void addLocation(
|
||||||
|
{ lat: Number(latlng.lat), lng: Number(latlng.lng) },
|
||||||
|
delay,
|
||||||
|
revGeo,
|
||||||
|
address,
|
||||||
|
);
|
||||||
console.log(
|
console.log(
|
||||||
'Confirmed location add: latitude: ' +
|
'Confirmed location add: latitude: ' +
|
||||||
latlng.lat +
|
latlng.lat +
|
||||||
@@ -639,10 +786,6 @@ function handleAddLocation() {
|
|||||||
.onDismiss(() => {
|
.onDismiss(() => {
|
||||||
console.log('Dialog dismissed');
|
console.log('Dialog dismissed');
|
||||||
});
|
});
|
||||||
// })
|
|
||||||
// .catch((error) => {
|
|
||||||
// console.error('Error fetching reverse geocode:', error);
|
|
||||||
// });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
@@ -659,19 +802,29 @@ async function reverseGeocode(lat: number, lng: number) {
|
|||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
async function addLocation(coords: coords, delay: number, address?: string) {
|
|
||||||
|
async function addLocation(
|
||||||
|
coords: coords,
|
||||||
|
delay: number | undefined,
|
||||||
|
getRevGeocode: boolean,
|
||||||
|
address?: string,
|
||||||
|
) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let notType: string = 'positive';
|
let notType: string = 'positive';
|
||||||
try {
|
try {
|
||||||
const setCmdRsp = socketStore.simulationControl({
|
let revGeo;
|
||||||
|
if (!address && getRevGeocode) {
|
||||||
|
revGeo = reverseGeocodeRateLimited(coords.lat, coords.lng);
|
||||||
|
}
|
||||||
|
const addy = address ?? revGeo?.address;
|
||||||
|
const setCmdRsp = simulationStore.simulationControl({
|
||||||
command: 'add',
|
command: 'add',
|
||||||
latitude: coords.lat,
|
latitude: coords.lat,
|
||||||
longitude: coords.lng,
|
longitude: coords.lng,
|
||||||
loc_id: '',
|
loc_id: '',
|
||||||
delay: delay,
|
delay: delay,
|
||||||
address: address,
|
address: addy,
|
||||||
});
|
});
|
||||||
if (setCmdRsp.msg) {
|
if (setCmdRsp.msg) {
|
||||||
responseMessage.value = setCmdRsp.msg;
|
responseMessage.value = setCmdRsp.msg;
|
||||||
@@ -697,8 +850,41 @@ async function addLocation(coords: coords, delay: number, address?: string) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
async function addLocation(
|
||||||
|
coords: coords,
|
||||||
|
delay: number | undefined,
|
||||||
|
getRevGeocode: boolean,
|
||||||
|
address?: string,
|
||||||
|
) {
|
||||||
|
let notType: string = 'positive';
|
||||||
|
let revGeo;
|
||||||
|
if (!address && getRevGeocode) {
|
||||||
|
revGeo = await reverseGeocodeRateLimited(coords.lat, coords.lng);
|
||||||
|
}
|
||||||
|
const addy = address ?? revGeo?.address;
|
||||||
|
const setCmdRsp = simulationStore.simulationControl({
|
||||||
|
command: 'add',
|
||||||
|
latitude: coords.lat,
|
||||||
|
longitude: coords.lng,
|
||||||
|
loc_id: '',
|
||||||
|
delay: delay,
|
||||||
|
address: addy,
|
||||||
|
});
|
||||||
|
if (setCmdRsp.sts === 'error') {
|
||||||
|
notType = 'negative';
|
||||||
|
responseMessage.value = 'Failed to set location: ' + setCmdRsp.msg;
|
||||||
|
console.error('Error setting location:', setCmdRsp.msg);
|
||||||
|
}
|
||||||
|
if (setCmdRsp.msg) {
|
||||||
|
responseMessage.value = setCmdRsp.msg;
|
||||||
|
}
|
||||||
|
$q.notify({ type: notType, message: responseMessage.value });
|
||||||
|
}
|
||||||
|
|
||||||
function zoomToCoords(arg: string) {
|
function zoomToCoords(arg: string) {
|
||||||
|
console.log('zoomToCoords: ', arg);
|
||||||
const item = locationQueueData.value[arg];
|
const item = locationQueueData.value[arg];
|
||||||
if (!item || item.latitude == null || item.longitude == null) {
|
if (!item || item.latitude == null || item.longitude == null) {
|
||||||
return;
|
return;
|
||||||
@@ -708,6 +894,15 @@ function zoomToCoords(arg: string) {
|
|||||||
qLocDrawer.value = false;
|
qLocDrawer.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const showFindMy = computed(() => {
|
||||||
|
if (findMyUpdate.value) {
|
||||||
|
const diffInSec = Math.floor(Math.abs(now.value - findMyUpdate.value.timeStamp) / 1000);
|
||||||
|
return diffInSec < 120;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const findMyTimePast = computed(() => {
|
const findMyTimePast = computed(() => {
|
||||||
if (findMyUpdate.value) {
|
if (findMyUpdate.value) {
|
||||||
const diffInMs = Math.abs(now.value - findMyUpdate.value.timeStamp);
|
const diffInMs = Math.abs(now.value - findMyUpdate.value.timeStamp);
|
||||||
@@ -752,13 +947,6 @@ function zoomTo(loc: string) {
|
|||||||
$q.notify({ type: 'negative', message: 'Simulation Location not available' });
|
$q.notify({ type: 'negative', message: 'Simulation Location not available' });
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'nextLoc':
|
|
||||||
if (nextLocation.value && nextLocation.value.latitude && nextLocation.value.longitude) {
|
|
||||||
leafletStore.setCenter(nextLocation.value.latitude, nextLocation.value.longitude);
|
|
||||||
} else {
|
|
||||||
$q.notify({ type: 'negative', message: 'Next Location not available' });
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
$q.notify({ type: 'negative', message: 'Invalid location' });
|
$q.notify({ type: 'negative', message: 'Invalid location' });
|
||||||
break;
|
break;
|
||||||
@@ -800,4 +988,15 @@ onUnmounted(() => {
|
|||||||
background-color: $dark
|
background-color: $dark
|
||||||
.marker-popup .leaflet-popup-content
|
.marker-popup .leaflet-popup-content
|
||||||
margin: 13px 10px 13px 10px
|
margin: 13px 10px 13px 10px
|
||||||
|
|
||||||
|
.small-icon svg
|
||||||
|
transform: scale(0.6)
|
||||||
|
transform-origin: center
|
||||||
|
bottom: 0
|
||||||
|
right: 0
|
||||||
|
|
||||||
|
.small-icon
|
||||||
|
position: absolute
|
||||||
|
margin-left: 14px
|
||||||
|
margin-top: 48px
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,20 +1,21 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useSocketioStore } from 'stores/socketio';
|
import { useSimulationStore } from 'stores/simulation';
|
||||||
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
||||||
import type { NominatimResponse } from 'components/models';
|
import type { NominatimResponse } from 'components/models';
|
||||||
import { Icon, PinCirclePanel, PinStarPanel } from 'leaflet-extra-markers';
|
import { Icon, PinCirclePanel, PinStarPanel } from 'leaflet-extra-markers';
|
||||||
|
|
||||||
import FormattedAddress from 'components/FormattedAddress.vue';
|
import FormattedAddress from 'components/FormattedAddress.vue';
|
||||||
|
//import NestedKnob from 'components/NestedKnob.vue';
|
||||||
|
|
||||||
const socketStore = useSocketioStore();
|
const simulationStore = useSimulationStore();
|
||||||
const { currentLocation, locationQueueOrder, locationQueueData, simulationRunning } =
|
const { currentLocation, locationQueueOrder, locationQueueData, simulationRunning } =
|
||||||
storeToRefs(socketStore);
|
storeToRefs(simulationStore);
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
address: {
|
address: {
|
||||||
type: Object as () => NominatimResponse,
|
type: Object as () => NominatimResponse,
|
||||||
required: true,
|
required: false,
|
||||||
},
|
},
|
||||||
latitude: {
|
latitude: {
|
||||||
type: Number,
|
type: Number,
|
||||||
@@ -26,7 +27,7 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
icon: {
|
icon: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'location_on',
|
default: 'mdi-map-marker',
|
||||||
},
|
},
|
||||||
start: {
|
start: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -136,19 +137,8 @@ const calculateDeltaTime = computed(() => {
|
|||||||
}
|
}
|
||||||
return delta;
|
return delta;
|
||||||
});
|
});
|
||||||
/*
|
|
||||||
const secondsToTime = computed(() => {
|
|
||||||
const seconds = props.delay;
|
|
||||||
const hours = Math.floor(seconds / 3600);
|
|
||||||
const minutes = Math.floor((seconds % 3600) / 60);
|
|
||||||
const remainingSeconds = seconds % 60;
|
|
||||||
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${remainingSeconds
|
|
||||||
.toString()
|
|
||||||
.padStart(2, '0')}`;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
const secondsToHhMmSs = (seconds: number) => {
|
||||||
const secondsToTime = (seconds: number) => {
|
|
||||||
const hours = Math.floor(seconds / 3600);
|
const hours = Math.floor(seconds / 3600);
|
||||||
const minutes = Math.floor((seconds % 3600) / 60);
|
const minutes = Math.floor((seconds % 3600) / 60);
|
||||||
const remainingSeconds = seconds % 60;
|
const remainingSeconds = seconds % 60;
|
||||||
@@ -156,7 +146,17 @@ const secondsToTime = (seconds: number) => {
|
|||||||
.toString()
|
.toString()
|
||||||
.padStart(2, '0')}`;
|
.padStart(2, '0')}`;
|
||||||
};
|
};
|
||||||
|
/*
|
||||||
|
const secondsToTimeShort = (seconds: number) => {
|
||||||
|
const hours = Math.floor(seconds / 3600);
|
||||||
|
const minutes = Math.floor((seconds % 3600) / 60);
|
||||||
|
if (hours > 2) {
|
||||||
|
return hours.toString();
|
||||||
|
}
|
||||||
|
if (minutes > 5) {
|
||||||
|
return minutes.toString();
|
||||||
|
} else return seconds.toString();
|
||||||
|
};
|
||||||
const timeToSeconds = (timeIn: string) => {
|
const timeToSeconds = (timeIn: string) => {
|
||||||
const a = timeIn.split(':');
|
const a = timeIn.split(':');
|
||||||
const seconds = +a[0] * 60 * 60 + +a[1] * 60 + +a[2];
|
const seconds = +a[0] * 60 * 60 + +a[1] * 60 + +a[2];
|
||||||
@@ -173,78 +173,6 @@ const humanReadableDateTime = (iso: string) => {
|
|||||||
second: '2-digit',
|
second: '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}`;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
const currentIndex = computed(() => {
|
const currentIndex = computed(() => {
|
||||||
return currentLocation.value ? locationQueueOrder.value.indexOf(currentLocation.value.loc_id) : 0;
|
return currentLocation.value ? locationQueueOrder.value.indexOf(currentLocation.value.loc_id) : 0;
|
||||||
@@ -258,12 +186,6 @@ const myUpdatedIndex = computed(() => {
|
|||||||
return myIndex.value - currentIndex.value;
|
return myIndex.value - currentIndex.value;
|
||||||
});
|
});
|
||||||
|
|
||||||
/*
|
|
||||||
const markerIndex = computed(() => {
|
|
||||||
return props.active ? '*' : myUpdatedIndex.value.toString();
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
|
|
||||||
const itemClass = computed(() => {
|
const itemClass = computed(() => {
|
||||||
if (myUpdatedIndex.value > 0) return 'future';
|
if (myUpdatedIndex.value > 0) return 'future';
|
||||||
else if (myUpdatedIndex.value < 0) return 'past';
|
else if (myUpdatedIndex.value < 0) return 'past';
|
||||||
@@ -313,21 +235,80 @@ onMounted(() => {
|
|||||||
timerId = requestAnimationFrame(update);
|
timerId = requestAnimationFrame(update);
|
||||||
});
|
});
|
||||||
|
|
||||||
const delayValue = computed({
|
const delayConverted = computed({
|
||||||
get: () => props.delay,
|
get: () => {
|
||||||
set: (val) => socketStore.updateLocationMark(props.loc_id, 'delay', val),
|
if (props.delay <= 260) {
|
||||||
|
return props.delay;
|
||||||
|
} else if (props.delay <= 3600) {
|
||||||
|
return Math.floor(props.delay / 60);
|
||||||
|
} else {
|
||||||
|
return Math.floor(props.delay / 3600);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
set: (val) => {
|
||||||
|
if (getDelayAttrs.value.units == 's') {
|
||||||
|
simulationStore.updateLocationMark(props.loc_id, 'delay', val);
|
||||||
|
} else if (getDelayAttrs.value.units == 'm') {
|
||||||
|
simulationStore.updateLocationMark(props.loc_id, 'delay', val * 60);
|
||||||
|
} else if (getDelayAttrs.value.units == 'h') {
|
||||||
|
simulationStore.updateLocationMark(props.loc_id, 'delay', val * 3600);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const delayHhMmSs = computed({
|
||||||
|
get: () => secondsToHhMmSs(props.delay),
|
||||||
|
set: (val) => {
|
||||||
|
const totalSeconds = val.split(':').reduce((acc, time) => 60 * acc + +time, 0);
|
||||||
|
simulationStore.updateLocationMark(props.loc_id, 'delay', totalSeconds);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const getDelayAttrs = computed(() => {
|
const getDelayAttrs = computed(() => {
|
||||||
if (props.delay <= 260) {
|
if (props.delay <= 260) {
|
||||||
return { units: 's', delay: props.delay, step: 10, max: 300 } as const;
|
return {
|
||||||
|
units: 's',
|
||||||
|
display: Math.floor((props.delay % 3600) / 60),
|
||||||
|
step: 10,
|
||||||
|
max: 80,
|
||||||
|
} as const;
|
||||||
} else if (props.delay <= 3600) {
|
} else if (props.delay <= 3600) {
|
||||||
return { units: 'm', delay: props.delay / 60, step: 60, max: 4500 } as const;
|
return {
|
||||||
|
units: 'm',
|
||||||
|
display: Math.floor((props.delay % 3600) / 60),
|
||||||
|
step: 1,
|
||||||
|
max: 80,
|
||||||
|
} as const;
|
||||||
} else {
|
} else {
|
||||||
return { units: 'h', delay: props.delay / 3600, step: 3600, max: 21600 } as const;
|
return {
|
||||||
|
units: 'h',
|
||||||
|
display: Math.floor(props.delay / 3600),
|
||||||
|
step: 1,
|
||||||
|
max: 12,
|
||||||
|
} as const;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
const delayMask = computed(() => {
|
||||||
|
if (props.delay < 60) {
|
||||||
|
return 'ss';
|
||||||
|
}
|
||||||
|
if (props.delay < 3600) {
|
||||||
|
return 'mm:ss';
|
||||||
|
} else return 'HH:mm:ss';
|
||||||
|
});
|
||||||
|
|
||||||
|
const delayInputMask = computed(() => {
|
||||||
|
if (props.delay < 60) {
|
||||||
|
return '##';
|
||||||
|
}
|
||||||
|
if (props.delay < 3600) {
|
||||||
|
return '##:##';
|
||||||
|
} else return '##:##:##';
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
const displayDelay = computed(() => {
|
const displayDelay = computed(() => {
|
||||||
return props.index > currentIndex.value;
|
return props.index > currentIndex.value;
|
||||||
});
|
});
|
||||||
@@ -336,58 +317,74 @@ const delayActive = computed(() => {
|
|||||||
return props.index === currentIndex.value + 1;
|
return props.index === currentIndex.value + 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const canDrag = computed(() => {
|
||||||
|
return props.index > currentIndex.value + 1;
|
||||||
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
cancelAnimationFrame(timerId);
|
cancelAnimationFrame(timerId);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<q-item v-if="displayDelay" :active="delayActive">
|
<div>
|
||||||
<q-item-section avatar>
|
<q-item
|
||||||
<q-icon name="access_time" color="secondary" />
|
v-if="displayDelay"
|
||||||
</q-item-section>
|
:active="delayActive"
|
||||||
<q-item-section>
|
:class="delayActive ? 'text-orange bg-dark' : ''"
|
||||||
<q-knob
|
active-class="text-orange bg-dark"
|
||||||
show-value
|
|
||||||
v-model="delayValue"
|
|
||||||
size="50px"
|
|
||||||
color="secondary"
|
|
||||||
track-color="dark-page"
|
|
||||||
:step="getDelayAttrs.step"
|
|
||||||
:max="getDelayAttrs.max"
|
|
||||||
>
|
>
|
||||||
{{ getDelayAttrs.delay }} {{ getDelayAttrs.units }}
|
<q-item-section class="flex flex-center">
|
||||||
</q-knob>
|
<q-input
|
||||||
<!--
|
class="q-pt-md"
|
||||||
|
filled
|
||||||
|
v-model="delayHhMmSs"
|
||||||
|
mask="fulltime"
|
||||||
|
stack-label
|
||||||
|
label="Delay"
|
||||||
|
dense
|
||||||
|
style="max-width: 150px"
|
||||||
|
>
|
||||||
|
<template v-slot:append> </template>
|
||||||
|
</q-input>
|
||||||
<q-slider
|
<q-slider
|
||||||
v-model="delayValue"
|
v-model="delayConverted"
|
||||||
:min="0"
|
:min="0"
|
||||||
:step="getDelayAttrs.step"
|
:step="getDelayAttrs.step"
|
||||||
:max="getDelayAttrs.max"
|
:max="getDelayAttrs.max"
|
||||||
label
|
label
|
||||||
|
switch-label-side
|
||||||
|
:label-value="delayConverted + ' ' + getDelayAttrs.units"
|
||||||
markers
|
markers
|
||||||
snap
|
snap
|
||||||
label-always
|
|
||||||
:label-value="getDelayAttrs.delay + getDelayAttrs.units"
|
|
||||||
:marker-labels="markerLabel"
|
|
||||||
color="primary"
|
color="primary"
|
||||||
|
style="max-width: 75%"
|
||||||
/>
|
/>
|
||||||
-->
|
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-separator inset spaced v-if="displayDelay" />
|
<q-separator inset spaced v-if="displayDelay" />
|
||||||
<q-item v-ripple clickable :active="active" @click="itemClicked" :class="itemClass">
|
<q-item
|
||||||
|
v-ripple
|
||||||
|
clickable
|
||||||
|
:active="active"
|
||||||
|
@click="itemClicked"
|
||||||
|
:class="itemClass"
|
||||||
|
active-class="text-orange bg-dark"
|
||||||
|
>
|
||||||
<q-item-section style="width: 70%">
|
<q-item-section style="width: 70%">
|
||||||
<q-item-label>
|
<q-item-label v-if="address">
|
||||||
<FormattedAddress :address="props.address" />
|
<FormattedAddress :address="props.address" />
|
||||||
<q-tooltip> {{ latitude }}, {{ longitude }} </q-tooltip>
|
<q-tooltip> {{ latitude }}, {{ longitude }} </q-tooltip>
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
|
<q-item-label v-else> {{ latitude }}, {{ longitude }} </q-item-label>
|
||||||
<q-item-label caption lines="1" v-if="start && simulationRunning">
|
<q-item-label caption lines="1" v-if="start && simulationRunning">
|
||||||
start: {{ humanReadableDateTime(start) }}
|
start: {{ humanReadableDateTime(start) }}
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
<q-item-label caption lines="1" v-if="end && simulationRunning">
|
<q-item-label caption lines="1" v-if="end && simulationRunning">
|
||||||
end: {{ humanReadableDateTime(end) }}
|
end: {{ humanReadableDateTime(end) }}
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
<q-item-label caption lines="1" v-else> delay: {{ delay }} seconds </q-item-label>
|
<q-item-label caption lines="1" v-if="!active && !end">
|
||||||
|
delay: {{ delay }} seconds
|
||||||
|
</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-item-section
|
<q-item-section
|
||||||
side
|
side
|
||||||
@@ -400,13 +397,14 @@ onUnmounted(() => {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<q-icon class="drag-handle" v-html="iconElement.outerHTML" />
|
<q-icon :class="{ 'drag-handle': canDrag }" v-html="iconElement.outerHTML" />
|
||||||
<q-item-label caption lines="1" v-if="simulationRunning">
|
<q-item-label caption lines="1" v-if="simulationRunning">
|
||||||
{{ calculateDeltaTime }}
|
{{ calculateDeltaTime }}
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-separator spaced inset v-if="!isLast" />
|
<q-separator spaced inset v-if="!isLast" />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<style lang="sass" scoped>
|
<style lang="sass" scoped>
|
||||||
.past
|
.past
|
||||||
|
|||||||
@@ -1,36 +1,56 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useLeafletStore } from 'stores/leaflet';
|
|
||||||
import type { coords } from 'components/models';
|
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { socket } from 'boot/socketio';
|
|
||||||
import ConfirmCommandDialog from 'components/ConfirmCommandDiaglog.vue';
|
|
||||||
import { useQuasar } from 'quasar';
|
import { useQuasar } from 'quasar';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { useSocketioStore } from 'stores/socketio';
|
import { computed, ref } from 'vue';
|
||||||
import { ref } from 'vue';
|
|
||||||
|
import ConfirmCommandDialog from 'components/ConfirmCommandDiaglog.vue';
|
||||||
|
|
||||||
import { favorites } from 'constants/favorites';
|
import { favorites } from 'constants/favorites';
|
||||||
import { controls } from 'constants/controls';
|
import { controls } from 'constants/controls';
|
||||||
import type { DeviceCommands } from 'components/models';
|
|
||||||
|
import type { coords, DeviceCommands, TunneldCommands } from 'components/models';
|
||||||
|
|
||||||
|
import { useDeviceStore } from 'stores/device';
|
||||||
|
import { useLeafletStore } from 'stores/leaflet';
|
||||||
|
import { useSimulationStore } from 'stores/simulation';
|
||||||
|
import { useIcloudStore } from 'stores/icloud';
|
||||||
|
import { useTunneldStore } from 'stores/tunneld';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const $q = useQuasar();
|
const $q = useQuasar();
|
||||||
|
|
||||||
|
const deviceStore = useDeviceStore();
|
||||||
const leafletStore = useLeafletStore();
|
const leafletStore = useLeafletStore();
|
||||||
const socketStore = useSocketioStore();
|
const simulationStore = useSimulationStore();
|
||||||
|
const icloudStore = useIcloudStore();
|
||||||
|
const tunneldStore = useTunneldStore();
|
||||||
const { center, markerLatLng, zoom } = storeToRefs(leafletStore);
|
const { center, markerLatLng, zoom } = storeToRefs(leafletStore);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
simulationRunning,
|
simulationRunning,
|
||||||
simulationState,
|
simulationState,
|
||||||
simulationQueueLength,
|
simulationQueueLength,
|
||||||
icloudMonitor,
|
locationQueueOrder,
|
||||||
testMode,
|
testMode,
|
||||||
gpsNoise,
|
gpsNoise,
|
||||||
} = storeToRefs(socketStore);
|
currentLocation,
|
||||||
|
} = storeToRefs(simulationStore);
|
||||||
|
|
||||||
|
const { icloudMonitor } = storeToRefs(icloudStore);
|
||||||
|
|
||||||
const menuOpen = ref(false);
|
const menuOpen = ref(false);
|
||||||
const favoritesMap = favorites as Record<string, unknown>;
|
const favoritesMap = favorites as Record<string, unknown>;
|
||||||
|
|
||||||
|
const isLast = computed(() => {
|
||||||
|
if (locationQueueOrder.value && locationQueueOrder.value.length > 0 && currentLocation.value) {
|
||||||
|
const currentIndex = locationQueueOrder.value.indexOf(currentLocation.value.loc_id);
|
||||||
|
return locationQueueOrder.value.length - 1 === currentIndex;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
type ControlAction = {
|
type ControlAction = {
|
||||||
name: string;
|
name: string;
|
||||||
cmd: string;
|
cmd: string;
|
||||||
@@ -67,7 +87,7 @@ function handleFavClick(coords: coords) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleTestToggle() {
|
function handleTestToggle() {
|
||||||
const response = socketStore.simulationControl({
|
const response = simulationStore.simulationControl({
|
||||||
command: 'test-mode',
|
command: 'test-mode',
|
||||||
latitude: null,
|
latitude: null,
|
||||||
longitude: null,
|
longitude: null,
|
||||||
@@ -81,7 +101,9 @@ function handleTestToggle() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleGpsNoiseToggle() {
|
function handleGpsNoiseToggle() {
|
||||||
const response = socketStore.simulationControl({command: 'gps-noise', latitude: null,
|
const response = simulationStore.simulationControl({
|
||||||
|
command: 'gps-noise',
|
||||||
|
latitude: null,
|
||||||
longitude: null,
|
longitude: null,
|
||||||
loc_id: null,
|
loc_id: null,
|
||||||
delay: 0,
|
delay: 0,
|
||||||
@@ -106,7 +128,14 @@ function handleControlClick(cmdAttr: ControlAction) {
|
|||||||
let notType: string = 'positive';
|
let notType: string = 'positive';
|
||||||
let notMsg: string = '';
|
let notMsg: string = '';
|
||||||
try {
|
try {
|
||||||
const ack = socketStore.simulationControl({ command: cmdAttr['cmd'], latitude: null, longitude: null, loc_id: null, delay: cmdAttr.delay, address: null });
|
const ack = simulationStore.simulationControl({
|
||||||
|
command: cmdAttr['cmd'],
|
||||||
|
latitude: null,
|
||||||
|
longitude: null,
|
||||||
|
loc_id: null,
|
||||||
|
delay: cmdAttr.delay,
|
||||||
|
address: null,
|
||||||
|
});
|
||||||
if (ack.sts === 'error') {
|
if (ack.sts === 'error') {
|
||||||
notType = 'negative';
|
notType = 'negative';
|
||||||
}
|
}
|
||||||
@@ -126,15 +155,53 @@ function handleControlClick(cmdAttr: ControlAction) {
|
|||||||
$q.notify({ type: notType, message: notMsg });
|
$q.notify({ type: notType, message: notMsg });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cmdAttr.cmdClass === 'dev_cntrl_class') {
|
if (cmdAttr.cmdClass === 'dev_cntrl_class') {
|
||||||
socket.emit(
|
let notType: string = 'positive';
|
||||||
'device_control',
|
let notMsg: string = '';
|
||||||
{ command: cmdAttr.cmd as DeviceCommands, delay: 0 },
|
try {
|
||||||
(response) => {
|
const ack = deviceStore.deviceControl(cmdAttr.cmd as DeviceCommands, cmdAttr.delay);
|
||||||
console.log(response.command_status, response.command);
|
if (ack.sts === 'error') {
|
||||||
},
|
notType = 'negative';
|
||||||
);
|
}
|
||||||
|
if (ack.msg) {
|
||||||
|
notMsg = ack.msg;
|
||||||
|
}
|
||||||
|
} catch (error: unknown) {
|
||||||
|
notType = 'negative';
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error('Device Command ERROR: ', error.message);
|
||||||
|
notMsg = `Device Command Error: ${error.message}`;
|
||||||
|
} else {
|
||||||
|
console.error('Device Command Error: ', error);
|
||||||
|
notMsg = 'Device Command Error: Unknow error';
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
$q.notify({ type: notType, message: notMsg });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cmdAttr.cmdClass === 'tunneld_cntrl_class') {
|
||||||
|
let notType: string = 'positive';
|
||||||
|
let notMsg: string = '';
|
||||||
|
try {
|
||||||
|
const ack = tunneldStore.tunneldControl(cmdAttr.cmd as TunneldCommands);
|
||||||
|
if (ack.sts === 'error') {
|
||||||
|
notType = 'negative';
|
||||||
|
}
|
||||||
|
if (ack.msg) {
|
||||||
|
notMsg = ack.msg;
|
||||||
|
}
|
||||||
|
} catch (error: unknown) {
|
||||||
|
notType = 'negative';
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error('Tunneld Command ERROR: ', error.message);
|
||||||
|
notMsg = `Tunneld Command Error: ${error.message}`;
|
||||||
|
} else {
|
||||||
|
console.error('Tunneld Command Error: ', error);
|
||||||
|
notMsg = 'Tunneld Command Error: Unknow error';
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
$q.notify({ type: notType, message: notMsg });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.onCancel(() => {
|
.onCancel(() => {
|
||||||
@@ -148,7 +215,7 @@ function handleControlClick(cmdAttr: ControlAction) {
|
|||||||
let notType: string = 'positive';
|
let notType: string = 'positive';
|
||||||
let notMsg: string = '';
|
let notMsg: string = '';
|
||||||
try {
|
try {
|
||||||
const ack = socketStore.simulationControl({
|
const ack = simulationStore.simulationControl({
|
||||||
command: cmdAttr.cmd,
|
command: cmdAttr.cmd,
|
||||||
latitude: null,
|
latitude: null,
|
||||||
longitude: null,
|
longitude: null,
|
||||||
@@ -179,7 +246,7 @@ function handleControlClick(cmdAttr: ControlAction) {
|
|||||||
let notType: string = 'positive';
|
let notType: string = 'positive';
|
||||||
let notMsg: string = '';
|
let notMsg: string = '';
|
||||||
try {
|
try {
|
||||||
const ack = socketStore.icloudMonitorControl(cmdAttr.cmd);
|
const ack = icloudStore.icloudMonitorControl(cmdAttr.cmd);
|
||||||
if (ack.sts === 'error') {
|
if (ack.sts === 'error') {
|
||||||
notType = 'negative';
|
notType = 'negative';
|
||||||
}
|
}
|
||||||
@@ -201,13 +268,52 @@ function handleControlClick(cmdAttr: ControlAction) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (cmdAttr.cmdClass === 'dev_cntrl_class') {
|
if (cmdAttr.cmdClass === 'dev_cntrl_class') {
|
||||||
socket.emit(
|
let notType: string = 'positive';
|
||||||
'device_control',
|
let notMsg: string = '';
|
||||||
{ command: cmdAttr.cmd as DeviceCommands, delay: 0 },
|
try {
|
||||||
(response) => {
|
const ack = deviceStore.deviceControl(cmdAttr.cmd as DeviceCommands, cmdAttr.delay);
|
||||||
console.log(response.command_status, response.command);
|
if (ack.sts === 'error') {
|
||||||
},
|
notType = 'negative';
|
||||||
);
|
}
|
||||||
|
if (ack.msg) {
|
||||||
|
notMsg = ack.msg;
|
||||||
|
}
|
||||||
|
} catch (error: unknown) {
|
||||||
|
notType = 'negative';
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error('Device Command ERROR: ', error.message);
|
||||||
|
notMsg = `Device Command Error: ${error.message}`;
|
||||||
|
} else {
|
||||||
|
console.error('Device Command Error: ', error);
|
||||||
|
notMsg = 'Device Command Error: Unknow error';
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
$q.notify({ type: notType, message: notMsg });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cmdAttr.cmdClass === 'tunneld_cntrl_class') {
|
||||||
|
let notType: string = 'positive';
|
||||||
|
let notMsg: string = '';
|
||||||
|
try {
|
||||||
|
const ack = tunneldStore.tunneldControl(cmdAttr.cmd as TunneldCommands);
|
||||||
|
if (ack.sts === 'error') {
|
||||||
|
notType = 'negative';
|
||||||
|
}
|
||||||
|
if (ack.msg) {
|
||||||
|
notMsg = ack.msg;
|
||||||
|
}
|
||||||
|
} catch (error: unknown) {
|
||||||
|
notType = 'negative';
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error('Tunneld Command ERROR: ', error.message);
|
||||||
|
notMsg = `Tunneld Command Error: ${error.message}`;
|
||||||
|
} else {
|
||||||
|
console.error('Tunneld Command Error: ', error);
|
||||||
|
notMsg = 'Tunneld Command Error: Unknow error';
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
$q.notify({ type: notType, message: notMsg });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -227,20 +333,23 @@ function handleControlClick(cmdAttr: ControlAction) {
|
|||||||
class="q-mr-sm"
|
class="q-mr-sm"
|
||||||
/>
|
/>
|
||||||
-->
|
-->
|
||||||
|
<q-avatar>
|
||||||
|
<q-img src="~assets/simloc_logo2.png" class="logo" />
|
||||||
|
</q-avatar>
|
||||||
<q-separator dark inset />
|
<q-separator dark inset />
|
||||||
<q-space />
|
<q-space />
|
||||||
<q-btn
|
<q-btn
|
||||||
:icon-right="menuOpen ? 'arrow_drop_up' : 'arrow_drop_down'"
|
:icon-right="menuOpen ? 'mdi-menu-up' : 'mdi-menu-down'"
|
||||||
stretch
|
stretch
|
||||||
flat
|
flat
|
||||||
label="Favorites"
|
label="Favorites"
|
||||||
v-if="route.name === 'Leaflet'"
|
v-if="route.name === 'Leaflet'"
|
||||||
|
class="text-weight-bold"
|
||||||
>
|
>
|
||||||
<q-menu @show="menuOpen = true" @hide="menuOpen = false" anchor="bottom end" self="top end">
|
<q-menu @show="menuOpen = true" @hide="menuOpen = false" anchor="bottom end" self="top end">
|
||||||
<q-list dense dark>
|
<q-list dark>
|
||||||
<template v-for="(favObj, favId) in favoritesMap" :key="favId">
|
<template v-for="(favObj, favId) in favoritesMap" :key="favId">
|
||||||
<q-item
|
<q-item
|
||||||
dense
|
|
||||||
dark
|
dark
|
||||||
v-if="hasCoords(favObj)"
|
v-if="hasCoords(favObj)"
|
||||||
clickable
|
clickable
|
||||||
@@ -255,7 +364,7 @@ function handleControlClick(cmdAttr: ControlAction) {
|
|||||||
<q-item-label>{{ favObj.name }}</q-item-label>
|
<q-item-label>{{ favObj.name }}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item v-else-if="hasSubitems(favObj)" clickable v-ripple dense dark>
|
<q-item v-else-if="hasSubitems(favObj)" clickable v-ripple dark>
|
||||||
<q-item-section avatar>
|
<q-item-section avatar>
|
||||||
<q-avatar :icon="favObj.icon" color="secondary" size="sm" text-color="black" />
|
<q-avatar :icon="favObj.icon" color="secondary" size="sm" text-color="black" />
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
@@ -263,12 +372,11 @@ function handleControlClick(cmdAttr: ControlAction) {
|
|||||||
<q-item-label>{{ favObj.name }}</q-item-label>
|
<q-item-label>{{ favObj.name }}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-item-section side>
|
<q-item-section side>
|
||||||
<q-icon name="keyboard_arrow_right" />
|
<q-icon name="mdi-chevron-right" />
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-menu anchor="bottom start" self="bottom end">
|
<q-menu anchor="bottom start" self="bottom end">
|
||||||
<q-list dense dark>
|
<q-list dark>
|
||||||
<q-item
|
<q-item
|
||||||
dense
|
|
||||||
dark
|
dark
|
||||||
v-for="(favSubObj, favSubId) in favObj.subitems"
|
v-for="(favSubObj, favSubId) in favObj.subitems"
|
||||||
:key="favSubId"
|
:key="favSubId"
|
||||||
@@ -296,11 +404,18 @@ function handleControlClick(cmdAttr: ControlAction) {
|
|||||||
</q-list>
|
</q-list>
|
||||||
</q-menu>
|
</q-menu>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
<q-btn-dropdown stretch flat label="Controls">
|
<q-btn-dropdown class="text-weight-bold" stretch flat label="Controls">
|
||||||
<q-list dense dark>
|
<q-list dark>
|
||||||
<q-item-label header>Simulation Controls</q-item-label>
|
<q-expansion-item
|
||||||
<q-item dense dark tag="label" v-ripple>
|
group="controls"
|
||||||
<q-item-section avatar>
|
dense-toggle
|
||||||
|
default-opened
|
||||||
|
icon="mdi-map-marker"
|
||||||
|
label="Simulation Controls"
|
||||||
|
color="accent"
|
||||||
|
>
|
||||||
|
<q-item dark tag="label" v-ripple>
|
||||||
|
<q-item-section avatar class="q-pl-lg">
|
||||||
<q-toggle
|
<q-toggle
|
||||||
v-model="testMode"
|
v-model="testMode"
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -308,18 +423,19 @@ function handleControlClick(cmdAttr: ControlAction) {
|
|||||||
@update:model-value="handleTestToggle"
|
@update:model-value="handleTestToggle"
|
||||||
dark
|
dark
|
||||||
dense
|
dense
|
||||||
|
:disabled="simulationRunning"
|
||||||
/>
|
/>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label>Test Mode</q-item-label>
|
<q-item-label>Test Mode</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item dense dark tag="label" v-ripple>
|
<q-item dark tag="label" v-ripple>
|
||||||
<q-item-section avatar>
|
<q-item-section avatar class="q-pl-lg">
|
||||||
<q-toggle
|
<q-toggle
|
||||||
v-model="gpsNoise"
|
v-model="gpsNoise"
|
||||||
size="sm"
|
size="sm"
|
||||||
color="brown"
|
color="accent"
|
||||||
@update:model-value="handleGpsNoiseToggle"
|
@update:model-value="handleGpsNoiseToggle"
|
||||||
dark
|
dark
|
||||||
dense
|
dense
|
||||||
@@ -330,7 +446,6 @@ function handleControlClick(cmdAttr: ControlAction) {
|
|||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item
|
<q-item
|
||||||
dense
|
|
||||||
dark
|
dark
|
||||||
v-if="!simulationRunning"
|
v-if="!simulationRunning"
|
||||||
clickable
|
clickable
|
||||||
@@ -338,7 +453,7 @@ function handleControlClick(cmdAttr: ControlAction) {
|
|||||||
v-close-popup
|
v-close-popup
|
||||||
@click="handleControlClick(controls.simulation.start)"
|
@click="handleControlClick(controls.simulation.start)"
|
||||||
>
|
>
|
||||||
<q-item-section avatar>
|
<q-item-section avatar class="q-pl-lg">
|
||||||
<q-avatar
|
<q-avatar
|
||||||
:icon="controls.simulation.start.icon"
|
:icon="controls.simulation.start.icon"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
@@ -351,7 +466,6 @@ function handleControlClick(cmdAttr: ControlAction) {
|
|||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item
|
<q-item
|
||||||
dense
|
|
||||||
dark
|
dark
|
||||||
v-if="simulationState === 'RUNNING' && simulationRunning"
|
v-if="simulationState === 'RUNNING' && simulationRunning"
|
||||||
clickable
|
clickable
|
||||||
@@ -359,7 +473,7 @@ function handleControlClick(cmdAttr: ControlAction) {
|
|||||||
v-close-popup
|
v-close-popup
|
||||||
@click="handleControlClick(controls.simulation.pause)"
|
@click="handleControlClick(controls.simulation.pause)"
|
||||||
>
|
>
|
||||||
<q-item-section avatar>
|
<q-item-section avatar class="q-pl-lg">
|
||||||
<q-avatar
|
<q-avatar
|
||||||
:icon="controls.simulation.pause.icon"
|
:icon="controls.simulation.pause.icon"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
@@ -372,7 +486,6 @@ function handleControlClick(cmdAttr: ControlAction) {
|
|||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item
|
<q-item
|
||||||
dense
|
|
||||||
dark
|
dark
|
||||||
v-if="simulationState === 'PAUSED'"
|
v-if="simulationState === 'PAUSED'"
|
||||||
clickable
|
clickable
|
||||||
@@ -380,7 +493,7 @@ function handleControlClick(cmdAttr: ControlAction) {
|
|||||||
v-close-popup
|
v-close-popup
|
||||||
@click="handleControlClick(controls.simulation.resume)"
|
@click="handleControlClick(controls.simulation.resume)"
|
||||||
>
|
>
|
||||||
<q-item-section avatar>
|
<q-item-section avatar class="q-pl-lg">
|
||||||
<q-avatar
|
<q-avatar
|
||||||
:icon="controls.simulation.resume.icon"
|
:icon="controls.simulation.resume.icon"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
@@ -393,15 +506,14 @@ function handleControlClick(cmdAttr: ControlAction) {
|
|||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item
|
<q-item
|
||||||
dense
|
|
||||||
dark
|
dark
|
||||||
v-if="simulationQueueLength && simulationQueueLength > 0"
|
v-if="simulationQueueLength && simulationQueueLength > 0 && !isLast"
|
||||||
clickable
|
clickable
|
||||||
v-ripple
|
v-ripple
|
||||||
v-close-popup
|
v-close-popup
|
||||||
@click="handleControlClick(controls.simulation.clear)"
|
@click="handleControlClick(controls.simulation.clear)"
|
||||||
>
|
>
|
||||||
<q-item-section avatar>
|
<q-item-section avatar class="q-pl-lg">
|
||||||
<q-avatar
|
<q-avatar
|
||||||
:icon="controls.simulation.clear.icon"
|
:icon="controls.simulation.clear.icon"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
@@ -414,7 +526,26 @@ function handleControlClick(cmdAttr: ControlAction) {
|
|||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item
|
<q-item
|
||||||
dense
|
dark
|
||||||
|
v-if="simulationQueueLength && simulationQueueLength > 0"
|
||||||
|
clickable
|
||||||
|
v-ripple
|
||||||
|
v-close-popup
|
||||||
|
@click="handleControlClick(controls.simulation.reset)"
|
||||||
|
>
|
||||||
|
<q-item-section avatar class="q-pl-lg">
|
||||||
|
<q-avatar
|
||||||
|
:icon="controls.simulation.reset.icon"
|
||||||
|
color="secondary"
|
||||||
|
text-color="black"
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label> {{ controls.simulation.reset.name }} </q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item
|
||||||
dark
|
dark
|
||||||
v-if="simulationRunning"
|
v-if="simulationRunning"
|
||||||
clickable
|
clickable
|
||||||
@@ -422,7 +553,7 @@ function handleControlClick(cmdAttr: ControlAction) {
|
|||||||
v-close-popup
|
v-close-popup
|
||||||
@click="handleControlClick(controls.simulation.end)"
|
@click="handleControlClick(controls.simulation.end)"
|
||||||
>
|
>
|
||||||
<q-item-section avatar>
|
<q-item-section avatar class="q-pl-lg">
|
||||||
<q-avatar
|
<q-avatar
|
||||||
:icon="controls.simulation.end.icon"
|
:icon="controls.simulation.end.icon"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
@@ -434,10 +565,15 @@ function handleControlClick(cmdAttr: ControlAction) {
|
|||||||
<q-item-label> {{ controls.simulation.end.name }} </q-item-label>
|
<q-item-label> {{ controls.simulation.end.name }} </q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-separator spaced />
|
</q-expansion-item>
|
||||||
<q-item-label header>iCloud Monitor Controls</q-item-label>
|
<q-expansion-item
|
||||||
|
group="controls"
|
||||||
|
icon="mdi-cloud"
|
||||||
|
dense-toggle
|
||||||
|
label="iCloud Monitor Controls"
|
||||||
|
color="accent"
|
||||||
|
>
|
||||||
<q-item
|
<q-item
|
||||||
dense
|
|
||||||
dark
|
dark
|
||||||
v-if="!icloudMonitor"
|
v-if="!icloudMonitor"
|
||||||
clickable
|
clickable
|
||||||
@@ -445,7 +581,7 @@ function handleControlClick(cmdAttr: ControlAction) {
|
|||||||
v-close-popup
|
v-close-popup
|
||||||
@click="handleControlClick(controls.icloudmonitor.start)"
|
@click="handleControlClick(controls.icloudmonitor.start)"
|
||||||
>
|
>
|
||||||
<q-item-section avatar>
|
<q-item-section avatar class="q-pl-lg">
|
||||||
<q-avatar
|
<q-avatar
|
||||||
:icon="controls.icloudmonitor.start.icon"
|
:icon="controls.icloudmonitor.start.icon"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
@@ -458,7 +594,6 @@ function handleControlClick(cmdAttr: ControlAction) {
|
|||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item
|
<q-item
|
||||||
dense
|
|
||||||
dark
|
dark
|
||||||
v-if="icloudMonitor"
|
v-if="icloudMonitor"
|
||||||
clickable
|
clickable
|
||||||
@@ -467,7 +602,7 @@ function handleControlClick(cmdAttr: ControlAction) {
|
|||||||
@click="handleControlClick(controls.icloudmonitor.stop)"
|
@click="handleControlClick(controls.icloudmonitor.stop)"
|
||||||
size="sm"
|
size="sm"
|
||||||
>
|
>
|
||||||
<q-item-section avatar>
|
<q-item-section avatar class="q-pl-lg">
|
||||||
<q-avatar
|
<q-avatar
|
||||||
:icon="controls.icloudmonitor.stop.icon"
|
:icon="controls.icloudmonitor.stop.icon"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
@@ -479,17 +614,163 @@ function handleControlClick(cmdAttr: ControlAction) {
|
|||||||
<q-item-label> {{ controls.icloudmonitor.stop.name }} </q-item-label>
|
<q-item-label> {{ controls.icloudmonitor.stop.name }} </q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-separator spaced />
|
</q-expansion-item>
|
||||||
<q-item-label header>Device Controls</q-item-label>
|
<q-expansion-item group="controls" dense-toggle icon="mdi-subway" label="Tunneld Controls">
|
||||||
|
<q-item
|
||||||
|
v-if="!tunneldStore.tunnelConnected"
|
||||||
|
dark
|
||||||
|
clickable
|
||||||
|
v-ripple
|
||||||
|
v-close-popup
|
||||||
|
@click="handleControlClick(controls.tunneld.start)"
|
||||||
|
>
|
||||||
|
<q-item-section avatar class="q-pl-lg">
|
||||||
|
<q-avatar
|
||||||
|
:icon="controls.tunneld.start.icon"
|
||||||
|
color="secondary"
|
||||||
|
text-color="black"
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label> {{ controls.tunneld.start.name }} </q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item
|
||||||
|
v-if="!tunneldStore.tunneldWatcher"
|
||||||
|
dark
|
||||||
|
clickable
|
||||||
|
v-ripple
|
||||||
|
v-close-popup
|
||||||
|
@click="handleControlClick(controls.tunneld.start_watcher)"
|
||||||
|
>
|
||||||
|
<q-item-section avatar class="q-pl-lg">
|
||||||
|
<q-avatar
|
||||||
|
:icon="controls.tunneld.start_watcher.icon"
|
||||||
|
color="secondary"
|
||||||
|
text-color="black"
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label> {{ controls.tunneld.start_watcher.name }} </q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item
|
||||||
|
v-if="tunneldStore.tunnelConnected"
|
||||||
|
dark
|
||||||
|
clickable
|
||||||
|
v-ripple
|
||||||
|
v-close-popup
|
||||||
|
@click="handleControlClick(controls.tunneld.restart)"
|
||||||
|
>
|
||||||
|
<q-item-section avatar class="q-pl-lg">
|
||||||
|
<q-avatar
|
||||||
|
:icon="controls.tunneld.restart.icon"
|
||||||
|
color="secondary"
|
||||||
|
text-color="black"
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label> {{ controls.tunneld.restart.name }} </q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item
|
||||||
|
v-if="tunneldStore.tunnelConnected"
|
||||||
|
dark
|
||||||
|
clickable
|
||||||
|
v-ripple
|
||||||
|
v-close-popup
|
||||||
|
@click="handleControlClick(controls.tunneld.clear)"
|
||||||
|
>
|
||||||
|
<q-item-section avatar class="q-pl-lg">
|
||||||
|
<q-avatar
|
||||||
|
:icon="controls.tunneld.clear.icon"
|
||||||
|
color="secondary"
|
||||||
|
text-color="black"
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label> {{ controls.tunneld.clear.name }} </q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item
|
||||||
|
v-if="tunneldStore.tunnelConnected"
|
||||||
|
dark
|
||||||
|
clickable
|
||||||
|
v-ripple
|
||||||
|
v-close-popup
|
||||||
|
@click="handleControlClick(controls.tunneld.cancel)"
|
||||||
|
>
|
||||||
|
<q-item-section avatar class="q-pl-lg">
|
||||||
|
<q-avatar
|
||||||
|
:icon="controls.tunneld.cancel.icon"
|
||||||
|
color="secondary"
|
||||||
|
text-color="black"
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label> {{ controls.tunneld.cancel.name }} </q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item
|
||||||
|
v-if="tunneldStore.tunneldWatcher"
|
||||||
|
dark
|
||||||
|
clickable
|
||||||
|
v-ripple
|
||||||
|
v-close-popup
|
||||||
|
@click="handleControlClick(controls.tunneld.end_watcher)"
|
||||||
|
>
|
||||||
|
<q-item-section avatar class="q-pl-lg">
|
||||||
|
<q-avatar
|
||||||
|
:icon="controls.tunneld.end_watcher.icon"
|
||||||
|
color="secondary"
|
||||||
|
text-color="black"
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label> {{ controls.tunneld.end_watcher.name }} </q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item
|
||||||
|
v-if="tunneldStore.tunnelConnected"
|
||||||
|
dark
|
||||||
|
clickable
|
||||||
|
v-ripple
|
||||||
|
v-close-popup
|
||||||
|
@click="handleControlClick(controls.tunneld.shutdown)"
|
||||||
|
>
|
||||||
|
<q-item-section avatar class="q-pl-lg">
|
||||||
|
<q-avatar
|
||||||
|
:icon="controls.tunneld.shutdown.icon"
|
||||||
|
color="secondary"
|
||||||
|
text-color="black"
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label> {{ controls.tunneld.shutdown.name }} </q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-expansion-item>
|
||||||
|
<q-expansion-item
|
||||||
|
dense-toggle
|
||||||
|
icon="mdi-raspberry-pi"
|
||||||
|
label="Device Controls"
|
||||||
|
group="controls"
|
||||||
|
>
|
||||||
<q-item
|
<q-item
|
||||||
dense
|
|
||||||
dark
|
dark
|
||||||
clickable
|
clickable
|
||||||
v-ripple
|
v-ripple
|
||||||
v-close-popup
|
v-close-popup
|
||||||
@click="handleControlClick(controls.device.reboot)"
|
@click="handleControlClick(controls.device.reboot)"
|
||||||
>
|
>
|
||||||
<q-item-section avatar>
|
<q-item-section avatar class="q-pl-lg">
|
||||||
<q-avatar
|
<q-avatar
|
||||||
:icon="controls.device.reboot.icon"
|
:icon="controls.device.reboot.icon"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
@@ -502,14 +783,13 @@ function handleControlClick(cmdAttr: ControlAction) {
|
|||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item
|
<q-item
|
||||||
dense
|
|
||||||
dark
|
dark
|
||||||
clickable
|
clickable
|
||||||
v-ripple
|
v-ripple
|
||||||
v-close-popup
|
v-close-popup
|
||||||
@click="handleControlClick(controls.device.shutdown)"
|
@click="handleControlClick(controls.device.shutdown)"
|
||||||
>
|
>
|
||||||
<q-item-section avatar>
|
<q-item-section avatar class="q-pl-lg">
|
||||||
<q-avatar
|
<q-avatar
|
||||||
:icon="controls.device.shutdown.icon"
|
:icon="controls.device.shutdown.icon"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
@@ -521,9 +801,21 @@ function handleControlClick(cmdAttr: ControlAction) {
|
|||||||
<q-item-label> {{ controls.device.shutdown.name }} </q-item-label>
|
<q-item-label> {{ controls.device.shutdown.name }} </q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
|
</q-expansion-item>
|
||||||
</q-list>
|
</q-list>
|
||||||
</q-btn-dropdown>
|
</q-btn-dropdown>
|
||||||
</q-toolbar>
|
</q-toolbar>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped lang="sass">
|
||||||
|
|
||||||
|
.logo
|
||||||
|
transition: transform 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275)
|
||||||
|
display: inline-block
|
||||||
|
cursor: pointer
|
||||||
|
|
||||||
|
|
||||||
|
.logo:hover
|
||||||
|
transform: scale(1.3) translateY(-5px)
|
||||||
|
filter: drop-shadow(0 10px 15px rgba(0, 0, 0, 0.3))
|
||||||
|
</style>
|
||||||
|
|||||||
110
src/components/NestedKnob.vue
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
<template>
|
||||||
|
<div class="q-pa-md flex flex-center">
|
||||||
|
<!-- Outer Container for positioning -->
|
||||||
|
<div class="relative-position flex flex-center">
|
||||||
|
<!-- 1. Outer Knob (e.g., Value A) -->
|
||||||
|
<q-knob
|
||||||
|
v-model="delayHours"
|
||||||
|
:min="0"
|
||||||
|
:max="24"
|
||||||
|
size="55px"
|
||||||
|
color="accent"
|
||||||
|
track-color="primary"
|
||||||
|
class="absolute"
|
||||||
|
:thickness="0.2"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 2. Middle Knob (e.g., Value B) -->
|
||||||
|
<q-knob
|
||||||
|
v-model="delayMinutes"
|
||||||
|
:min="0"
|
||||||
|
:max="60"
|
||||||
|
size="40px"
|
||||||
|
color="accent"
|
||||||
|
track-color="primary"
|
||||||
|
class="absolute"
|
||||||
|
:thickness="0.2"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 3. Inner Knob (e.g., Value C) -->
|
||||||
|
<q-knob
|
||||||
|
v-model="delaySeconds"
|
||||||
|
:min="0"
|
||||||
|
:max="60"
|
||||||
|
size="25px"
|
||||||
|
color="accent"
|
||||||
|
track-color="secondary"
|
||||||
|
class="absolute"
|
||||||
|
:thickness="0.2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ hhMmSs }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { useSimulationStore } from 'stores/simulation';
|
||||||
|
|
||||||
|
const simulationStore = useSimulationStore();
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
loc_id: { type: String, required: true },
|
||||||
|
delay: { type: Number, required: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
const delayHours = computed({
|
||||||
|
get: () => Math.floor(props.delay / 3600),
|
||||||
|
set: (val) => {
|
||||||
|
const hrSeconds: number = val * 3600;
|
||||||
|
const minSeconds: number = delayMinutes.value * 60;
|
||||||
|
const secSeconds: number = delaySeconds.value;
|
||||||
|
const totalSeconds: number = hrSeconds + minSeconds + secSeconds;
|
||||||
|
simulationStore.updateLocationMark(props.loc_id, 'delay', totalSeconds);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const delayMinutes = computed({
|
||||||
|
get: () => Math.floor((props.delay % 3600) / 60),
|
||||||
|
set: (val) => {
|
||||||
|
const hrSeconds: number = delayHours.value * 3600;
|
||||||
|
const minSeconds: number = val * 60;
|
||||||
|
const secSeconds: number = delaySeconds.value;
|
||||||
|
const totalSeconds: number = hrSeconds + minSeconds + secSeconds;
|
||||||
|
simulationStore.updateLocationMark(props.loc_id, 'delay', totalSeconds);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const delaySeconds = computed({
|
||||||
|
get: () => Math.floor(props.delay % 60),
|
||||||
|
set: (val) => {
|
||||||
|
const hrSeconds: number = delayHours.value * 3600;
|
||||||
|
const minSeconds: number = delayMinutes.value * 60;
|
||||||
|
const secSeconds: number = val;
|
||||||
|
const totalSeconds: number = hrSeconds + minSeconds + secSeconds;
|
||||||
|
simulationStore.updateLocationMark(props.loc_id, 'delay', totalSeconds);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
const secondsToHhMmSs = (seconds: number) => {
|
||||||
|
const hours = Math.floor(seconds / 3600);
|
||||||
|
const minutes = Math.floor((seconds % 3600) / 60);
|
||||||
|
const remainingSeconds = seconds % 60;
|
||||||
|
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${remainingSeconds
|
||||||
|
.toString()
|
||||||
|
.padStart(2, '0')}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
const hhMmSs = computed(() => {
|
||||||
|
if (delayHours.value > 0)
|
||||||
|
return `${delayHours.value.toString().padStart(2, '0')}:${delayMinutes.value.toString().padStart(2, '0')}:${delaySeconds.value.toString().padStart(2, '0')}`;
|
||||||
|
if (delayMinutes.value > 0)
|
||||||
|
return `${delayMinutes.value.toString().padStart(2, '0')}:${delaySeconds.value.toString().padStart(2, '0')}`;
|
||||||
|
else return `${delaySeconds.value.toString().padStart(2, '0')}`;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="sass"></style>
|
||||||
@@ -2,15 +2,27 @@
|
|||||||
<q-dialog ref="dlgRef" persistent>
|
<q-dialog ref="dlgRef" persistent>
|
||||||
<q-card class="bg-dark text-grey-1 add-loc-card q-pa-sm">
|
<q-card class="bg-dark text-grey-1 add-loc-card q-pa-sm">
|
||||||
<q-toolbar>
|
<q-toolbar>
|
||||||
<q-avatar icon="add_location" color="primary" text-color="white" />
|
<q-avatar icon="mdi-map-marker" color="primary" text-color="white" />
|
||||||
<q-toolbar-title>Add Location to Queue</q-toolbar-title>
|
<q-toolbar-title>Add Location to Queue</q-toolbar-title>
|
||||||
</q-toolbar>
|
</q-toolbar>
|
||||||
<q-card-section class="q-ml-lg">
|
<q-card-section class="q-ml-lg">
|
||||||
<div class="q-mb-sm">Are you sure you want to set location to:</div>
|
<div>Are you sure you want to set location to:</div>
|
||||||
<div class="q-ml-lg">
|
<div class="q-ml-md">
|
||||||
<formatted-address :address="address" />
|
<div class="relative-position">
|
||||||
|
<div>
|
||||||
|
<transition
|
||||||
|
appear
|
||||||
|
enter-active-class="animated fadeIn"
|
||||||
|
leave-active-class="animated fadeOut"
|
||||||
|
>
|
||||||
|
<formatted-address :address="address" v-if="!loading" />
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
<q-inner-loading :showing="loading" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<q-input
|
<q-input
|
||||||
class="q-mt-sm"
|
class="q-mt-md"
|
||||||
style="max-width: 150px"
|
style="max-width: 150px"
|
||||||
v-model.number="delay"
|
v-model.number="delay"
|
||||||
filled
|
filled
|
||||||
@@ -19,13 +31,30 @@
|
|||||||
type="number"
|
type="number"
|
||||||
suffix="seconds"
|
suffix="seconds"
|
||||||
color="grey-4"
|
color="grey-4"
|
||||||
|
autofocus
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-separator />
|
<q-separator />
|
||||||
<q-card-actions align="right">
|
<q-card-actions align="between">
|
||||||
|
<div class="text-yellow">
|
||||||
|
<q-checkbox
|
||||||
|
class="cursor-pointer"
|
||||||
|
v-model="isFavorite"
|
||||||
|
checked-icon="mdi-star"
|
||||||
|
unchecked-icon="mdi-star-outline"
|
||||||
|
indeterminate-icon="mdi-help"
|
||||||
|
color="yellow"
|
||||||
|
@click="handleFavoriteClick"
|
||||||
|
:label="favoriteName"
|
||||||
|
>
|
||||||
|
<q-tooltip>Favorite</q-tooltip>
|
||||||
|
</q-checkbox>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
<q-btn flat label="OK" @click="onOkClick" />
|
<q-btn flat label="OK" @click="onOkClick" />
|
||||||
<q-btn flat label="Cancel" @click="onDialogCancel" />
|
<q-btn flat label="Cancel" @click="onDialogCancel" />
|
||||||
|
</div>
|
||||||
</q-card-actions>
|
</q-card-actions>
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
@@ -34,37 +63,81 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useDialogPluginComponent } from 'quasar';
|
import { useDialogPluginComponent } from 'quasar';
|
||||||
import { reverseGeocodeRateLimited } from 'functions/reverseGeocodeSocket';
|
import { reverseGeocodeRateLimited } from 'functions/reverseGeocodeSocket';
|
||||||
import { ref, onMounted } from 'vue';
|
import { ref, onMounted, computed } from 'vue';
|
||||||
|
import { useQuasar } from 'quasar';
|
||||||
import FormattedAddress from 'components/FormattedAddress.vue';
|
import FormattedAddress from 'components/FormattedAddress.vue';
|
||||||
|
import EditFavoriteDialog from 'components/EditFavoriteDialog.vue';
|
||||||
|
import type { NominatimResponse } from 'components/models';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
lat: { type: Number, required: true },
|
lat: { type: Number, required: true },
|
||||||
lng: { type: Number, required: true },
|
lng: { type: Number, required: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const $q = useQuasar();
|
||||||
const loading = ref(true);
|
const loading = ref(true);
|
||||||
const delay = ref(0);
|
const delay = ref(300);
|
||||||
|
|
||||||
defineEmits([...useDialogPluginComponent.emits]);
|
defineEmits([...useDialogPluginComponent.emits]);
|
||||||
|
|
||||||
const { dialogRef: dlgRef, onDialogOK, onDialogCancel } = useDialogPluginComponent();
|
const { dialogRef: dlgRef, onDialogOK, onDialogCancel } = useDialogPluginComponent();
|
||||||
|
|
||||||
const address = ref();
|
const address = ref<NominatimResponse>();
|
||||||
|
const isFavorite = ref(false);
|
||||||
|
const favorite = ref();
|
||||||
|
const favoriteName = computed(() => favorite.value?.name ?? '');
|
||||||
|
|
||||||
|
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
try {
|
try {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const response = await reverseGeocodeRateLimited(props.lat, props.lng);
|
const response = await reverseGeocodeRateLimited(props.lat, props.lng);
|
||||||
console.log('reverse geocode response: ', response);
|
console.log('reverse geocode response: ', response);
|
||||||
address.value = response;
|
address.value = response.address;
|
||||||
|
if (response.favorite) {
|
||||||
|
favorite.value = response.favorite;
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching reverse geocode:', error);
|
console.error('Error fetching reverse geocode:', error);
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
|
await sleep(500);
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function handleFavoriteClick() {
|
||||||
|
if (!favorite.value) {
|
||||||
|
favorite.value = {
|
||||||
|
name: '',
|
||||||
|
icon: '',
|
||||||
|
category: '',
|
||||||
|
latitude: props.lat,
|
||||||
|
longitude: props.lng,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
$q.dialog({
|
||||||
|
component: EditFavoriteDialog,
|
||||||
|
componentProps: {
|
||||||
|
lat: props.lat,
|
||||||
|
lng: props.lng,
|
||||||
|
fav: favorite.value,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.onOk(({ data }) => {
|
||||||
|
favorite.value = data;
|
||||||
|
})
|
||||||
|
.onCancel(() => {
|
||||||
|
console.log('Dialog cancelled');
|
||||||
|
})
|
||||||
|
.onDismiss(() => {
|
||||||
|
console.log('Dialog dismissed');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function onOkClick() {
|
function onOkClick() {
|
||||||
onDialogOK({ delay: delay.value, address: address.value });
|
onDialogOK({ delay: delay.value, address: address.value });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +1,21 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { socket } from 'boot/socketio';
|
import { socket } from 'boot/socketio';
|
||||||
import { useQuasar } from 'quasar';
|
import { computed, ref } from 'vue';
|
||||||
import { computed, ref, watch } from 'vue';
|
import { useSocketStore } from 'stores/socket';
|
||||||
import { useSocketioStore } from 'stores/socketio';
|
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import type { ClientToServerEvents } from 'components/models';
|
import type { ClientToServerEvents } from 'components/models';
|
||||||
|
|
||||||
const socketioStore = useSocketioStore();
|
const socketStore = useSocketStore();
|
||||||
const $q = useQuasar();
|
|
||||||
const msgInput = ref('');
|
|
||||||
const sockEvent = ref('');
|
const sockEvent = ref('');
|
||||||
const eventArgs = ref('');
|
const eventArgs = ref('');
|
||||||
|
|
||||||
const { sockConnected, messageList } = storeToRefs(socketioStore);
|
const { sockConnected } = storeToRefs(socketStore);
|
||||||
|
|
||||||
const sockStatColor = computed(() => {
|
const sockStatColor = computed(() => {
|
||||||
return sockConnected.value ? 'green' : 'red';
|
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() {
|
function handleEmit() {
|
||||||
const event = sockEvent.value;
|
const event = sockEvent.value;
|
||||||
const jsonArgs = eventArgs.value;
|
const jsonArgs = eventArgs.value;
|
||||||
@@ -37,33 +25,14 @@ function handleEmit() {
|
|||||||
sockEvent.value = '';
|
sockEvent.value = '';
|
||||||
eventArgs.value = '';
|
eventArgs.value = '';
|
||||||
}
|
}
|
||||||
|
const timeWithSeconds = ref('00:00:45');
|
||||||
watch(
|
|
||||||
messageList,
|
|
||||||
(newVal: string[], oldVal: string[]) => {
|
|
||||||
const newMsg = newVal[newVal.length - 1] ?? '';
|
|
||||||
console.log('New message received: ', newMsg);
|
|
||||||
console.log('Past List', oldVal);
|
|
||||||
$q.notify(newMsg);
|
|
||||||
},
|
|
||||||
{ deep: true },
|
|
||||||
);
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-center col q-ma-lg q-gutter-md">
|
<div class="flex flex-center col q-ma-lg q-gutter-md">
|
||||||
<q-btn icom="webhook"><q-badge floating :color="sockStatColor" rounded></q-badge></q-btn>
|
<q-btn icom="webhook"><q-badge floating :color="sockStatColor" rounded></q-badge></q-btn>
|
||||||
<q-btn label="Connect" @click="socketioStore.connect()" />
|
<q-btn label="Connect" @click="socketStore.connect()" />
|
||||||
<q-btn label="Disconnect" @click="socketioStore.disconnect()" />
|
<q-btn label="Disconnect" @click="socketStore.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>
|
||||||
<div class="flex flex-center col q-ma-lg q-gutter-md">
|
<div class="flex flex-center col q-ma-lg q-gutter-md">
|
||||||
<div class="text-h3">SocketIO Functions</div>
|
<div class="text-h3">SocketIO Functions</div>
|
||||||
@@ -73,5 +42,7 @@ watch(
|
|||||||
<q-btn label="Emit" @click="handleEmit" />
|
<q-btn label="Emit" @click="handleEmit" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<q-time v-model="timeWithSeconds" with-seconds format24h default-view="seconds" />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,10 +1,23 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useSocketioStore } from 'stores/socketio';
|
import { useSocketStore } from 'stores/socket';
|
||||||
|
import { useSimulationStore } from 'stores/simulation';
|
||||||
|
import { useIcloudStore } from 'stores/icloud';
|
||||||
|
import { useDeviceStore } from 'stores/device';
|
||||||
|
import { useTunneldStore } from 'stores/tunneld';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
|
|
||||||
const socketioStore = useSocketioStore();
|
const socketStore = useSocketStore();
|
||||||
const { sockConnected, deviceConnected, tunnelConnected, simulationState, icloudMonitor, testMode } =
|
const simulationStore = useSimulationStore();
|
||||||
storeToRefs(socketioStore);
|
const icloudStore = useIcloudStore();
|
||||||
|
const deviceStore = useDeviceStore();
|
||||||
|
const tunneldStore = useTunneldStore();
|
||||||
|
|
||||||
|
const { sockConnected } = storeToRefs(socketStore);
|
||||||
|
const { deviceConnected } = storeToRefs(deviceStore);
|
||||||
|
const { tunnelConnected } = storeToRefs(tunneldStore);
|
||||||
|
const { simulationState, testMode } = storeToRefs(simulationStore);
|
||||||
|
const { icloudMonitor } = storeToRefs(icloudStore);
|
||||||
|
|
||||||
function statusDevColor(state: string | boolean | null | undefined): string {
|
function statusDevColor(state: string | boolean | null | undefined): string {
|
||||||
if (state === null || state === undefined) {
|
if (state === null || state === undefined) {
|
||||||
return 'grey';
|
return 'grey';
|
||||||
@@ -28,58 +41,47 @@ function statusDevColor(state: string | boolean | null | undefined): string {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<q-toolbar :class="testMode ? 'bg-warning text-black' : 'bg-primary text-white'">
|
<q-toolbar :class="testMode ? 'bg-warning text-black' : 'bg-primary text-white'">
|
||||||
<div class="flex col q-gutter-md align-center justify-start content-center">
|
<div class="flex col">
|
||||||
<q-space />
|
<q-space />
|
||||||
|
<!--
|
||||||
<div style="width: 80vw" class="flex justify-end">
|
<div style="width: 80vw" class="flex justify-end">
|
||||||
<q-btn
|
-->
|
||||||
rounded
|
<div class="q-gutter-x-sm">
|
||||||
push
|
<q-btn dense rounded push size="sm" icon="mdi-cog" @click="socketStore.toggleSock()">
|
||||||
size="sm"
|
<q-badge :color="statusDevColor(sockConnected)" rounded floating />
|
||||||
icon="settings"
|
|
||||||
class="q-mr-sm"
|
|
||||||
@click="socketioStore.toggleSock()"
|
|
||||||
>
|
|
||||||
<q-badge :color="statusDevColor(sockConnected)" rounded floating class="q-mr-sm" />
|
|
||||||
</q-btn>
|
</q-btn>
|
||||||
<q-btn
|
<q-btn
|
||||||
|
dense
|
||||||
rounded
|
rounded
|
||||||
push
|
push
|
||||||
size="sm"
|
size="sm"
|
||||||
icon="phone_iphone"
|
icon="mdi-cellphone"
|
||||||
class="q-mr-sm"
|
@click="socketStore.requestUpdate()"
|
||||||
@click="socketioStore.requestUpdate()"
|
|
||||||
>
|
>
|
||||||
<q-badge :color="statusDevColor(deviceConnected)" rounded floating class="q-mr-sm" />
|
<q-badge :color="statusDevColor(deviceConnected)" rounded floating />
|
||||||
|
</q-btn>
|
||||||
|
<q-btn dense rounded push size="sm" icon="mdi-subway" @click="socketStore.requestUpdate()">
|
||||||
|
<q-badge :color="statusDevColor(tunnelConnected)" rounded floating />
|
||||||
</q-btn>
|
</q-btn>
|
||||||
<q-btn
|
<q-btn
|
||||||
|
dense
|
||||||
rounded
|
rounded
|
||||||
push
|
push
|
||||||
size="sm"
|
size="sm"
|
||||||
icon="subway"
|
icon="mdi-map-marker"
|
||||||
class="q-mr-sm"
|
@click="socketStore.requestUpdate()"
|
||||||
@click="socketioStore.requestUpdate()"
|
|
||||||
>
|
>
|
||||||
<q-badge :color="statusDevColor(tunnelConnected)" rounded floating class="q-mr-sm" />
|
<q-badge :color="statusDevColor(simulationState)" rounded floating />
|
||||||
</q-btn>
|
</q-btn>
|
||||||
<q-btn
|
<q-btn
|
||||||
|
dense
|
||||||
rounded
|
rounded
|
||||||
push
|
push
|
||||||
size="sm"
|
size="sm"
|
||||||
icon="location_on"
|
icon="mdi-cloud"
|
||||||
class="q-mr-sm"
|
@click="icloudStore.icloudMonitorControl('refresh')"
|
||||||
@click="socketioStore.requestUpdate()"
|
|
||||||
>
|
>
|
||||||
<q-badge :color="statusDevColor(simulationState)" rounded floating class="q-mr-sm" />
|
<q-badge :color="statusDevColor(icloudMonitor)" rounded floating />
|
||||||
</q-btn>
|
|
||||||
<q-btn
|
|
||||||
rounded
|
|
||||||
push
|
|
||||||
size="sm"
|
|
||||||
icon="cloud"
|
|
||||||
class="q-mr-sm"
|
|
||||||
@click="socketioStore.icloudMonitorControl('refresh')"
|
|
||||||
>
|
|
||||||
<q-badge :color="statusDevColor(icloudMonitor)" rounded floating class="q-mr-sm" />
|
|
||||||
</q-btn>
|
</q-btn>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -26,11 +26,14 @@ export interface DevCtrlAttr {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export type FavoriteCommands = 'set' | 'delete' | 'get';
|
||||||
|
|
||||||
export type SimulationCommands =
|
export type SimulationCommands =
|
||||||
'restart'
|
'restart'
|
||||||
| 'start'
|
| 'start'
|
||||||
| 'pause'
|
| 'pause'
|
||||||
| 'resume'
|
| 'resume'
|
||||||
|
| 'reset'
|
||||||
| 'clear'
|
| 'clear'
|
||||||
| 'end'
|
| 'end'
|
||||||
| 'add'
|
| 'add'
|
||||||
@@ -41,7 +44,7 @@ export type SimulationCommands =
|
|||||||
|
|
||||||
export type DeviceCommands = 'shutdown' | 'reboot';
|
export type DeviceCommands = 'shutdown' | 'reboot';
|
||||||
|
|
||||||
export type TunnelCommands =
|
export type TunneldCommands =
|
||||||
| 'start'
|
| 'start'
|
||||||
| 'start-watcher'
|
| 'start-watcher'
|
||||||
| 'end-watcher'
|
| 'end-watcher'
|
||||||
@@ -128,6 +131,14 @@ export interface SimulationStatus {
|
|||||||
longitude: number;
|
longitude: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface icloudData {
|
||||||
|
consumer_queue: string | number | boolean;
|
||||||
|
consumer_task: string | boolean;
|
||||||
|
monitor_enabled: boolean;
|
||||||
|
monitor_task: string | boolean;
|
||||||
|
monitor_running: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface QueueData {
|
export interface QueueData {
|
||||||
active: boolean;
|
active: boolean;
|
||||||
data: LocationQueue;
|
data: LocationQueue;
|
||||||
@@ -149,13 +160,7 @@ export interface StatusUpdate {
|
|||||||
};
|
};
|
||||||
device_name: string | undefined | null;
|
device_name: string | undefined | null;
|
||||||
fmf_location: FindMyUpdate | undefined | null;
|
fmf_location: FindMyUpdate | undefined | null;
|
||||||
icloud: {
|
icloud: icloudData;
|
||||||
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;
|
next_move?: number | undefined | null;
|
||||||
set_location_enabled: boolean;
|
set_location_enabled: boolean;
|
||||||
simulation_queue: QueueData
|
simulation_queue: QueueData
|
||||||
@@ -224,6 +229,20 @@ export interface ClientToServerEvents {
|
|||||||
// status
|
// status
|
||||||
request_update: (callback: (response: StatusUpdate) => void) => void;
|
request_update: (callback: (response: StatusUpdate) => void) => void;
|
||||||
// control
|
// control
|
||||||
|
tunneld_control: (
|
||||||
|
args: {
|
||||||
|
command: TunneldCommands;
|
||||||
|
udid?: string;
|
||||||
|
},
|
||||||
|
callback?: (response: TunneldControlResponse) => void,
|
||||||
|
) => void;
|
||||||
|
favorite_control: (
|
||||||
|
args: {
|
||||||
|
command: FavoriteCommands;
|
||||||
|
favorite?: Favorite | undefined | null;
|
||||||
|
},
|
||||||
|
callback: (response: FavoriteControlResponse) => void,
|
||||||
|
) => void;
|
||||||
simulation_control: (
|
simulation_control: (
|
||||||
args: {
|
args: {
|
||||||
command: SimulationCommands;
|
command: SimulationCommands;
|
||||||
@@ -231,7 +250,7 @@ export interface ClientToServerEvents {
|
|||||||
longitude?: number | null | undefined;
|
longitude?: number | null | undefined;
|
||||||
delay?: number | null | undefined;
|
delay?: number | null | undefined;
|
||||||
loc_id?: string | null | undefined;
|
loc_id?: string | null | undefined;
|
||||||
address?: string | null | undefined;
|
address?: NominatimResponse | string | null | undefined;
|
||||||
},
|
},
|
||||||
callback: (response: SimulationControlResponse) => void,
|
callback: (response: SimulationControlResponse) => void,
|
||||||
) => void;
|
) => void;
|
||||||
@@ -242,13 +261,6 @@ export interface ClientToServerEvents {
|
|||||||
},
|
},
|
||||||
callback?: (response: DeviceControlResponse) => void,
|
callback?: (response: DeviceControlResponse) => void,
|
||||||
) => void;
|
) => void;
|
||||||
tunnel_control: (
|
|
||||||
args: {
|
|
||||||
command: TunnelCommands;
|
|
||||||
delay?: number;
|
|
||||||
},
|
|
||||||
callback?: (response: DeviceControlResponse) => void,
|
|
||||||
) => void;
|
|
||||||
icloud_monitor_control: (
|
icloud_monitor_control: (
|
||||||
args: {
|
args: {
|
||||||
command: string;
|
command: string;
|
||||||
@@ -276,17 +288,26 @@ export interface ClientToServerEvents {
|
|||||||
latitude: number;
|
latitude: number;
|
||||||
longitude: number;
|
longitude: number;
|
||||||
},
|
},
|
||||||
callback?: (response: NominatimResponse) => void,
|
callback?: (response: GeoCacheResponse) => void,
|
||||||
) => void;
|
) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface FavoriteControlResponse {
|
||||||
|
command_status: string;
|
||||||
|
command: FavoriteCommands;
|
||||||
|
command_class: string;
|
||||||
|
data?: {
|
||||||
|
favorites: Favorite[] | undefined | null;
|
||||||
|
};
|
||||||
|
message?: string | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export interface SimulationControlResponse {
|
export interface SimulationControlResponse {
|
||||||
command_status: string;
|
command_status: string;
|
||||||
command: SimulationCommands;
|
command: SimulationCommands;
|
||||||
command_class: string;
|
command_class: string;
|
||||||
data?: SimulationControlResponseData | undefined | null;
|
data?: SimulationControlResponseData | undefined | null;
|
||||||
message?: string | undefined;
|
message?: string | undefined;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SimulationControlResponseData {
|
interface SimulationControlResponseData {
|
||||||
@@ -301,6 +322,14 @@ interface DeviceControlResponse {
|
|||||||
message?: string;
|
message?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
interface TunneldControlResponse {
|
||||||
|
command_status: string;
|
||||||
|
command_class: string;
|
||||||
|
command: TunneldCommands;
|
||||||
|
message?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface iCloudMonitorResponse {
|
export interface iCloudMonitorResponse {
|
||||||
command_status: string;
|
command_status: string;
|
||||||
command: string;
|
command: string;
|
||||||
@@ -401,20 +430,42 @@ export interface LatLng {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface routeDirections {
|
export interface routeDirections {
|
||||||
dirIndex: number;
|
dirIndex?: number | undefined;
|
||||||
coordinateIndex: number;
|
coordinateIndex: number;
|
||||||
text: string;
|
text?: string | undefined;
|
||||||
distance: number;
|
distance?: number | undefined;
|
||||||
time: number;
|
time?: number | undefined;
|
||||||
coordinates: LatLng | null | undefined;
|
coordinates: LatLng | null | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface routeCoordinates {
|
||||||
|
coordinateIndex: number;
|
||||||
|
lat: number;
|
||||||
|
lng: number;
|
||||||
|
distanceFromPrev: number;
|
||||||
|
timeFromPrev: number;
|
||||||
|
instruction: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface RouteSet {
|
export interface RouteSet {
|
||||||
start: [number, number] | [null, null] | [undefined, undefined] | null | undefined;
|
start: [number, number] | [null, null] | [undefined, undefined] | null | undefined;
|
||||||
end: [number, number] | [null, null] | [undefined, undefined] | null | undefined;
|
end: [number, number] | [null, null] | [undefined, undefined] | null | undefined;
|
||||||
wayPoints?: [number, number][] | [null, null] | [undefined, undefined] | null | undefined;
|
wayPoints?: [number, number][] | [null, null] | [undefined, undefined] | null | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Favorite {
|
||||||
|
name: string | null | undefined;
|
||||||
|
latitude: number;
|
||||||
|
longitude: number;
|
||||||
|
icon: string | null;
|
||||||
|
category: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GeoCacheResponse {
|
||||||
|
address: NominatimResponse;
|
||||||
|
favorite?: Favorite;
|
||||||
|
}
|
||||||
|
|
||||||
// TypeScript Interface for Reverse Geocoding Response
|
// TypeScript Interface for Reverse Geocoding Response
|
||||||
export interface NominatimResponse {
|
export interface NominatimResponse {
|
||||||
shop?: string | null | undefined;
|
shop?: string | null | undefined;
|
||||||
@@ -424,8 +475,10 @@ export interface NominatimResponse {
|
|||||||
neighbourhood?: string | null | undefined;
|
neighbourhood?: string | null | undefined;
|
||||||
suburb?: string | null | undefined;
|
suburb?: string | null | undefined;
|
||||||
county: string;
|
county: string;
|
||||||
|
town? : string | null | undefined;
|
||||||
city?: string | null | undefined;
|
city?: string | null | undefined;
|
||||||
village?: string | null | undefined;
|
village?: string | null | undefined;
|
||||||
|
municipality?: string | null | undefined;
|
||||||
state: string;
|
state: string;
|
||||||
'ISO3166-2-lvl4': string;
|
'ISO3166-2-lvl4': string;
|
||||||
postcode: string;
|
postcode: string;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { ref, type Ref } from 'vue';
|
import { ref, type Ref } from 'vue';
|
||||||
import * as LeafLet from 'leaflet';
|
import * as LeafLet from 'leaflet';
|
||||||
import type { LeafletMouseEvent } from 'leaflet';
|
import type { LeafletMouseEvent } from 'leaflet';
|
||||||
|
import { reverseGeocodeRateLimited } from 'functions/reverseGeocodeSocket';
|
||||||
|
|
||||||
type RouteSet = {
|
type RouteSet = {
|
||||||
start?: LeafLet.LatLng | null | undefined;
|
start?: LeafLet.LatLng | null | undefined;
|
||||||
@@ -19,11 +20,14 @@ export function useMarkerContextMenu(
|
|||||||
closeAllPopups: () => void,
|
closeAllPopups: () => void,
|
||||||
) {
|
) {
|
||||||
const clickedLatLng = ref<LeafLet.LatLng | null>(null);
|
const clickedLatLng = ref<LeafLet.LatLng | null>(null);
|
||||||
|
const isFavorite = ref(false);
|
||||||
|
|
||||||
const handleMarkerClick = (event: LeafletMouseEvent) => {
|
const handleMarkerClick = async (event: LeafletMouseEvent) => {
|
||||||
console.log('marker clicked', event);
|
console.log('marker clicked', event);
|
||||||
closeAllPopups();
|
closeAllPopups();
|
||||||
clickedLatLng.value = event.latlng;
|
clickedLatLng.value = event.latlng;
|
||||||
|
const resp = await reverseGeocodeRateLimited(Number(event.latlng.lat), Number(event.latlng.lng));
|
||||||
|
isFavorite.value = !!resp.favorite;
|
||||||
LeafLet.DomEvent.stopPropagation(event.originalEvent);
|
LeafLet.DomEvent.stopPropagation(event.originalEvent);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -44,6 +48,7 @@ export function useMarkerContextMenu(
|
|||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
isFavorite,
|
||||||
clickedLatLng,
|
clickedLatLng,
|
||||||
handleMarkerClick,
|
handleMarkerClick,
|
||||||
setStartRoute,
|
setStartRoute,
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { useLeafletStore } from 'stores/leaflet';
|
import { useLeafletStore } from 'stores/leaflet';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
|
import type { routeCoordinates as StoreRouteCoordinates } from 'components/models';
|
||||||
|
|
||||||
const leafletStore = useLeafletStore();
|
const leafletStore = useLeafletStore();
|
||||||
const { routeSegments, routeDirections } = storeToRefs(leafletStore);
|
const { routeSegments, routeDirections, routeCoordinates } = storeToRefs(leafletStore);
|
||||||
|
|
||||||
type RouteSummary = {
|
type RouteSummary = {
|
||||||
totalDistance: number;
|
totalDistance: number;
|
||||||
@@ -37,6 +38,23 @@ type RouteResult = {
|
|||||||
coordinates?: RouteCoordinates[];
|
coordinates?: RouteCoordinates[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function getDistance(a: RouteCoordinates, b: RouteCoordinates) {
|
||||||
|
const R = 6371000; // meters
|
||||||
|
const toRad = (x: number) => (x * Math.PI) / 180;
|
||||||
|
|
||||||
|
const dLat = toRad(b.lat - a.lat);
|
||||||
|
const dLng = toRad(b.lng - a.lng);
|
||||||
|
|
||||||
|
const lat1 = toRad(a.lat);
|
||||||
|
const lat2 = toRad(b.lat);
|
||||||
|
|
||||||
|
const aVal = Math.sin(dLat / 2) ** 2 + Math.sin(dLng / 2) ** 2 * Math.cos(lat1) * Math.cos(lat2);
|
||||||
|
|
||||||
|
const c = 2 * Math.atan2(Math.sqrt(aVal), Math.sqrt(1 - aVal));
|
||||||
|
|
||||||
|
return R * c;
|
||||||
|
}
|
||||||
|
|
||||||
export function useRoutingEvents() {
|
export function useRoutingEvents() {
|
||||||
const handleRoutesFound = (event: { routes?: RouteResult[] }) => {
|
const handleRoutesFound = (event: { routes?: RouteResult[] }) => {
|
||||||
const route = event.routes?.[0];
|
const route = event.routes?.[0];
|
||||||
@@ -81,7 +99,73 @@ export function useRoutingEvents() {
|
|||||||
routeDirections.value = directionsSummary;
|
routeDirections.value = directionsSummary;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (route.coordinates?.length) {
|
||||||
|
const coordinatesAll: StoreRouteCoordinates[] = [];
|
||||||
|
|
||||||
|
const instructions = route.instructions || [];
|
||||||
|
const coords = route.coordinates;
|
||||||
|
|
||||||
|
for (let i = 0; i < instructions.length; i++) {
|
||||||
|
const current = instructions[i];
|
||||||
|
if (!current) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const next = instructions[i + 1];
|
||||||
|
|
||||||
|
const startIndex = current.index;
|
||||||
|
const endIndex = Math.min(next ? next.index : coords.length - 1, coords.length - 1);
|
||||||
|
if (startIndex < 0 || startIndex > endIndex) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// slice this segment
|
||||||
|
const segmentCoords = coords.slice(startIndex, endIndex + 1);
|
||||||
|
|
||||||
|
// calculate distances between each pair
|
||||||
|
let totalSegmentDistance = 0;
|
||||||
|
|
||||||
|
for (let j = 0; j < segmentCoords.length - 1; j++) {
|
||||||
|
const from = segmentCoords[j];
|
||||||
|
const to = segmentCoords[j + 1];
|
||||||
|
if (from && to) {
|
||||||
|
totalSegmentDistance += getDistance(from, to);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// distribute time proportionally
|
||||||
|
for (let j = 0; j < segmentCoords.length; j++) {
|
||||||
|
const coord = segmentCoords[j];
|
||||||
|
if (!coord) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const coordIndex = startIndex + j;
|
||||||
|
const previousCoord = j === 0 ? undefined : segmentCoords[j - 1];
|
||||||
|
const segmentDistance = previousCoord ? getDistance(previousCoord, coord) : 0;
|
||||||
|
|
||||||
|
const timePortion =
|
||||||
|
totalSegmentDistance > 0 ? (segmentDistance / totalSegmentDistance) * current.time : 0;
|
||||||
|
|
||||||
|
coordinatesAll[coordIndex] = {
|
||||||
|
coordinateIndex: coordIndex,
|
||||||
|
lat: coord.lat,
|
||||||
|
lng: coord.lng,
|
||||||
|
distanceFromPrev: segmentDistance,
|
||||||
|
timeFromPrev: timePortion,
|
||||||
|
instruction: j === 0 ? current.text : '',
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log('Coordinates:', coordinatesAll);
|
||||||
|
if (routeCoordinates) {
|
||||||
|
routeCoordinates.value = coordinatesAll;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
handleRoutesFound,
|
handleRoutesFound,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ export const controls = {
|
|||||||
name: 'Start Location Sim',
|
name: 'Start Location Sim',
|
||||||
cmd: 'start',
|
cmd: 'start',
|
||||||
cmdClass: 'sim_cntrl_class',
|
cmdClass: 'sim_cntrl_class',
|
||||||
icon: 'play_arrow',
|
icon: 'mdi-play',
|
||||||
cnfrm: false,
|
cnfrm: false,
|
||||||
delay: 0,
|
delay: 0,
|
||||||
},
|
},
|
||||||
@@ -12,7 +12,7 @@ export const controls = {
|
|||||||
name: 'Pause Location Sim',
|
name: 'Pause Location Sim',
|
||||||
cmd: 'pause',
|
cmd: 'pause',
|
||||||
cmdClass: 'sim_cntrl_class',
|
cmdClass: 'sim_cntrl_class',
|
||||||
icon: 'pause',
|
icon: 'mdi-pause',
|
||||||
cnfrm: false,
|
cnfrm: false,
|
||||||
delay: 0,
|
delay: 0,
|
||||||
},
|
},
|
||||||
@@ -20,23 +20,31 @@ export const controls = {
|
|||||||
name: 'Resume Location Simulation',
|
name: 'Resume Location Simulation',
|
||||||
cmd: 'resume',
|
cmd: 'resume',
|
||||||
cmdClass: 'sim_cntrl_class',
|
cmdClass: 'sim_cntrl_class',
|
||||||
icon: 'play_arrow',
|
icon: 'mdi-play-pause',
|
||||||
cnfrm: false,
|
cnfrm: false,
|
||||||
delay: 0,
|
delay: 0,
|
||||||
},
|
},
|
||||||
clear: {
|
clear: {
|
||||||
name: 'Clear Location Queue',
|
name: 'Clear Future Items',
|
||||||
cmd: 'clear',
|
cmd: 'clear',
|
||||||
cmdClass: 'sim_cntrl_class',
|
cmdClass: 'sim_cntrl_class',
|
||||||
icon: 'directions_off',
|
icon: 'msi-map-marker-remove',
|
||||||
cnfrm: false,
|
cnfrm: false,
|
||||||
delay: 0,
|
delay: 0,
|
||||||
},
|
},
|
||||||
|
reset: {
|
||||||
|
name: 'Reset Location Queue',
|
||||||
|
cmd: 'reset',
|
||||||
|
cmdClass: 'sim_cntrl_class',
|
||||||
|
icon: 'mdi-restart',
|
||||||
|
cnfrm: true,
|
||||||
|
delay: 0,
|
||||||
|
},
|
||||||
end: {
|
end: {
|
||||||
name: 'End Location Sim',
|
name: 'End Location Sim',
|
||||||
cmd: 'end',
|
cmd: 'end',
|
||||||
cmdClass: 'sim_cntrl_class',
|
cmdClass: 'sim_cntrl_class',
|
||||||
icon: 'stop',
|
icon: 'mdi-stop',
|
||||||
cnfrm: true,
|
cnfrm: true,
|
||||||
delay: 0,
|
delay: 0,
|
||||||
},
|
},
|
||||||
@@ -46,7 +54,7 @@ export const controls = {
|
|||||||
name: 'Shutdown',
|
name: 'Shutdown',
|
||||||
cmd: 'shutdown',
|
cmd: 'shutdown',
|
||||||
cmdClass: 'dev_cntrl_class',
|
cmdClass: 'dev_cntrl_class',
|
||||||
icon: 'power_settings_new',
|
icon: 'mdi-power',
|
||||||
cnfrm: true,
|
cnfrm: true,
|
||||||
delay: 5,
|
delay: 5,
|
||||||
},
|
},
|
||||||
@@ -54,7 +62,7 @@ export const controls = {
|
|||||||
name: 'Reboot',
|
name: 'Reboot',
|
||||||
cmd: 'reboot',
|
cmd: 'reboot',
|
||||||
cmdClass: 'dev_cntrl_class',
|
cmdClass: 'dev_cntrl_class',
|
||||||
icon: 'restart_alt',
|
icon: 'mdi-restart',
|
||||||
cnfrm: true,
|
cnfrm: true,
|
||||||
delay: 5,
|
delay: 5,
|
||||||
},
|
},
|
||||||
@@ -64,7 +72,7 @@ export const controls = {
|
|||||||
name: 'Start iCloud Monitor',
|
name: 'Start iCloud Monitor',
|
||||||
cmd: 'start',
|
cmd: 'start',
|
||||||
cmdClass: 'icloud-monitor_cntrl_class',
|
cmdClass: 'icloud-monitor_cntrl_class',
|
||||||
icon: 'play_arrow',
|
icon: 'mdi-play',
|
||||||
cnfrm: false,
|
cnfrm: false,
|
||||||
delay: 0,
|
delay: 0,
|
||||||
},
|
},
|
||||||
@@ -72,7 +80,65 @@ export const controls = {
|
|||||||
name: 'Stop iCloud Monitor',
|
name: 'Stop iCloud Monitor',
|
||||||
cmd: 'stop',
|
cmd: 'stop',
|
||||||
cmdClass: 'icloud-monitor_cntrl_class',
|
cmdClass: 'icloud-monitor_cntrl_class',
|
||||||
icon: 'stop',
|
icon: 'mdi-stop',
|
||||||
|
cnfrm: false,
|
||||||
|
delay: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tunneld: {
|
||||||
|
start: {
|
||||||
|
name: 'Start Tunneld',
|
||||||
|
cmd: 'start',
|
||||||
|
cmdClass: 'tunneld_cntrl_class',
|
||||||
|
icon: 'mdi-play',
|
||||||
|
cnfrm: false,
|
||||||
|
delay: 0,
|
||||||
|
},
|
||||||
|
start_watcher: {
|
||||||
|
name: 'Start TunneldWatcher',
|
||||||
|
cmd: 'start-watcher',
|
||||||
|
cmdClass: 'tunneld_cntrl_class',
|
||||||
|
icon: 'mdi-play',
|
||||||
|
cnfrm: false,
|
||||||
|
delay: 0,
|
||||||
|
},
|
||||||
|
end_watcher: {
|
||||||
|
name: 'End TunneldWatcher',
|
||||||
|
cmd: 'end-watcher',
|
||||||
|
cmdClass: 'tunneld_cntrl_class',
|
||||||
|
icon: 'mdi-stop',
|
||||||
|
cnfrm: false,
|
||||||
|
delay: 0,
|
||||||
|
},
|
||||||
|
clear: {
|
||||||
|
name: 'Clear Tunneld',
|
||||||
|
cmd: 'clear',
|
||||||
|
cmdClass: 'tunneld_cntrl_class',
|
||||||
|
icon: 'mdi-backspace',
|
||||||
|
cnfrm: false,
|
||||||
|
delay: 0,
|
||||||
|
},
|
||||||
|
cancel: {
|
||||||
|
name: 'Cancel Tunneld',
|
||||||
|
cmd: 'cancel',
|
||||||
|
cmdClass: 'tunneld_cntrl_class',
|
||||||
|
icon: 'mdi-cancel',
|
||||||
|
cnfrm: false,
|
||||||
|
delay: 0,
|
||||||
|
},
|
||||||
|
restart: {
|
||||||
|
name: 'Restart Tunneld',
|
||||||
|
cmd: 'restart',
|
||||||
|
cmdClass: 'tunneld_cntrl_class',
|
||||||
|
icon: 'mdi-restart',
|
||||||
|
cnfrm: false,
|
||||||
|
delay: 0,
|
||||||
|
},
|
||||||
|
shutdown: {
|
||||||
|
name: 'Shutdown Tunneld',
|
||||||
|
cmd: 'shutdown',
|
||||||
|
cmdClass: 'tunneld_cntrl_class',
|
||||||
|
icon: 'mdi-stop',
|
||||||
cnfrm: false,
|
cnfrm: false,
|
||||||
delay: 0,
|
delay: 0,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export const favorites = {
|
export const favorites = {
|
||||||
home: {
|
home: {
|
||||||
name: 'Home',
|
name: 'Home',
|
||||||
icon: 'home',
|
icon: 'mdi-home',
|
||||||
coords: {
|
coords: {
|
||||||
lat: 40.910773020811,
|
lat: 40.910773020811,
|
||||||
lng: -73.891069806448,
|
lng: -73.891069806448,
|
||||||
@@ -9,11 +9,11 @@ export const favorites = {
|
|||||||
},
|
},
|
||||||
work_places: {
|
work_places: {
|
||||||
name: 'Work Places',
|
name: 'Work Places',
|
||||||
icon: 'work',
|
icon: 'mdi-briefcase',
|
||||||
subitems: {
|
subitems: {
|
||||||
jeong: {
|
jeong: {
|
||||||
name: 'Jeong',
|
name: 'Jeong',
|
||||||
icon: 'dermatology',
|
icon: 'mdi-doctor',
|
||||||
coords: {
|
coords: {
|
||||||
lat: 40.76624975651346,
|
lat: 40.76624975651346,
|
||||||
lng: -73.81444335286128,
|
lng: -73.81444335286128,
|
||||||
@@ -22,7 +22,7 @@ export const favorites = {
|
|||||||
},
|
},
|
||||||
santos: {
|
santos: {
|
||||||
name: 'Santos',
|
name: 'Santos',
|
||||||
icon: 'syringe',
|
icon: 'mdi-doctor',
|
||||||
coords: {
|
coords: {
|
||||||
lat: 40.74504671877868,
|
lat: 40.74504671877868,
|
||||||
lng: -73.8880099638491,
|
lng: -73.8880099638491,
|
||||||
@@ -31,7 +31,7 @@ export const favorites = {
|
|||||||
},
|
},
|
||||||
natalya_qns: {
|
natalya_qns: {
|
||||||
name: 'Natalya (Qns)',
|
name: 'Natalya (Qns)',
|
||||||
icon: 'healing',
|
icon: 'mdi-doctor',
|
||||||
coords: {
|
coords: {
|
||||||
lat: 40.69644966409178,
|
lat: 40.69644966409178,
|
||||||
lng: -73.837453217826,
|
lng: -73.837453217826,
|
||||||
@@ -40,7 +40,7 @@ export const favorites = {
|
|||||||
},
|
},
|
||||||
natalya_bx: {
|
natalya_bx: {
|
||||||
name: 'Natalya (Bronx)',
|
name: 'Natalya (Bronx)',
|
||||||
icon: 'healing',
|
icon: 'mdi-doctor',
|
||||||
coords: {
|
coords: {
|
||||||
lat: 40.85384419116598,
|
lat: 40.85384419116598,
|
||||||
lng: -73.86314767911834,
|
lng: -73.86314767911834,
|
||||||
@@ -49,7 +49,7 @@ export const favorites = {
|
|||||||
},
|
},
|
||||||
office: {
|
office: {
|
||||||
name: 'Linwood Plaza',
|
name: 'Linwood Plaza',
|
||||||
icon: 'allergy',
|
icon: 'mdi-allergy',
|
||||||
coords: {
|
coords: {
|
||||||
lat: 40.86141832913106,
|
lat: 40.86141832913106,
|
||||||
lng: -73.96997583196286,
|
lng: -73.96997583196286,
|
||||||
@@ -60,7 +60,7 @@ export const favorites = {
|
|||||||
},
|
},
|
||||||
strg: {
|
strg: {
|
||||||
name: 'Man Mini Storage',
|
name: 'Man Mini Storage',
|
||||||
icon: 'box',
|
icon: 'mdi-dolly',
|
||||||
coords: {
|
coords: {
|
||||||
lat: 40.75158955085288,
|
lat: 40.75158955085288,
|
||||||
lng: -73.9328988710467,
|
lng: -73.9328988710467,
|
||||||
@@ -69,7 +69,7 @@ export const favorites = {
|
|||||||
},
|
},
|
||||||
acme: {
|
acme: {
|
||||||
name: 'Acme',
|
name: 'Acme',
|
||||||
icon: 'grocery',
|
icon: 'mdi-cart',
|
||||||
coords: {
|
coords: {
|
||||||
lat: 40.90930366920829,
|
lat: 40.90930366920829,
|
||||||
lng: -73.87658695470259,
|
lng: -73.87658695470259,
|
||||||
|
|||||||
@@ -1,15 +1,24 @@
|
|||||||
// services/nominatimService.ts
|
// services/nominatimService.ts
|
||||||
import { useSocketioStore } from 'stores/socketio';
|
import { socket } from 'boot/socketio';
|
||||||
import type { NominatimResponse, NominatimRequest } from 'components/models';
|
import type { NominatimRequest, GeoCacheResponse } from 'components/models';
|
||||||
|
|
||||||
|
|
||||||
const socketStore = useSocketioStore();
|
|
||||||
|
|
||||||
let lastRequestTime = 0;
|
let lastRequestTime = 0;
|
||||||
|
|
||||||
export const reverseGeocodeRateLimited = async (
|
function revGeoCode(nomRequest: NominatimRequest): Promise<GeoCacheResponse> {
|
||||||
lat: number,
|
return new Promise((resolve) => {
|
||||||
lon: number,
|
socket.emit(
|
||||||
): Promise<NominatimResponse> => {
|
'reverse_geocode',
|
||||||
|
{ latitude: nomRequest.latitude, longitude: nomRequest.longitude },
|
||||||
|
(response) => {
|
||||||
|
resolve(response);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const reverseGeocodeRateLimited = async (lat: number, lon: number): Promise<GeoCacheResponse> => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const timeSinceLast = now - lastRequestTime;
|
const timeSinceLast = now - lastRequestTime;
|
||||||
|
|
||||||
@@ -17,7 +26,10 @@ export const reverseGeocodeRateLimited = async (
|
|||||||
if (timeSinceLast < 1000) {
|
if (timeSinceLast < 1000) {
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000 - timeSinceLast));
|
await new Promise((resolve) => setTimeout(resolve, 1000 - timeSinceLast));
|
||||||
}
|
}
|
||||||
const response: NominatimResponse = await socketStore.revGeoCode({latitude: lat, longitude: lon} as NominatimRequest);
|
const response: GeoCacheResponse = await revGeoCode({
|
||||||
|
latitude: lat,
|
||||||
|
longitude: lon,
|
||||||
|
} as NominatimRequest);
|
||||||
|
|
||||||
lastRequestTime = Date.now();
|
lastRequestTime = Date.now();
|
||||||
return response;
|
return response;
|
||||||
|
|||||||
@@ -45,77 +45,26 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
import { useSocketioStore } from 'stores/socketio';
|
import { useSimulationStore } from 'stores/simulation';
|
||||||
|
import { useSocketStore } from 'stores/socket';
|
||||||
|
import { useIcloudStore } from 'stores/icloud';
|
||||||
|
import { useFavoriteStore } from 'stores/favorite';
|
||||||
|
|
||||||
import MenuBar from 'components/MenuBar.vue';
|
import MenuBar from 'components/MenuBar.vue';
|
||||||
import StatusBar from 'components/StatusBar.vue';
|
import StatusBar from 'components/StatusBar.vue';
|
||||||
|
|
||||||
const socketStore = useSocketioStore();
|
const simulationStore = useSimulationStore();
|
||||||
|
const socketStore = useSocketStore();
|
||||||
|
const icloudStore = useIcloudStore();
|
||||||
|
const favoriteStore = useFavoriteStore();
|
||||||
|
|
||||||
const drawer = ref(false);
|
const drawer = ref(false);
|
||||||
|
|
||||||
/*
|
|
||||||
const route = useRoute();
|
|
||||||
|
|
||||||
const menuList = [
|
|
||||||
{
|
|
||||||
icon: 'map',
|
|
||||||
label: 'Map',
|
|
||||||
separator: false,
|
|
||||||
route: 'Leaflet',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'info',
|
|
||||||
label: 'Device Info',
|
|
||||||
separator: false,
|
|
||||||
route: 'DeviceInfo',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'inbox',
|
|
||||||
label: 'Inbox',
|
|
||||||
separator: true,
|
|
||||||
route: 'ErrorNotFound',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'send',
|
|
||||||
label: 'Outbox',
|
|
||||||
separator: false,
|
|
||||||
route: 'ErrorNotFound',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'delete',
|
|
||||||
label: 'Trash',
|
|
||||||
separator: false,
|
|
||||||
route: 'ErrorNotFound',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'error',
|
|
||||||
label: 'Spam',
|
|
||||||
separator: true,
|
|
||||||
route: 'ErrorNotFound',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'settings',
|
|
||||||
label: 'Settings',
|
|
||||||
separator: false,
|
|
||||||
route: 'ErrorNotFound',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'feedback',
|
|
||||||
label: 'Send Feedback',
|
|
||||||
separator: false,
|
|
||||||
route: 'ErrorNotFound',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'help',
|
|
||||||
iconColor: 'primary',
|
|
||||||
label: 'Help',
|
|
||||||
separator: false,
|
|
||||||
route: 'ErrorNotFound',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
*/
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
socketStore.bindEvents();
|
socketStore.bindEvents();
|
||||||
|
simulationStore.bindEvents();
|
||||||
|
icloudStore.bindEvents();
|
||||||
socketStore.connect();
|
socketStore.connect();
|
||||||
|
favoriteStore.initialize();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -7,25 +7,26 @@
|
|||||||
<q-btn color="purple" @click="showNotif" label="Show Notification" />
|
<q-btn color="purple" @click="showNotif" label="Show Notification" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col" style="border: 5px pink dashed;" >
|
<div class="col" style="border: 5px pink dashed">
|
||||||
<q-icon :name="darkStatus" size="100px" @click="toggleDarkLight" />
|
<q-icon :name="darkStatus" size="100px" @click="toggleDarkLight" />
|
||||||
</div>
|
</div>
|
||||||
</q-page>
|
</q-page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useQuasar } from 'quasar'
|
import { useQuasar } from 'quasar';
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue';
|
||||||
import SocketTest from 'components/SocketTest.vue'
|
import SocketTest from 'components/SocketTest.vue';
|
||||||
const $q = useQuasar()
|
const $q = useQuasar();
|
||||||
|
|
||||||
const darkStatus = computed(() => $q.dark.isActive ? 'dark_mode' : 'light_mode')
|
const darkStatus = computed(() => ($q.dark.isActive ? 'dark_mode' : 'light_mode'));
|
||||||
|
|
||||||
function toggleDarkLight() {
|
function toggleDarkLight() {
|
||||||
$q.dark.toggle()
|
$q.dark.toggle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function showNotif() {
|
function showNotif() {
|
||||||
$q.notify({
|
$q.notify({
|
||||||
message: 'This is a notification',
|
message: 'This is a notification',
|
||||||
@@ -33,9 +34,16 @@ function showNotif() {
|
|||||||
position: 'top-right',
|
position: 'top-right',
|
||||||
timeout: 3000,
|
timeout: 3000,
|
||||||
actions: [
|
actions: [
|
||||||
{ label: 'Dismiss', color: 'white', handler: () => { /* Dismiss action */ } }
|
{
|
||||||
]
|
label: 'Dismiss',
|
||||||
})
|
color: 'white',
|
||||||
|
handler: () => {
|
||||||
|
/* Dismiss action */
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<style lang="sass" scoped>
|
||||||
|
</style>
|
||||||
|
|||||||
47
src/stores/device.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { defineStore, acceptHMRUpdate } from 'pinia';
|
||||||
|
import { socket } from 'boot/socketio';
|
||||||
|
import type { DeviceCommands } from 'components/models';
|
||||||
|
|
||||||
|
import { useSocketStore } from 'stores/socket';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const useDeviceStore = defineStore('deviceStore', {
|
||||||
|
state: () => {
|
||||||
|
return {
|
||||||
|
deviceConnected: false as boolean,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getters: {},
|
||||||
|
actions: {
|
||||||
|
digestUpdate(data: boolean) {
|
||||||
|
this.deviceConnected = data;
|
||||||
|
},
|
||||||
|
deviceControl(command: DeviceCommands, delay: number = 0) {
|
||||||
|
const socketStore = useSocketStore();
|
||||||
|
const debugLog = socketStore.debugLog;
|
||||||
|
let fnctRtn: { sts: string; msg?: string | undefined } = { sts: '', msg: '' };
|
||||||
|
if (debugLog) {
|
||||||
|
console.log('deviceStore: got command: ', command, ' with delay: ', delay, ' seconds');
|
||||||
|
}
|
||||||
|
socket.emit(
|
||||||
|
'device_control',
|
||||||
|
{ command: command, delay: delay },
|
||||||
|
(response) => {
|
||||||
|
console.log(response.command_status, response.command);
|
||||||
|
if (response.command_status == 'ERROR') {
|
||||||
|
fnctRtn = { sts: 'error', msg: response.message?.toString() };
|
||||||
|
throw new Error(response.message);
|
||||||
|
} else {
|
||||||
|
fnctRtn = { sts: 'OK', msg: response.message?.toString() };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return fnctRtn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (import.meta.hot) {
|
||||||
|
import.meta.hot.accept(acceptHMRUpdate(useDeviceStore, import.meta.hot));
|
||||||
|
}
|
||||||
124
src/stores/favorite.ts
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
import { defineStore, acceptHMRUpdate } from 'pinia';
|
||||||
|
import { socket } from 'boot/socketio';
|
||||||
|
import { useSocketStore } from 'stores/socket';
|
||||||
|
import type { Favorite } from 'components/models';
|
||||||
|
|
||||||
|
export const useFavoriteStore = defineStore('favoriteStore', {
|
||||||
|
state: () => {
|
||||||
|
return {
|
||||||
|
favorites: [] as Favorite[],
|
||||||
|
categories: [] as string[],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getters: {},
|
||||||
|
actions: {
|
||||||
|
favoriteControl({ command, favorite }: { command: string; favorite?: Favorite }) {
|
||||||
|
const socketStore = useSocketStore();
|
||||||
|
const debugLog = socketStore.debugLog;
|
||||||
|
let fnctRtn: { sts: string; msg?: string | undefined } = { sts: '', msg: '' };
|
||||||
|
switch (command) {
|
||||||
|
case 'set':
|
||||||
|
if (!favorite) {
|
||||||
|
console.log('Favorite not provided, favdata: %s', favorite);
|
||||||
|
fnctRtn = { sts: 'error', msg: 'Favorite not provided.' };
|
||||||
|
throw new Error('Favorite not provided.');
|
||||||
|
}
|
||||||
|
socket.emit('favorite_control', { command: 'set', favorite: favorite }, (response) => {
|
||||||
|
if (response.command_status == 'ERROR') {
|
||||||
|
throw new Error(response.message);
|
||||||
|
} else {
|
||||||
|
if (!response.data?.favorites) {
|
||||||
|
fnctRtn = { sts: 'error', msg: 'No favorites found.' };
|
||||||
|
throw new Error(response.message);
|
||||||
|
} else {
|
||||||
|
console.log('Favorite added, favdata: %s', favorite);
|
||||||
|
fnctRtn = { sts: 'OK', msg: response.message?.toString() };
|
||||||
|
this.favorites = response.data.favorites;
|
||||||
|
response.data.favorites.forEach((fav) => {
|
||||||
|
if (!this.categories.includes(fav.category)) {
|
||||||
|
this.categories.push(fav.category);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (debugLog) {
|
||||||
|
console.log('response from favorite_control_add: ', response);
|
||||||
|
}
|
||||||
|
return response.message;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'delete':
|
||||||
|
socket.emit('favorite_control', { command: 'delete', favorite: favorite }, (response) => {
|
||||||
|
if (response.command_status == 'ERROR') {
|
||||||
|
fnctRtn = { sts: 'error', msg: response.message };
|
||||||
|
throw new Error(response.message);
|
||||||
|
} else {
|
||||||
|
if (!response.data?.favorites) {
|
||||||
|
fnctRtn = { sts: 'error', msg: 'No favorites found.' };
|
||||||
|
throw new Error(response.message);
|
||||||
|
} else {
|
||||||
|
fnctRtn = { sts: 'OK', msg: response.message?.toString() };
|
||||||
|
this.favorites = response.data.favorites;
|
||||||
|
response.data.favorites.forEach((fav) => {
|
||||||
|
if (!this.categories.includes(fav.category)) {
|
||||||
|
this.categories.push(fav.category);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (debugLog) {
|
||||||
|
console.log('response from favorite_control_delete: ', response);
|
||||||
|
}
|
||||||
|
return response.message;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'get':
|
||||||
|
socket.emit('favorite_control', { command: 'get', favorite: favorite }, (response) => {
|
||||||
|
if (response.command_status == 'ERROR') {
|
||||||
|
fnctRtn = { sts: 'error', msg: response.message };
|
||||||
|
throw new Error(response.message);
|
||||||
|
} else {
|
||||||
|
if (!response.data?.favorites) {
|
||||||
|
fnctRtn = { sts: 'error', msg: 'No favorites found.' };
|
||||||
|
throw new Error(response.message);
|
||||||
|
} else {
|
||||||
|
fnctRtn = { sts: 'OK', msg: response.message?.toString() };
|
||||||
|
this.favorites = response.data.favorites;
|
||||||
|
response.data.favorites.forEach((fav) => {
|
||||||
|
if(!this.categories.includes(fav.category)) {
|
||||||
|
this.categories.push(fav.category);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (debugLog) {
|
||||||
|
console.log('response from favorite_control_get: ', response);
|
||||||
|
}
|
||||||
|
return response.message;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fnctRtn = { sts: 'error', msg: 'Invalid command' + command };
|
||||||
|
throw new Error('Invalid command' + command);
|
||||||
|
}
|
||||||
|
return fnctRtn;
|
||||||
|
},
|
||||||
|
initialize(): void {
|
||||||
|
this.favoriteControl({ command: 'get' });
|
||||||
|
},
|
||||||
|
getCategories(): string[] {
|
||||||
|
this.favoriteControl({ command: 'get' });
|
||||||
|
const categories: string[] = [];
|
||||||
|
this.favorites.forEach((favorite) => {
|
||||||
|
if (!categories.includes(favorite.category)) {
|
||||||
|
categories.push(favorite.category);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return categories;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (import.meta.hot) {
|
||||||
|
import.meta.hot.accept(acceptHMRUpdate(useFavoriteStore, import.meta.hot));
|
||||||
|
}
|
||||||
219
src/stores/icloud.ts
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
import { defineStore, acceptHMRUpdate } from 'pinia';
|
||||||
|
import { socket } from 'boot/socketio';
|
||||||
|
import { useSocketStore } from 'stores/socket';
|
||||||
|
import { useQuasar } from 'quasar';
|
||||||
|
|
||||||
|
import iCloudCodeDialog from 'components/iCloudCodeDialog.vue';
|
||||||
|
|
||||||
|
import type { FindMyUpdate, icloudData, iCloudMonitorResponse } from 'components/models';
|
||||||
|
|
||||||
|
const $q = useQuasar();
|
||||||
|
|
||||||
|
|
||||||
|
export const useIcloudStore = defineStore('icloudStore', {
|
||||||
|
state: () => {
|
||||||
|
return {
|
||||||
|
icloudMonitor: false as boolean,
|
||||||
|
findMyUpdate: null as FindMyUpdate | null | undefined,
|
||||||
|
consumerQueue: 0 as number | string | boolean,
|
||||||
|
consumerTask: false as boolean | string,
|
||||||
|
monitorTask: false as boolean | string,
|
||||||
|
monitorEnabled: false as boolean,
|
||||||
|
monitorRunning: false as boolean,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getters: {},
|
||||||
|
actions: {
|
||||||
|
bindEvents() {
|
||||||
|
const socketStore = useSocketStore();
|
||||||
|
const debugLog = socketStore.debugLog;
|
||||||
|
// fmf_update
|
||||||
|
socket.on('fmf_update', (data: FindMyUpdate): void => {
|
||||||
|
if (debugLog) {
|
||||||
|
console.log('event: fmf_update received: ', data);
|
||||||
|
}
|
||||||
|
this.findMyUpdate = data;
|
||||||
|
});
|
||||||
|
|
||||||
|
// icloud_2fa_request
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
icloudMonitorControl(command: string) {
|
||||||
|
const socketStore = useSocketStore();
|
||||||
|
const debugLog = socketStore.debugLog;
|
||||||
|
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.command_status;
|
||||||
|
if (response.command_status == 'ERROR') {
|
||||||
|
if (response.message) {
|
||||||
|
if (debugLog) {
|
||||||
|
console.log(response.message);
|
||||||
|
}
|
||||||
|
fnctRtn.msg = response.message;
|
||||||
|
} else {
|
||||||
|
fnctRtn.msg = 'Error';
|
||||||
|
}
|
||||||
|
throw new Error(fnctRtn.msg);
|
||||||
|
} else {
|
||||||
|
fnctRtn = { sts: 'OK', msg: 'iCloud Monitor: ' + response.command_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.command_status;
|
||||||
|
if (response.command_status == 'ERROR') {
|
||||||
|
if (response.message) {
|
||||||
|
if (debugLog) {
|
||||||
|
console.log(response.message);
|
||||||
|
}
|
||||||
|
fnctRtn.msg = response.message;
|
||||||
|
} else {
|
||||||
|
fnctRtn.msg = 'Error';
|
||||||
|
}
|
||||||
|
throw new Error(fnctRtn.msg);
|
||||||
|
} else {
|
||||||
|
fnctRtn = { sts: 'OK', msg: 'iCloud Monitor: ' + response.command_status };
|
||||||
|
this.icloudMonitor = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'status':
|
||||||
|
if (debugLog) {
|
||||||
|
console.log('socketStore: got command: icloudMonitor status');
|
||||||
|
}
|
||||||
|
socket.emit(
|
||||||
|
'icloud_monitor_control',
|
||||||
|
{ command: 'get' },
|
||||||
|
(response: iCloudMonitorResponse) => {
|
||||||
|
fnctRtn.sts = response.command_status;
|
||||||
|
if (response.command_status == 'error') {
|
||||||
|
if (response.message) {
|
||||||
|
if (debugLog) {
|
||||||
|
console.log(response.message);
|
||||||
|
}
|
||||||
|
fnctRtn.msg = response.message;
|
||||||
|
} else {
|
||||||
|
fnctRtn.msg = 'Error';
|
||||||
|
}
|
||||||
|
throw new Error(fnctRtn.msg);
|
||||||
|
} else {
|
||||||
|
fnctRtn = {
|
||||||
|
sts: 'OK',
|
||||||
|
msg: 'iCloud Location Requested',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
this.icloudMonitor = !!(
|
||||||
|
response.icloud_monitor_enabled && response.icloud_monitor_running
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'refresh':
|
||||||
|
if (debugLog) {
|
||||||
|
console.log('socketStore: got command: icloudMonitor refresh');
|
||||||
|
}
|
||||||
|
socket.emit(
|
||||||
|
'icloud_monitor_control',
|
||||||
|
{ command: 'status' },
|
||||||
|
(response: iCloudMonitorResponse) => {
|
||||||
|
fnctRtn.sts = response.command_status;
|
||||||
|
if (response.command_status == 'ERROR') {
|
||||||
|
if (response.message) {
|
||||||
|
if (debugLog) {
|
||||||
|
console.log(response.message);
|
||||||
|
}
|
||||||
|
fnctRtn.msg = response.message;
|
||||||
|
} else {
|
||||||
|
fnctRtn.msg = 'Error';
|
||||||
|
}
|
||||||
|
throw new Error(fnctRtn.msg);
|
||||||
|
} else {
|
||||||
|
fnctRtn = {
|
||||||
|
sts: 'OK',
|
||||||
|
msg:
|
||||||
|
'iCloud 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;
|
||||||
|
},
|
||||||
|
digestUpdate(data: icloudData) {
|
||||||
|
this.icloudMonitor = data.monitor_running;
|
||||||
|
this.consumerQueue= data.consumer_queue;
|
||||||
|
this.consumerTask = data.consumer_task;
|
||||||
|
this.monitorEnabled = data.monitor_enabled;
|
||||||
|
this.monitorTask = data.monitor_task;
|
||||||
|
this.monitorRunning = data.monitor_running;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (import.meta.hot) {
|
||||||
|
import.meta.hot.accept(acceptHMRUpdate(useIcloudStore, import.meta.hot));
|
||||||
|
}
|
||||||
@@ -1,6 +1,12 @@
|
|||||||
import { defineStore, acceptHMRUpdate } from 'pinia';
|
import { defineStore, acceptHMRUpdate } from 'pinia';
|
||||||
import { favorites } from 'constants/favorites'
|
import { favorites } from 'constants/favorites'
|
||||||
import type { RoutesSet, LatLng, routeSegments, routeDirections } from 'components/models';
|
import type {
|
||||||
|
RoutesSet,
|
||||||
|
LatLng,
|
||||||
|
routeSegments,
|
||||||
|
routeDirections,
|
||||||
|
routeCoordinates,
|
||||||
|
} from 'components/models';
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
zoom: number;
|
zoom: number;
|
||||||
@@ -13,8 +19,9 @@ interface State {
|
|||||||
wayPoints?: LatLng[] | null | undefined;
|
wayPoints?: LatLng[] | null | undefined;
|
||||||
};
|
};
|
||||||
routesSet: RoutesSet[] | null;
|
routesSet: RoutesSet[] | null;
|
||||||
routeSegments?: routeSegments[] | null;
|
routeSegments: routeSegments[] | null;
|
||||||
routeDirections?: routeDirections[] | null;
|
routeDirections: routeDirections[] | null;
|
||||||
|
routeCoordinates: routeCoordinates[] | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useLeafletStore = defineStore('leaflet', {
|
export const useLeafletStore = defineStore('leaflet', {
|
||||||
@@ -32,6 +39,7 @@ export const useLeafletStore = defineStore('leaflet', {
|
|||||||
routesSet: [],
|
routesSet: [],
|
||||||
routeSegments: [],
|
routeSegments: [],
|
||||||
routeDirections: [],
|
routeDirections: [],
|
||||||
|
routeCoordinates: [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
|
|||||||
443
src/stores/simulation.ts
Normal file
@@ -0,0 +1,443 @@
|
|||||||
|
import { defineStore, acceptHMRUpdate } from 'pinia';
|
||||||
|
import { socket } from 'boot/socketio';
|
||||||
|
import { useSocketStore } from 'stores/socket';
|
||||||
|
|
||||||
|
import type {
|
||||||
|
CurrentLocation,
|
||||||
|
LocationItemUpdate,
|
||||||
|
LocationMarkUpdateResponse,
|
||||||
|
LocationQueue,
|
||||||
|
NominatimResponse,
|
||||||
|
QueueData,
|
||||||
|
SimulationControlResponse,
|
||||||
|
SimulationStatus,
|
||||||
|
} from 'components/models';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const useSimulationStore = defineStore('simulationStore', {
|
||||||
|
state: () => {
|
||||||
|
return {
|
||||||
|
currentLocation: null as CurrentLocation | null | undefined,
|
||||||
|
gpsNoise: null as boolean | undefined | null,
|
||||||
|
locationQueueData: {} as LocationQueue,
|
||||||
|
locationQueueDeletedItems: [] as string[],
|
||||||
|
locationQueueOrder: [] as string[],
|
||||||
|
simulationQueueLength: 0 as number | null | undefined,
|
||||||
|
simulationRunning: false as boolean | undefined | null,
|
||||||
|
simulationState: null as string | null | undefined,
|
||||||
|
testMode: null as boolean | undefined | null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getters: {},
|
||||||
|
actions: {
|
||||||
|
bindEvents() {
|
||||||
|
const socketStore = useSocketStore();
|
||||||
|
const debugLog = socketStore.debugLog;
|
||||||
|
// simulation_status
|
||||||
|
socket.on('simulation_status', (data: SimulationStatus): void => {
|
||||||
|
if (debugLog) {
|
||||||
|
console.log('event: simulation_status received: ', data);
|
||||||
|
}
|
||||||
|
console.log('updating currentLocation', data);
|
||||||
|
this.currentLocation = {
|
||||||
|
loc_id: data.loc_id,
|
||||||
|
latitude: data.latitude,
|
||||||
|
longitude: data.longitude,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// queue_data_update
|
||||||
|
socket.on('queue_data_update', (inData: QueueData): void => {
|
||||||
|
if (debugLog) {
|
||||||
|
console.log('QueueUpdate received: ', inData);
|
||||||
|
}
|
||||||
|
this.digestQueueUpdate(inData);
|
||||||
|
});
|
||||||
|
// location_item_update
|
||||||
|
socket.on('location_item_update', (inData: LocationItemUpdate): void => {
|
||||||
|
console.log('Location item update received, data: ', inData);
|
||||||
|
this.locationQueueData[inData.loc_id] = inData.data;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
simulationControl({
|
||||||
|
command,
|
||||||
|
latitude,
|
||||||
|
longitude,
|
||||||
|
loc_id,
|
||||||
|
delay,
|
||||||
|
address,
|
||||||
|
}: {
|
||||||
|
command: string;
|
||||||
|
latitude?: number | null | undefined;
|
||||||
|
longitude?: number | null | undefined;
|
||||||
|
loc_id?: string | null | undefined;
|
||||||
|
delay?: number | null | undefined;
|
||||||
|
address?: NominatimResponse | string | null | undefined;
|
||||||
|
}) {
|
||||||
|
const socketStore = useSocketStore();
|
||||||
|
const debugLog = socketStore.debugLog;
|
||||||
|
let fnctRtn: { sts: string; msg?: string | undefined } = { sts: '', msg: '' };
|
||||||
|
switch (command) {
|
||||||
|
case 'start':
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
if (debugLog) {
|
||||||
|
console.log('Emitting simulation_control: start');
|
||||||
|
}
|
||||||
|
socket.emit(
|
||||||
|
'simulation_control',
|
||||||
|
{ command: 'start' },
|
||||||
|
(response: SimulationControlResponse) => {
|
||||||
|
if (response.command_status == 'ERROR') {
|
||||||
|
fnctRtn = { sts: 'error', msg: response.message?.toString() };
|
||||||
|
throw new Error(response.message);
|
||||||
|
} else {
|
||||||
|
if (!response.data?.simulation_queue) {
|
||||||
|
fnctRtn = { sts: 'error', msg: 'Simulation queue data missing' };
|
||||||
|
throw new Error(response.message);
|
||||||
|
} else {
|
||||||
|
fnctRtn = { sts: 'OK', msg: response.message?.toString() };
|
||||||
|
this.digestQueueUpdate(response.data.simulation_queue);
|
||||||
|
}
|
||||||
|
if (debugLog) {
|
||||||
|
console.log('response from simulate_control_start: ', response);
|
||||||
|
}
|
||||||
|
// return response.message;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'test-mode':
|
||||||
|
socket.emit(
|
||||||
|
'simulation_control',
|
||||||
|
{ command: 'test-mode' },
|
||||||
|
(response: SimulationControlResponse) => {
|
||||||
|
if (response.command_status === 'ERROR') {
|
||||||
|
throw new Error(response.message);
|
||||||
|
} else {
|
||||||
|
if (!response.data?.simulation_queue) {
|
||||||
|
fnctRtn = { sts: 'error', msg: 'Simulation queue data missing' };
|
||||||
|
throw new Error(response.message);
|
||||||
|
} else {
|
||||||
|
fnctRtn = { sts: 'OK', msg: response.message?.toString() };
|
||||||
|
this.digestQueueUpdate(response.data.simulation_queue);
|
||||||
|
}
|
||||||
|
if (debugLog) {
|
||||||
|
console.log('response from simulate_control_test-mode: ', response);
|
||||||
|
}
|
||||||
|
return response.message;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'gps-noise':
|
||||||
|
socket.emit(
|
||||||
|
'simulation_control',
|
||||||
|
{ command: 'gps-noise' },
|
||||||
|
(response: SimulationControlResponse) => {
|
||||||
|
if (response.command_status === 'ERROR') {
|
||||||
|
throw new Error(response.message);
|
||||||
|
} else {
|
||||||
|
if (!response.data?.simulation_queue) {
|
||||||
|
fnctRtn = { sts: 'error', msg: 'Simulation queue data missing' };
|
||||||
|
throw new Error(response.message);
|
||||||
|
} else {
|
||||||
|
fnctRtn = { sts: 'OK', msg: response.message?.toString() };
|
||||||
|
this.digestQueueUpdate(response.data.simulation_queue);
|
||||||
|
}
|
||||||
|
if (debugLog) {
|
||||||
|
console.log('response from simulate_control_gps-noise: ', response);
|
||||||
|
}
|
||||||
|
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.command_status === 'ERROR') {
|
||||||
|
throw new Error(response.message);
|
||||||
|
} else {
|
||||||
|
if (!response.data?.simulation_queue) {
|
||||||
|
fnctRtn = { sts: 'error', msg: 'Simulation queue data missing' };
|
||||||
|
throw new Error(response.message);
|
||||||
|
} else {
|
||||||
|
fnctRtn = { sts: 'OK', msg: response.message?.toString() };
|
||||||
|
this.digestQueueUpdate(response.data.simulation_queue);
|
||||||
|
}
|
||||||
|
if (debugLog) {
|
||||||
|
console.log('response from simulate_control_pause: ', response);
|
||||||
|
}
|
||||||
|
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.command_status == 'ERROR') {
|
||||||
|
throw new Error(response.message);
|
||||||
|
} else {
|
||||||
|
if (!response.data?.simulation_queue) {
|
||||||
|
fnctRtn = { sts: 'error', msg: 'Simulation queue data missing' };
|
||||||
|
throw new Error(response.message);
|
||||||
|
} else {
|
||||||
|
fnctRtn = { sts: 'OK', msg: response.message?.toString() };
|
||||||
|
this.digestQueueUpdate(response.data.simulation_queue);
|
||||||
|
}
|
||||||
|
if (debugLog) {
|
||||||
|
console.log('response from simulate_control_resume: ', response);
|
||||||
|
}
|
||||||
|
return response.message;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'clear':
|
||||||
|
if (this.simulationQueueLength == 0) {
|
||||||
|
throw new Error('Simulation queue is empty');
|
||||||
|
}
|
||||||
|
socket.emit('simulation_control', { command: 'clear' }, (response) => {
|
||||||
|
if (response.command_status == 'ERROR') {
|
||||||
|
throw new Error(response.message);
|
||||||
|
} else {
|
||||||
|
if (!response.data?.simulation_queue) {
|
||||||
|
fnctRtn = { sts: 'error', msg: 'Simulation queue data missing' };
|
||||||
|
throw new Error(response.message);
|
||||||
|
} else {
|
||||||
|
fnctRtn = { sts: 'OK', msg: response.message?.toString() };
|
||||||
|
this.digestQueueUpdate(response.data.simulation_queue);
|
||||||
|
}
|
||||||
|
if (debugLog) {
|
||||||
|
console.log('response from simulate_control_clear: ', response);
|
||||||
|
}
|
||||||
|
return response.message;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'reset':
|
||||||
|
if (this.simulationQueueLength == 0) {
|
||||||
|
throw new Error('Simulation queue is empty');
|
||||||
|
}
|
||||||
|
socket.emit('simulation_control', { command: 'reset' }, (response) => {
|
||||||
|
if (response.command_status == 'ERROR') {
|
||||||
|
throw new Error(response.message);
|
||||||
|
} else {
|
||||||
|
if (!response.data?.simulation_queue) {
|
||||||
|
fnctRtn = { sts: 'error', msg: 'Simulation queue data missing' };
|
||||||
|
throw new Error(response.message);
|
||||||
|
} else {
|
||||||
|
fnctRtn = { sts: 'OK', msg: response.message?.toString() };
|
||||||
|
this.digestQueueUpdate(response.data.simulation_queue);
|
||||||
|
}
|
||||||
|
if (debugLog) {
|
||||||
|
console.log('response from simulate_control_reset: ', response);
|
||||||
|
}
|
||||||
|
return response.message;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'end':
|
||||||
|
if (this.simulationState == 'ENDED' || !this.simulationRunning) {
|
||||||
|
throw new Error('Simulation has already ended');
|
||||||
|
}
|
||||||
|
socket.emit('simulation_control', { command: 'end' }, (response) => {
|
||||||
|
if (response.command_status == 'ERROR') {
|
||||||
|
throw new Error(response.message);
|
||||||
|
} else {
|
||||||
|
if (!response.data?.simulation_queue) {
|
||||||
|
fnctRtn = { sts: 'error', msg: 'Simulation queue data missing' };
|
||||||
|
throw new Error(response.message);
|
||||||
|
} else {
|
||||||
|
fnctRtn = { sts: 'OK', msg: response.message?.toString() };
|
||||||
|
this.digestQueueUpdate(response.data.simulation_queue);
|
||||||
|
}
|
||||||
|
if (debugLog) {
|
||||||
|
console.log('response from simulate_control_end: ', response);
|
||||||
|
}
|
||||||
|
return response.message;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'add':
|
||||||
|
if (!latitude || !longitude) {
|
||||||
|
throw new Error('latitude or longitude not set');
|
||||||
|
}
|
||||||
|
if (!address) {
|
||||||
|
address = '';
|
||||||
|
}
|
||||||
|
socket.emit(
|
||||||
|
'simulation_control',
|
||||||
|
{
|
||||||
|
command: 'add',
|
||||||
|
latitude: latitude,
|
||||||
|
longitude: longitude,
|
||||||
|
delay: delay,
|
||||||
|
address: address,
|
||||||
|
},
|
||||||
|
(response) => {
|
||||||
|
if (response.command_status == 'ERROR') {
|
||||||
|
throw new Error(response.message);
|
||||||
|
} else {
|
||||||
|
if (!response.data?.simulation_queue) {
|
||||||
|
fnctRtn = { sts: 'error', msg: 'Simulation queue data missing' };
|
||||||
|
throw new Error(response.message);
|
||||||
|
} else {
|
||||||
|
fnctRtn = { sts: 'OK', msg: response.message?.toString() };
|
||||||
|
this.digestQueueUpdate(response.data.simulation_queue);
|
||||||
|
}
|
||||||
|
if (debugLog) {
|
||||||
|
console.log('response from simulate_control_add: ', response);
|
||||||
|
}
|
||||||
|
return response.message;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'delete':
|
||||||
|
if (!loc_id) {
|
||||||
|
throw new Error('loc_id not set.');
|
||||||
|
}
|
||||||
|
socket.emit('simulation_control', { command: 'delete', loc_id: loc_id }, (response) => {
|
||||||
|
if (response.command_status == 'ERROR') {
|
||||||
|
throw new Error(response.message);
|
||||||
|
} else {
|
||||||
|
if (!response.data?.simulation_queue) {
|
||||||
|
fnctRtn = { sts: 'error', msg: 'Simulation queue data missing' };
|
||||||
|
throw new Error(response.message);
|
||||||
|
} else {
|
||||||
|
fnctRtn = { sts: 'OK', msg: response.message?.toString() };
|
||||||
|
this.digestQueueUpdate(response.data.simulation_queue);
|
||||||
|
}
|
||||||
|
if (debugLog) {
|
||||||
|
console.log('response from simulate_control_delete: ', response);
|
||||||
|
}
|
||||||
|
return response.message;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.updateLocationMark(loc_id, 'status', 'deleted');
|
||||||
|
break;
|
||||||
|
case 'next':
|
||||||
|
socket.emit('simulation_control', { command: 'next' }, (response) => {
|
||||||
|
if (response.command_status == 'ERROR') {
|
||||||
|
throw new Error(response.message);
|
||||||
|
} else {
|
||||||
|
this.simulationState = response.data?.simulation_queue?.state;
|
||||||
|
if (debugLog) {
|
||||||
|
console.log('response from simulate_control_next: ', response);
|
||||||
|
}
|
||||||
|
return response.message;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fnctRtn = { sts: 'error', msg: 'Invalid command' };
|
||||||
|
throw new Error('Invalid command');
|
||||||
|
}
|
||||||
|
return fnctRtn;
|
||||||
|
},
|
||||||
|
updateLocationMark(loc_id: string, key: string, value: string | number) {
|
||||||
|
const socketStore = useSocketStore();
|
||||||
|
const debugLog = socketStore.debugLog;
|
||||||
|
let fnctRtn: { command_status: string; message: string };
|
||||||
|
if (debugLog) {
|
||||||
|
console.log(
|
||||||
|
'socketStore: Update LocationMark request, loc_id: %s, key: %s, new value: %s',
|
||||||
|
loc_id,
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!this.locationQueueData[loc_id]) {
|
||||||
|
fnctRtn = {
|
||||||
|
command_status: 'error',
|
||||||
|
message: 'Location Id: ' + loc_id + ' is not in the simulation queue',
|
||||||
|
};
|
||||||
|
throw new Error('loc_id ' + loc_id + 'not found');
|
||||||
|
}
|
||||||
|
if (debugLog) {
|
||||||
|
console.log('Emitting LocationItem Update');
|
||||||
|
}
|
||||||
|
socket.emit(
|
||||||
|
'location_item_update',
|
||||||
|
{ loc_id: loc_id, key: key, value: value },
|
||||||
|
(response: LocationMarkUpdateResponse) => {
|
||||||
|
if (response.command_status == 'ERROR') {
|
||||||
|
fnctRtn = { command_status: 'error', message: response.message?.toString() };
|
||||||
|
throw new Error(response.message);
|
||||||
|
} else {
|
||||||
|
fnctRtn = { command_status: 'OK', message: response.message?.toString() };
|
||||||
|
if (debugLog) {
|
||||||
|
console.log('response from backend: ', response);
|
||||||
|
}
|
||||||
|
this.locationQueueData[loc_id] = response.data;
|
||||||
|
}
|
||||||
|
return fnctRtn;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
updateLocationQueueOrder(newOrder: string[]) {
|
||||||
|
const socketStore = useSocketStore();
|
||||||
|
const debugLog = socketStore.debugLog;
|
||||||
|
let fnctRtn: { command_status: string; message: string };
|
||||||
|
if (debugLog) {
|
||||||
|
console.log('socketStore: Update Location Queue Order, new order: %s', newOrder);
|
||||||
|
}
|
||||||
|
socket.emit('queue_order_update', { newOrder: newOrder }, (response) => {
|
||||||
|
if (response.command_status == 'ERROR') {
|
||||||
|
fnctRtn = { command_status: 'error', message: response.message?.toString() };
|
||||||
|
throw new Error(response.message);
|
||||||
|
} else {
|
||||||
|
fnctRtn = { command_status: 'OK', message: response.message?.toString() };
|
||||||
|
if (debugLog) {
|
||||||
|
console.log('response from queue_order_update: ', response);
|
||||||
|
}
|
||||||
|
this.locationQueueOrder = response.data;
|
||||||
|
}
|
||||||
|
return fnctRtn;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
digestQueueUpdate(data: QueueData): void {
|
||||||
|
console.log('digesting QueueUpdate: ', data);
|
||||||
|
this.simulationRunning = data.active;
|
||||||
|
console.log('Setting SimulationState to %s', data.state);
|
||||||
|
this.simulationState = data.state;
|
||||||
|
this.simulationQueueLength = data.order.length;
|
||||||
|
this.locationQueueData = data.data;
|
||||||
|
this.locationQueueOrder = data.order;
|
||||||
|
this.locationQueueDeletedItems = data.deleted_items;
|
||||||
|
this.testMode = data.test_mode;
|
||||||
|
this.gpsNoise = data.gps_noise;
|
||||||
|
},
|
||||||
|
digestCurrentLocation(data: CurrentLocation): void {
|
||||||
|
this.currentLocation = {
|
||||||
|
loc_id: data.loc_id,
|
||||||
|
latitude: data.latitude,
|
||||||
|
longitude: data.longitude,
|
||||||
|
next_move: data.next_move,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (import.meta.hot) {
|
||||||
|
import.meta.hot.accept(acceptHMRUpdate(useSimulationStore, import.meta.hot));
|
||||||
|
}
|
||||||
129
src/stores/socket.ts
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
import { defineStore, acceptHMRUpdate } from 'pinia';
|
||||||
|
import { socket } from 'boot/socketio';
|
||||||
|
|
||||||
|
import { useDeviceStore } from 'stores/device';
|
||||||
|
import { useIcloudStore } from 'stores/icloud';
|
||||||
|
import { useSimulationStore } from 'stores/simulation';
|
||||||
|
import { useTunneldStore } from 'stores/tunneld';
|
||||||
|
|
||||||
|
import type { AppError, ErrorFull, StatusUpdate } from 'components/models';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const useSocketStore = defineStore('socketStore', {
|
||||||
|
state: () => {
|
||||||
|
return {
|
||||||
|
sockConnected: false as boolean,
|
||||||
|
socketID: null as string | null | undefined,
|
||||||
|
errorList: [] as ErrorFull[],
|
||||||
|
debugLog: true,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getters: {},
|
||||||
|
actions: {
|
||||||
|
setSockStatus() {
|
||||||
|
this.sockConnected = socket.connected;
|
||||||
|
this.socketID = socket.id;
|
||||||
|
},
|
||||||
|
connect() {
|
||||||
|
if (this.debugLog) {
|
||||||
|
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();
|
||||||
|
},
|
||||||
|
bindEvents() {
|
||||||
|
this.setSockStatus();
|
||||||
|
|
||||||
|
// connect
|
||||||
|
socket.on('connect', () => {
|
||||||
|
this.setSockStatus();
|
||||||
|
socket.emit('message', 'Hello from client', (e: boolean) => {
|
||||||
|
if (this.debugLog) {
|
||||||
|
console.log('Message delivered: ' + e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (this.debugLog) {
|
||||||
|
console.log('Connected to server');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// disconnect
|
||||||
|
socket.on('disconnect', () => {
|
||||||
|
this.setSockStatus();
|
||||||
|
console.log('Disconnected from server');
|
||||||
|
});
|
||||||
|
|
||||||
|
// error
|
||||||
|
socket.on('error', (data: ErrorFull) => {
|
||||||
|
this.setSockStatus();
|
||||||
|
const errorFull = { type: data.type, error: data.error };
|
||||||
|
this.errorList.push(errorFull);
|
||||||
|
console.error('Error Received: ', data);
|
||||||
|
});
|
||||||
|
|
||||||
|
// app_error
|
||||||
|
socket.on('app_error', (data: AppError) => {
|
||||||
|
this.setSockStatus();
|
||||||
|
const errorFull = {
|
||||||
|
type: data.type,
|
||||||
|
error: data.error,
|
||||||
|
message: data.message,
|
||||||
|
data: data.data,
|
||||||
|
};
|
||||||
|
this.errorList.push(errorFull);
|
||||||
|
console.error('Error Received: ', data);
|
||||||
|
});
|
||||||
|
|
||||||
|
// status
|
||||||
|
socket.on('status', (data: StatusUpdate): void => {
|
||||||
|
if (this.debugLog) {
|
||||||
|
console.log('StatusUpdate received: ', data);
|
||||||
|
}
|
||||||
|
this.digestUpdate(data);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
requestUpdate(): void {
|
||||||
|
socket.emit('request_update', (response: StatusUpdate) => {
|
||||||
|
this.digestUpdate(response);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
digestUpdate(data: StatusUpdate): void {
|
||||||
|
const deviceStore = useDeviceStore();
|
||||||
|
const icloudStore = useIcloudStore();
|
||||||
|
const simulationStore = useSimulationStore();
|
||||||
|
const tunneldStore = useTunneldStore();
|
||||||
|
if (data.simulation_queue) {
|
||||||
|
simulationStore.digestQueueUpdate(data.simulation_queue);
|
||||||
|
}
|
||||||
|
deviceStore.deviceConnected = !!(data.udid && data.tunnel);
|
||||||
|
if (data.current_location) {
|
||||||
|
simulationStore.digestCurrentLocation(data.current_location);
|
||||||
|
}
|
||||||
|
tunneldStore.tunnelConnected = !!data.tunnel;
|
||||||
|
tunneldStore.tunneldWatcher = data.tunnel_watcher_running;
|
||||||
|
if (data.icloud) {
|
||||||
|
icloudStore.digestUpdate(data.icloud);
|
||||||
|
}
|
||||||
|
icloudStore.findMyUpdate = data.fmf_location;
|
||||||
|
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (import.meta.hot) {
|
||||||
|
import.meta.hot.accept(acceptHMRUpdate(useSocketStore, import.meta.hot));
|
||||||
|
}
|
||||||
@@ -1,54 +1,24 @@
|
|||||||
import { defineStore, acceptHMRUpdate } from 'pinia';
|
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,
|
|
||||||
LocationQueue,
|
|
||||||
SimulationControlResponse,
|
|
||||||
StatusUpdate,
|
|
||||||
FindMyUpdate,
|
|
||||||
iCloudMonitorResponse,
|
|
||||||
SimulationStatus,
|
|
||||||
LocationMarkUpdateResponse,
|
|
||||||
LocationItemUpdate,
|
|
||||||
NominatimResponse,
|
|
||||||
NominatimRequest,
|
|
||||||
AppError,
|
|
||||||
QueueData,
|
|
||||||
} from 'components/models';
|
|
||||||
|
|
||||||
const $q = useQuasar();
|
|
||||||
const debugLog: boolean = true;
|
|
||||||
|
|
||||||
|
|
||||||
export const useSocketioStore = defineStore('socketio', {
|
export const useSocketioStore = defineStore('socketio', {
|
||||||
state: () => {
|
state: () => {
|
||||||
return {
|
return {
|
||||||
sockConnected: false as boolean,
|
|
||||||
socketID: null as string | null | undefined,
|
|
||||||
testMode: null as boolean | undefined | null,
|
|
||||||
gpsNoise: null as boolean | undefined | null,
|
// nextLocation: null as NextLocation | null,
|
||||||
deviceConnected: false as boolean,
|
// messageList: [''] as string[],
|
||||||
tunnelConnected: false as boolean,
|
|
||||||
simulationRunning: false as boolean | undefined | null,
|
|
||||||
simulationState: null as string | null | undefined,
|
|
||||||
simulationQueueLength: 0 as number | null | undefined,
|
|
||||||
currentLocation: null as CurrentLocation | null | undefined,
|
|
||||||
nextLocation: null as NextLocation | null,
|
|
||||||
messageList: [''] as string[],
|
|
||||||
errorList: [] as ErrorFull[],
|
|
||||||
locationQueueData: {} as LocationQueue,
|
|
||||||
locationQueueOrder: [] as string[],
|
|
||||||
locationQueueDeletedItems: [] as string[],
|
|
||||||
leafletZoom: 10 as number,
|
leafletZoom: 10 as number,
|
||||||
icloudMonitor: false as boolean,
|
|
||||||
findMyUpdate: null as FindMyUpdate | null | undefined,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
sockState: (state) => state.sockConnected,
|
/* sockState: (state) => state.sockConnected,
|
||||||
deviceState: (state) => state.deviceConnected,
|
deviceState: (state) => state.deviceConnected,
|
||||||
lMarkerLatLng: (state): [number, number] => {
|
lMarkerLatLng: (state): [number, number] => {
|
||||||
if (
|
if (
|
||||||
@@ -64,687 +34,27 @@ export const useSocketioStore = defineStore('socketio', {
|
|||||||
return this.lMarkerLatLng;
|
return this.lMarkerLatLng;
|
||||||
},
|
},
|
||||||
lZoom: (state): number => state.leafletZoom,
|
lZoom: (state): number => state.leafletZoom,
|
||||||
|
*/
|
||||||
|
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
setSockStatus() {
|
|
||||||
this.sockConnected = socket.connected;
|
|
||||||
this.socketID = socket.id;
|
|
||||||
},
|
|
||||||
bindEvents() {
|
|
||||||
|
|
||||||
this.setSockStatus();
|
|
||||||
|
|
||||||
// connect
|
|
||||||
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');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// disconnect
|
|
||||||
socket.on('disconnect', () => {
|
|
||||||
this.setSockStatus();
|
|
||||||
console.log('Disconnected from server');
|
|
||||||
});
|
|
||||||
|
|
||||||
// message
|
|
||||||
socket.on('message', (e: string) => {
|
|
||||||
this.setSockStatus();
|
|
||||||
this.messageList.push(e);
|
|
||||||
if (debugLog) {
|
|
||||||
console.log('Websock message received!');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// error
|
|
||||||
socket.on('error', (data: ErrorFull) => {
|
|
||||||
this.setSockStatus();
|
|
||||||
const errorFull = { type: data.type, error: data.error };
|
|
||||||
this.errorList.push(errorFull);
|
|
||||||
console.error('Error Received: ', data);
|
|
||||||
});
|
|
||||||
|
|
||||||
// app_error
|
|
||||||
socket.on('app_error', (data: AppError) => {
|
|
||||||
this.setSockStatus();
|
|
||||||
const errorFull = { type: data.type, error: data.error, message: data.message, data: data.data };
|
|
||||||
this.errorList.push(errorFull);
|
|
||||||
console.error('Error Received: ', data);
|
|
||||||
});
|
|
||||||
|
|
||||||
// status
|
|
||||||
socket.on('status', (data: StatusUpdate): void => {
|
|
||||||
if (debugLog) {
|
|
||||||
console.log('StatusUpdate received: ', data);
|
|
||||||
}
|
|
||||||
this.digestUpdate(data);
|
|
||||||
});
|
|
||||||
|
|
||||||
// fmf_update
|
|
||||||
socket.on('fmf_update', (data: FindMyUpdate): void => {
|
|
||||||
if (debugLog) {
|
|
||||||
console.log('event: fmf_update received: ', data);
|
|
||||||
}
|
|
||||||
this.findMyUpdate = data;
|
|
||||||
});
|
|
||||||
|
|
||||||
// simulation_status
|
|
||||||
socket.on('simulation_status', (data: SimulationStatus): void => {
|
|
||||||
if (debugLog) {
|
|
||||||
console.log('event: simulation_status received: ', data);
|
|
||||||
}
|
|
||||||
console.log('updating currentLocation', data);
|
|
||||||
this.currentLocation = {
|
|
||||||
loc_id: data.loc_id,
|
|
||||||
latitude: data.latitude,
|
|
||||||
longitude: data.longitude
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// icloud_2fa_request
|
|
||||||
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');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// queue_data_update
|
|
||||||
socket.on('queue_data_update', (inData: QueueData): void => {
|
|
||||||
if (debugLog) {
|
|
||||||
console.log('QueueUpdate received: ', inData);
|
|
||||||
}
|
|
||||||
this.digestQueueUpdate(inData);
|
|
||||||
});
|
|
||||||
// location_item_update
|
|
||||||
socket.on('location_item_update', (inData: LocationItemUpdate): void => {
|
|
||||||
console.log('Location item update received, data: ', inData);
|
|
||||||
this.locationQueueData[inData.loc_id] = inData.data;
|
|
||||||
});
|
|
||||||
/*
|
|
||||||
socket.onAny((eventName, ...args) => {
|
|
||||||
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();
|
|
||||||
},
|
|
||||||
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;
|
|
||||||
},
|
|
||||||
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.command_status;
|
|
||||||
if (response.command_status == 'ERROR') {
|
|
||||||
if (response.message) {
|
|
||||||
if (debugLog) {
|
|
||||||
console.log(response.message);
|
|
||||||
}
|
|
||||||
fnctRtn.msg = response.message;
|
|
||||||
} else {
|
|
||||||
fnctRtn.msg = 'Error';
|
|
||||||
}
|
|
||||||
throw new Error(fnctRtn.msg);
|
|
||||||
} else {
|
|
||||||
fnctRtn = { sts: 'OK', msg: 'iCloud Monitor: ' + response.command_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.command_status;
|
|
||||||
if (response.command_status == 'ERROR') {
|
|
||||||
if (response.message) {
|
|
||||||
if (debugLog) {
|
|
||||||
console.log(response.message);
|
|
||||||
}
|
|
||||||
fnctRtn.msg = response.message;
|
|
||||||
} else {
|
|
||||||
fnctRtn.msg = 'Error';
|
|
||||||
}
|
|
||||||
throw new Error(fnctRtn.msg);
|
|
||||||
} else {
|
|
||||||
fnctRtn = { sts: 'OK', msg: 'iCloud Monitor: ' + response.command_status };
|
|
||||||
this.icloudMonitor = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case 'status':
|
|
||||||
if (debugLog) {
|
|
||||||
console.log('socketStore: got command: icloudMonitor status');
|
|
||||||
}
|
|
||||||
socket.emit(
|
|
||||||
'icloud_monitor_control',
|
|
||||||
{ command: 'get' },
|
|
||||||
(response: iCloudMonitorResponse) => {
|
|
||||||
fnctRtn.sts = response.command_status;
|
|
||||||
if (response.command_status == 'error') {
|
|
||||||
if (response.message) {
|
|
||||||
if (debugLog) {
|
|
||||||
console.log(response.message);
|
|
||||||
}
|
|
||||||
fnctRtn.msg = response.message;
|
|
||||||
} else {
|
|
||||||
fnctRtn.msg = 'Error';
|
|
||||||
}
|
|
||||||
throw new Error(fnctRtn.msg);
|
|
||||||
} else {
|
|
||||||
fnctRtn = {
|
|
||||||
sts: 'OK',
|
|
||||||
msg: 'iCloud Location Requested',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
this.icloudMonitor = !!(
|
|
||||||
response.icloud_monitor_enabled && response.icloud_monitor_running
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case 'refresh':
|
|
||||||
if (debugLog) {
|
|
||||||
console.log('socketStore: got command: icloudMonitor refresh');
|
|
||||||
}
|
|
||||||
socket.emit(
|
|
||||||
'icloud_monitor_control',
|
|
||||||
{ command: 'status' },
|
|
||||||
(response: iCloudMonitorResponse) => {
|
|
||||||
fnctRtn.sts = response.command_status;
|
|
||||||
if (response.command_status == 'ERROR') {
|
|
||||||
if (response.message) {
|
|
||||||
if (debugLog) {
|
|
||||||
console.log(response.message);
|
|
||||||
}
|
|
||||||
fnctRtn.msg = response.message;
|
|
||||||
} else {
|
|
||||||
fnctRtn.msg = 'Error';
|
|
||||||
}
|
|
||||||
throw new Error(fnctRtn.msg);
|
|
||||||
} else {
|
|
||||||
fnctRtn = {
|
|
||||||
sts: 'OK',
|
|
||||||
msg:
|
|
||||||
'iCloud 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,
|
|
||||||
latitude,
|
|
||||||
longitude,
|
|
||||||
loc_id,
|
|
||||||
delay,
|
|
||||||
address,
|
|
||||||
}: {
|
|
||||||
command: string;
|
|
||||||
latitude?: number | null | undefined;
|
|
||||||
longitude?: number | null | undefined;
|
|
||||||
loc_id?: string | null | undefined;
|
|
||||||
delay?: number | null | undefined;
|
|
||||||
address?: string | null | undefined;
|
|
||||||
}) {
|
|
||||||
let fnctRtn: { sts: string; msg?: string | undefined } = { sts: '', msg: '' };
|
|
||||||
this.setSockStatus();
|
|
||||||
switch (command) {
|
|
||||||
case 'start':
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
if (debugLog) {
|
|
||||||
console.log('Emitting simulation_control: start');
|
|
||||||
}
|
|
||||||
socket.emit(
|
|
||||||
'simulation_control',
|
|
||||||
{ command: 'start', delay: 0, latitude: null, longitude: null },
|
|
||||||
(response: SimulationControlResponse) => {
|
|
||||||
if (response.command_status == 'ERROR') {
|
|
||||||
fnctRtn = { sts: 'error', msg: response.message?.toString() };
|
|
||||||
throw new Error(response.message);
|
|
||||||
} else {
|
|
||||||
if (!response.data?.simulation_queue) {
|
|
||||||
fnctRtn = { sts: 'error', msg: "Simulation queue data missing"};
|
|
||||||
throw new Error(response.message);
|
|
||||||
} else {
|
|
||||||
fnctRtn = { sts: 'OK', msg: response.message?.toString() };
|
|
||||||
this.digestQueueUpdate(response.data.simulation_queue);
|
|
||||||
}
|
|
||||||
if (debugLog) {
|
|
||||||
console.log('response from simulate_control_start: ', response);
|
|
||||||
}
|
|
||||||
// return response.message;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case 'test-mode':
|
|
||||||
socket.emit(
|
|
||||||
'simulation_control',
|
|
||||||
{ command: 'test-mode' },
|
|
||||||
(response: SimulationControlResponse) => {
|
|
||||||
if (response.command_status === 'ERROR') {
|
|
||||||
throw new Error(response.message);
|
|
||||||
} else {
|
|
||||||
if (!response.data?.simulation_queue) {
|
|
||||||
fnctRtn = { sts: 'error', msg: 'Simulation queue data missing' };
|
|
||||||
throw new Error(response.message);
|
|
||||||
} else {
|
|
||||||
fnctRtn = { sts: 'OK', msg: response.message?.toString() };
|
|
||||||
this.digestQueueUpdate(response.data.simulation_queue);
|
|
||||||
}
|
|
||||||
if (debugLog) {
|
|
||||||
console.log('response from simulate_control_test-mode: ', response);
|
|
||||||
}
|
|
||||||
return response.message;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case 'gps-noise':
|
|
||||||
socket.emit(
|
|
||||||
'simulation_control',
|
|
||||||
{ command: 'gps-noise' },
|
|
||||||
(response: SimulationControlResponse) => {
|
|
||||||
if (response.command_status === 'ERROR') {
|
|
||||||
throw new Error(response.message);
|
|
||||||
} else {
|
|
||||||
if (!response.data?.simulation_queue) {
|
|
||||||
fnctRtn = { sts: 'error', msg: 'Simulation queue data missing' };
|
|
||||||
throw new Error(response.message);
|
|
||||||
} else {
|
|
||||||
fnctRtn = { sts: 'OK', msg: response.message?.toString() };
|
|
||||||
this.digestQueueUpdate(response.data.simulation_queue);
|
|
||||||
}
|
|
||||||
if (debugLog) {
|
|
||||||
console.log('response from simulate_control_gps-noise: ', response);
|
|
||||||
}
|
|
||||||
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.command_status === 'ERROR') {
|
|
||||||
throw new Error(response.message);
|
|
||||||
} else {
|
|
||||||
if (!response.data?.simulation_queue) {
|
|
||||||
fnctRtn = { sts: 'error', msg: 'Simulation queue data missing' };
|
|
||||||
throw new Error(response.message);
|
|
||||||
} else {
|
|
||||||
fnctRtn = { sts: 'OK', msg: response.message?.toString() };
|
|
||||||
this.digestQueueUpdate(response.data.simulation_queue);
|
|
||||||
}
|
|
||||||
if (debugLog) {
|
|
||||||
console.log('response from simulate_control_pause: ', response);
|
|
||||||
}
|
|
||||||
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.command_status == 'ERROR') {
|
|
||||||
throw new Error(response.message);
|
|
||||||
} else {
|
|
||||||
if (!response.data?.simulation_queue) {
|
|
||||||
fnctRtn = { sts: 'error', msg: 'Simulation queue data missing' };
|
|
||||||
throw new Error(response.message);
|
|
||||||
} else {
|
|
||||||
fnctRtn = { sts: 'OK', msg: response.message?.toString() };
|
|
||||||
this.digestQueueUpdate(response.data.simulation_queue);
|
|
||||||
}
|
|
||||||
if (debugLog) {
|
|
||||||
console.log('response from simulate_control_resume: ', response);
|
|
||||||
}
|
|
||||||
return response.message;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case 'clear':
|
|
||||||
if (this.simulationQueueLength == 0) {
|
|
||||||
throw new Error('Simulation queue is empty');
|
|
||||||
}
|
|
||||||
socket.emit('simulation_control', { command: 'clear' }, (response) => {
|
|
||||||
if (response.command_status == 'ERROR') {
|
|
||||||
throw new Error(response.message);
|
|
||||||
} else {
|
|
||||||
if (!response.data?.simulation_queue) {
|
|
||||||
fnctRtn = { sts: 'error', msg: 'Simulation queue data missing' };
|
|
||||||
throw new Error(response.message);
|
|
||||||
} else {
|
|
||||||
fnctRtn = { sts: 'OK', msg: response.message?.toString() };
|
|
||||||
this.digestQueueUpdate(response.data.simulation_queue);
|
|
||||||
}
|
|
||||||
if (debugLog) {
|
|
||||||
console.log('response from simulate_control_clear: ', response);
|
|
||||||
}
|
|
||||||
return response.message;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case 'end':
|
|
||||||
if (this.simulationState == 'ENDED' || !this.simulationRunning) {
|
|
||||||
throw new Error('Simulation has already ended');
|
|
||||||
}
|
|
||||||
socket.emit('simulation_control', { command: 'end' }, (response) => {
|
|
||||||
if (response.command_status == 'ERROR') {
|
|
||||||
throw new Error(response.message);
|
|
||||||
} else {
|
|
||||||
if (!response.data?.simulation_queue) {
|
|
||||||
fnctRtn = { sts: 'error', msg: 'Simulation queue data missing' };
|
|
||||||
throw new Error(response.message);
|
|
||||||
} else {
|
|
||||||
fnctRtn = { sts: 'OK', msg: response.message?.toString() };
|
|
||||||
this.digestQueueUpdate(response.data.simulation_queue);
|
|
||||||
}
|
|
||||||
if (debugLog) {
|
|
||||||
console.log('response from simulate_control_end: ', response);
|
|
||||||
}
|
|
||||||
return response.message;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case 'add':
|
|
||||||
if (!latitude || !longitude) {
|
|
||||||
throw new Error('latitude or longitude not set');
|
|
||||||
}
|
|
||||||
if (!address) {
|
|
||||||
address = '{latitude}, {longitude}';
|
|
||||||
}
|
|
||||||
socket.emit(
|
|
||||||
'simulation_control',
|
|
||||||
{
|
|
||||||
command: 'add',
|
|
||||||
latitude: latitude,
|
|
||||||
longitude: longitude,
|
|
||||||
delay: delay,
|
|
||||||
address: address,
|
|
||||||
},
|
|
||||||
(response) => {
|
|
||||||
if (response.command_status == 'ERROR') {
|
|
||||||
throw new Error(response.message);
|
|
||||||
} else {
|
|
||||||
if (!response.data?.simulation_queue) {
|
|
||||||
fnctRtn = { sts: 'error', msg: 'Simulation queue data missing' };
|
|
||||||
throw new Error(response.message);
|
|
||||||
} else {
|
|
||||||
fnctRtn = { sts: 'OK', msg: response.message?.toString() };
|
|
||||||
this.digestQueueUpdate(response.data.simulation_queue);
|
|
||||||
}
|
|
||||||
if (debugLog) {
|
|
||||||
console.log('response from simulate_control_add: ', response);
|
|
||||||
}
|
|
||||||
return response.message;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case 'delete':
|
|
||||||
if (!loc_id) {
|
|
||||||
throw new Error('loc_id not set.');
|
|
||||||
}
|
|
||||||
socket.emit('simulation_control', { command: 'delete', loc_id: loc_id }, (response) => {
|
|
||||||
if (response.command_status == 'ERROR') {
|
|
||||||
throw new Error(response.message);
|
|
||||||
} else {
|
|
||||||
if (!response.data?.simulation_queue) {
|
|
||||||
fnctRtn = { sts: 'error', msg: 'Simulation queue data missing' };
|
|
||||||
throw new Error(response.message);
|
|
||||||
} else {
|
|
||||||
fnctRtn = { sts: 'OK', msg: response.message?.toString() };
|
|
||||||
this.digestQueueUpdate(response.data.simulation_queue);
|
|
||||||
}
|
|
||||||
if (debugLog) {
|
|
||||||
console.log('response from simulate_control_delete: ', response);
|
|
||||||
}
|
|
||||||
return response.message;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.updateLocationMark(loc_id, 'status', 'deleted');
|
|
||||||
break;
|
|
||||||
case 'next':
|
|
||||||
socket.emit('simulation_control', { command: 'next' }, (response) => {
|
|
||||||
if (response.command_status == 'ERROR') {
|
|
||||||
throw new Error(response.message);
|
|
||||||
} else {
|
|
||||||
this.simulationState = response.data?.simulation_queue?.state;
|
|
||||||
if (debugLog) {
|
|
||||||
console.log('response from simulate_control_next: ', response);
|
|
||||||
}
|
|
||||||
return response.message;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
fnctRtn = { sts: 'error', msg: 'Invalid command' };
|
|
||||||
throw new Error('Invalid command');
|
|
||||||
}
|
|
||||||
return fnctRtn;
|
|
||||||
},
|
|
||||||
requestUpdate(): void {
|
|
||||||
socket.emit('request_update', (response: StatusUpdate) => {
|
|
||||||
this.digestUpdate(response);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
digestQueueUpdate(data: QueueData): void {
|
|
||||||
console.log("digesting QueueUpdate: ", data)
|
|
||||||
this.simulationRunning = data.active;
|
|
||||||
console.log("Setting SimulationState to %s", data.state)
|
|
||||||
this.simulationState = data.state;
|
|
||||||
this.simulationQueueLength = data.order.length;
|
|
||||||
this.locationQueueData = data.data;
|
|
||||||
this.locationQueueOrder = data.order;
|
|
||||||
this.locationQueueDeletedItems = data.deleted_items;
|
|
||||||
this.testMode = data.test_mode;
|
|
||||||
this.gpsNoise = data.gps_noise;
|
|
||||||
},
|
|
||||||
digestUpdate(data: StatusUpdate): void {
|
|
||||||
if (data.simulation_queue) {
|
|
||||||
this.digestQueueUpdate(data.simulation_queue)
|
|
||||||
}
|
|
||||||
this.deviceConnected = !!(data.udid && data.tunnel);
|
|
||||||
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.findMyUpdate = data.fmf_location;
|
|
||||||
},
|
|
||||||
setDeviceState(state: boolean) {
|
|
||||||
this.deviceConnected = state;
|
|
||||||
},
|
|
||||||
revGeoCode(nomRequest: NominatimRequest): Promise<NominatimResponse> {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
socket.emit(
|
|
||||||
'reverse_geocode',
|
|
||||||
{ latitude: nomRequest.latitude, longitude: nomRequest.longitude },
|
|
||||||
(response) => {
|
|
||||||
resolve(response);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
updateLocationMark(loc_id: string, key: string, value: string | number) {
|
|
||||||
let fnctRtn: { command_status: string; message: string };
|
|
||||||
if (debugLog) {
|
|
||||||
console.log(
|
|
||||||
'socketStore: Update LocationMark request, loc_id: %s, key: %s, new value: %s',
|
|
||||||
loc_id,
|
|
||||||
key,
|
|
||||||
value,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (!this.locationQueueData[loc_id]) {
|
|
||||||
fnctRtn = {
|
|
||||||
command_status: 'error',
|
|
||||||
message: 'Location Id: ' + loc_id + ' is not in the simulation queue',
|
|
||||||
};
|
|
||||||
throw new Error('loc_id ' + loc_id + 'not found');
|
|
||||||
}
|
|
||||||
if (debugLog) {
|
|
||||||
console.log('Emitting LocationItem Update');
|
|
||||||
}
|
|
||||||
socket.emit(
|
|
||||||
'location_item_update',
|
|
||||||
{ loc_id: loc_id, key: key, value: value },
|
|
||||||
(response: LocationMarkUpdateResponse) => {
|
|
||||||
if (response.command_status == 'ERROR') {
|
|
||||||
fnctRtn = { command_status: 'error', message: response.message?.toString() };
|
|
||||||
throw new Error(response.message);
|
|
||||||
} else {
|
|
||||||
fnctRtn = { command_status: 'OK', message: response.message?.toString() };
|
|
||||||
if (debugLog) {
|
|
||||||
console.log('response from backend: ', response);
|
|
||||||
}
|
|
||||||
this.locationQueueData[loc_id] = response.data;
|
|
||||||
}
|
|
||||||
return fnctRtn;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
updateLocationQueueOrder(newOrder: string[]) {
|
|
||||||
let fnctRtn: { command_status: string; message: string };
|
|
||||||
if (debugLog) {
|
|
||||||
console.log('socketStore: Update Location Queue Order, new order: %s', newOrder);
|
|
||||||
}
|
|
||||||
socket.emit('queue_order_update', { newOrder: newOrder }, (response) => {
|
|
||||||
if (response.command_status == 'ERROR') {
|
|
||||||
fnctRtn = { command_status: 'error', message: response.message?.toString() };
|
|
||||||
throw new Error(response.message);
|
|
||||||
} else {
|
|
||||||
fnctRtn = { command_status: 'OK', message: response.message?.toString() };
|
|
||||||
if (debugLog) {
|
|
||||||
console.log('response from queue_order_update: ', response);
|
|
||||||
}
|
|
||||||
this.locationQueueOrder = response.data;
|
|
||||||
}
|
|
||||||
return fnctRtn;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
48
src/stores/tunneld.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { defineStore, acceptHMRUpdate } from 'pinia';
|
||||||
|
import type { TunneldCommands } from 'components/models';
|
||||||
|
import { useSocketStore } from 'stores/socket';
|
||||||
|
import { socket } from 'boot/socketio';
|
||||||
|
|
||||||
|
export const useTunneldStore = defineStore('tunneldStore', {
|
||||||
|
state: () => {
|
||||||
|
return {
|
||||||
|
tunnelConnected: false as boolean,
|
||||||
|
tunneldWatcher: false as boolean,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getters: {},
|
||||||
|
actions: {
|
||||||
|
digestUpdate(data: boolean) {
|
||||||
|
this.tunnelConnected = data;
|
||||||
|
},
|
||||||
|
tunneldControl(command: TunneldCommands, udid: string = '') {
|
||||||
|
const socketStore = useSocketStore();
|
||||||
|
const debugLog = socketStore.debugLog;
|
||||||
|
let fnctRtn: { sts: string; msg?: string | undefined } = { sts: '', msg: '' };
|
||||||
|
if (debugLog) {
|
||||||
|
console.log('tunneldStore: got command: ', command);
|
||||||
|
}
|
||||||
|
let args: {command: TunneldCommands, udid?: string} = {command: command};
|
||||||
|
if (udid != '') {
|
||||||
|
args = { command: command, udid: udid };
|
||||||
|
}
|
||||||
|
socket.emit(
|
||||||
|
'tunneld_control', args, (response) => {
|
||||||
|
console.log(response.command_status, response.command);
|
||||||
|
if (response.command_status == 'ERROR') {
|
||||||
|
fnctRtn = { sts: 'error', msg: response.message?.toString() };
|
||||||
|
throw new Error(response.message);
|
||||||
|
} else {
|
||||||
|
fnctRtn = { sts: 'OK', msg: response.message?.toString() };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return fnctRtn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
if (import.meta.hot) {
|
||||||
|
import.meta.hot.accept(acceptHMRUpdate(useTunneldStore, import.meta.hot));
|
||||||
|
}
|
||||||