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
|
||||
extras: [
|
||||
// 'ionicons-v4',
|
||||
// 'mdi-v7',
|
||||
'mdi-v7',
|
||||
// 'fontawesome-v6',
|
||||
// 'eva-icons',
|
||||
// 'themify',
|
||||
// 'line-awesome',
|
||||
// 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both!
|
||||
|
||||
'roboto-font', // optional, you are not bound to it
|
||||
'material-icons', // 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
|
||||
],
|
||||
|
||||
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#build
|
||||
@@ -151,7 +151,8 @@ export default defineConfig((/* ctx */) => {
|
||||
config: {
|
||||
dark: true,
|
||||
},
|
||||
iconSet: 'material-icons', // Quasar icon set
|
||||
// iconSet: 'material-icons', // Quasar icon set
|
||||
iconSet: 'mdi-v7',
|
||||
// lang: 'en-US', // Quasar language pack
|
||||
|
||||
// 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>
|
||||
<div>
|
||||
<div style="white-space: pre-line">
|
||||
{{ formattedAddressLine1 }}
|
||||
<q-inner-loading v-if="loading">
|
||||
<q-spinner-dots color="primary" />
|
||||
</q-inner-loading>
|
||||
</div>
|
||||
<div>
|
||||
{{ formattedAddressLine2 }}
|
||||
<q-inner-loading v-if="loading">
|
||||
<q-spinner-dots color="primary" />
|
||||
</q-inner-loading>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
import type { NominatimResponse } from 'components/models';
|
||||
|
||||
const props = defineProps({
|
||||
address: Object as () => NominatimResponse,
|
||||
});
|
||||
|
||||
const loading = ref(false);
|
||||
const props = defineProps<{
|
||||
address?: NominatimResponse | undefined;
|
||||
}>();
|
||||
|
||||
const formattedAddressLine1 = computed(() => {
|
||||
if (!loading.value && props.address) {
|
||||
if (props.address) {
|
||||
const place = [props.address.leisure, props.address.shop].filter(Boolean);
|
||||
const addy = [props.address.house_number, props.address.road].filter(Boolean).join(' ');
|
||||
return [place, addy].filter(Boolean).join('\n');
|
||||
@@ -34,15 +28,11 @@ const formattedAddressLine1 = computed(() => {
|
||||
});
|
||||
|
||||
const formattedAddressLine2 = computed(() => {
|
||||
if (!loading.value && props.address) {
|
||||
const town: string = props.address.city
|
||||
? props.address.city
|
||||
: props.address.village
|
||||
? props.address.village
|
||||
: ' ';
|
||||
if (props.address) {
|
||||
const town = props.address.city ?? props.address.village ?? props.address.town;
|
||||
const stateAbbr = stateAbbrevMap[props.address.state] ?? props.address.state ?? ' ';
|
||||
const formAddress: string = town + ', ' + stateAbbr + ' ' + props.address.postcode;
|
||||
return formAddress;
|
||||
const cityState = [town, stateAbbr].filter(Boolean).join(', ');
|
||||
return [cityState, props.address.postcode].filter(Boolean).join(' ');
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
class="q-mr-sm"
|
||||
dense
|
||||
flat
|
||||
icon="menu"
|
||||
icon="mdi-menu"
|
||||
round
|
||||
size="sm"
|
||||
@click="qLocDrawer = !qLocDrawer"
|
||||
@@ -35,11 +35,16 @@
|
||||
</q-item>
|
||||
</q-list>
|
||||
</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-item v-close-popup v-ripple clickable @click="routeToQueue">
|
||||
<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>
|
||||
<q-item v-close-popup v-ripple clickable @click="clearRoute">
|
||||
@@ -53,35 +58,43 @@
|
||||
</q-footer>
|
||||
<q-drawer
|
||||
v-model="qLocDrawer"
|
||||
behavior="mobile"
|
||||
show-if-above
|
||||
:width="300"
|
||||
side="left"
|
||||
:breakpoint="500"
|
||||
@mouseenter="miniState = false"
|
||||
@mouseleave="miniState = true"
|
||||
class="leafletDrawer"
|
||||
>
|
||||
<q-scroll-area class="fit" :horizontal-thumb-style="{ opacity: '50' }">
|
||||
<div class="full-height column no-wrap">
|
||||
<q-list padding>
|
||||
<q-item-label header
|
||||
set service dns forwarding options cname=qbittorrent.famor.org,docker.famor.org
|
||||
><span class="bold">Location Queue: </span> {{ simulationState }}</q-item-label
|
||||
<q-item class="q-gutter-x-md">
|
||||
<q-item-section avatar>
|
||||
<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-scroll-area :horizontal-thumb-style="{ opacity: '50' }" class="col">
|
||||
<q-list padding>
|
||||
<VueDraggable
|
||||
ref="el"
|
||||
v-model="locationQueueOrderFiltered"
|
||||
handle=".drag-handle"
|
||||
filter=".undraggable"
|
||||
:isDisabled="disableDraggable"
|
||||
>
|
||||
<div
|
||||
<LocationItem
|
||||
v-for="(key, index) in locationQueueOrderFiltered"
|
||||
:class="isDraggable(key)"
|
||||
:key="key"
|
||||
@contextmenu.prevent="onDrawerContextMenu($event, key)"
|
||||
>
|
||||
<LocationItem
|
||||
:loc_id="key"
|
||||
:active="
|
||||
(locationQueueData as Record<string, any>)[key]?.loc_id ===
|
||||
@@ -101,8 +114,9 @@
|
||||
:end="(locationQueueData as Record<string, any>)[key]?.end ?? undefined"
|
||||
:status="(locationQueueData as Record<string, any>)[key]?.status ?? undefined"
|
||||
@item-clicked="zoomToCoords"
|
||||
@contextmenu.prevent="onDrawerContextMenu($event, key)"
|
||||
active-class="text-orange"
|
||||
/>
|
||||
</div>
|
||||
</VueDraggable>
|
||||
</q-list>
|
||||
</q-scroll-area>
|
||||
@@ -119,6 +133,7 @@
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-menu>
|
||||
</div>
|
||||
</q-drawer>
|
||||
|
||||
<q-page-container>
|
||||
@@ -151,8 +166,8 @@
|
||||
<q-item clickable v-ripple @click="handleAddLocation">
|
||||
<q-item-section avatar>
|
||||
<q-avatar
|
||||
icon="add_location"
|
||||
color="primary"
|
||||
icon="mdi-map-marker"
|
||||
color="accent"
|
||||
text-color="white"
|
||||
size="sm"
|
||||
/>
|
||||
@@ -161,25 +176,36 @@
|
||||
</q-item>
|
||||
<q-item clickable v-ripple @click="setStartRoute">
|
||||
<q-item-section avatar>
|
||||
<q-avatar
|
||||
icon="add_location"
|
||||
color="primary"
|
||||
text-color="white"
|
||||
size="sm"
|
||||
/>
|
||||
<q-avatar color="accent" size="sm">
|
||||
<q-icon class="small-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 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-avatar
|
||||
icon="add_location"
|
||||
color="primary"
|
||||
text-color="white"
|
||||
icon="mdi-star-outline"
|
||||
color="accent"
|
||||
text-color="yellow"
|
||||
size="sm"
|
||||
/>
|
||||
</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-list>
|
||||
</L-Popup>
|
||||
@@ -197,7 +223,7 @@
|
||||
>
|
||||
</L-Marker>
|
||||
</L-Layer-Group>
|
||||
<L-Layer-Group v-if="routeSet.start && routeSet.end">
|
||||
<L-Layer-Group v-if="routeSet.start || routeSet.end">
|
||||
<LRoutingMachine
|
||||
v-bind="routingOptions"
|
||||
@routingstart="debugRoutingEvent"
|
||||
@@ -205,12 +231,48 @@
|
||||
@routingerror="debugRoutingEvent"
|
||||
/>
|
||||
</L-Layer-Group>
|
||||
<L-Layer-Group v-if="findMyUpdate">
|
||||
<L-Layer-Group v-if="findMyUpdate && showFindMy">
|
||||
<L-Marker
|
||||
v-if="findMyUpdate"
|
||||
:icon="fmIcon as any"
|
||||
: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>
|
||||
{{ findMyTimePast }}
|
||||
</L-Tooltip>
|
||||
@@ -266,10 +328,12 @@ import * as LeafLet from 'leaflet';
|
||||
import LRoutingMachine from 'components/LRoutingMachine.vue';
|
||||
import LocationItem from 'components/LocationItem.vue';
|
||||
import SetLocationDialog from 'components/SetLocationDialog.vue';
|
||||
import EditFavoriteDialog from 'components/EditFavoriteDialog.vue';
|
||||
import { customRouter } from 'functions/serviceURL';
|
||||
import { useRoutingEvents } from '../composables/useRoutingEvents';
|
||||
import { useMarkerContextMenu } from '../composables/useMarkerContextMenu';
|
||||
import type { IRouter } from 'leaflet-routing-machine';
|
||||
import { reverseGeocodeRateLimited } from 'functions/reverseGeocodeSocket';
|
||||
|
||||
// Types
|
||||
import type { coords, SearchControlProps } from 'components/models';
|
||||
@@ -277,31 +341,30 @@ import type { LeafletMouseEvent, Map } from 'leaflet';
|
||||
|
||||
// Stores
|
||||
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 { useIcloudStore } from 'stores/icloud';
|
||||
import { favorites } from 'constants/favorites';
|
||||
|
||||
const socketStore = useSocketStore();
|
||||
|
||||
const leafletStore = useLeafletStore();
|
||||
const { zoom, center, markerLatLng, qLocDrawer, routeSet, routeDirections } =
|
||||
const { zoom, center, markerLatLng, qLocDrawer, routeSet, routeDirections, routeCoordinates } =
|
||||
storeToRefs(leafletStore);
|
||||
|
||||
const socketStore = useSocketioStore();
|
||||
const {
|
||||
currentLocation,
|
||||
nextLocation,
|
||||
locationQueueData,
|
||||
locationQueueOrder,
|
||||
findMyUpdate,
|
||||
simulationState,
|
||||
} = storeToRefs(socketStore);
|
||||
const simulationStore = useSimulationStore();
|
||||
const { currentLocation, locationQueueData, locationQueueOrder, simulationState, simulationRunning } =
|
||||
storeToRefs(simulationStore);
|
||||
|
||||
const icloudStore = useIcloudStore();
|
||||
const { findMyUpdate } = storeToRefs(icloudStore);
|
||||
|
||||
const $q = useQuasar();
|
||||
|
||||
const now = ref(Date.now());
|
||||
const mapRef = ref();
|
||||
const responseMessage = ref('');
|
||||
const miniState = ref(true);
|
||||
const safeCenter = computed<[number, number]>(() => {
|
||||
const lat = center.value?.[0];
|
||||
const lng = center.value?.[1];
|
||||
@@ -319,6 +382,12 @@ const safeMarkerLatLng = computed<[number, number] | 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 provider = new OpenStreetMapProvider();
|
||||
const searchOptions: SearchControlProps = {
|
||||
@@ -358,6 +427,10 @@ const startIcon = new Icon({
|
||||
svg: PinTriangle,
|
||||
});
|
||||
|
||||
const iconElement = (icon: Icon) => {
|
||||
return icon.createIcon();
|
||||
};
|
||||
|
||||
const endIcon = new Icon({
|
||||
color: '#a23337',
|
||||
accentColor: 'rgba(0,0,0,0.25)',
|
||||
@@ -390,20 +463,22 @@ const locationQueueOrderFiltered = computed({
|
||||
}
|
||||
},
|
||||
set: (val) => {
|
||||
socketStore.updateLocationQueueOrder(val);
|
||||
simulationStore.updateLocationQueueOrder(val);
|
||||
},
|
||||
});
|
||||
|
||||
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 myUpdatedIndex = myIndex - currentIndex;
|
||||
if ( myUpdatedIndex > 0 ) {
|
||||
if (myUpdatedIndex > 1) {
|
||||
return 'draggable';
|
||||
} else {
|
||||
return 'undraggable';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getCustomIcon = (locid: string) => {
|
||||
const currentIndex = currentLocation.value
|
||||
@@ -497,6 +572,7 @@ async function routeToQueue() {
|
||||
await addLocation(
|
||||
{ lat: Number(direction.coordinates.lat), lng: Number(direction.coordinates.lng) },
|
||||
direction.time,
|
||||
true,
|
||||
);
|
||||
}
|
||||
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() {
|
||||
console.log('routeToQueue');
|
||||
if (routeSet.value.start && routeSet.value.end && routeSegments) {
|
||||
@@ -548,15 +642,26 @@ const updateRoute = () => {
|
||||
routingOptions.waypoints = waypoints;
|
||||
};
|
||||
|
||||
const { clickedLatLng, handleMarkerClick, setStartRoute, setEndRoute } = useMarkerContextMenu(
|
||||
routeSet,
|
||||
updateRoute,
|
||||
closeAllPopups,
|
||||
const { isFavorite, handleMarkerClick, clickedLatLng, setStartRoute, setEndRoute } =
|
||||
useMarkerContextMenu(routeSet, 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 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) {
|
||||
console.log('onDrawerContextMenu: ', item);
|
||||
selectedItem.value = item;
|
||||
@@ -570,7 +675,7 @@ function handleDrawerContextMenu(command: string) {
|
||||
break;
|
||||
case 'delete':
|
||||
try {
|
||||
const ack = socketStore.simulationControl({
|
||||
const ack = simulationStore.simulationControl({
|
||||
command: 'delete',
|
||||
latitude: 0,
|
||||
longitude: 0,
|
||||
@@ -606,24 +711,66 @@ function handleDrawerContextMenu(command: string) {
|
||||
$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() {
|
||||
if (clickedLatLng.value) {
|
||||
const latlng = clickedLatLng.value;
|
||||
closeAllPopups();
|
||||
$q.notify(`add location...${latlng.toString()}`);
|
||||
// reverseGeocode(latlng.lat, latlng.lng)
|
||||
// .then((data) => {
|
||||
// const NomAddress = data.address as unknown as NominatimAddress;
|
||||
$q.dialog({
|
||||
component: SetLocationDialog,
|
||||
componentProps: {
|
||||
lat: Number(latlng.lat),
|
||||
lng: Number(latlng.lng),
|
||||
// address: NomAddress,
|
||||
},
|
||||
})
|
||||
.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(
|
||||
'Confirmed location add: latitude: ' +
|
||||
latlng.lat +
|
||||
@@ -639,10 +786,6 @@ function handleAddLocation() {
|
||||
.onDismiss(() => {
|
||||
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;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
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) => {
|
||||
let notType: string = 'positive';
|
||||
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',
|
||||
latitude: coords.lat,
|
||||
longitude: coords.lng,
|
||||
loc_id: '',
|
||||
delay: delay,
|
||||
address: address,
|
||||
address: addy,
|
||||
});
|
||||
if (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) {
|
||||
console.log('zoomToCoords: ', arg);
|
||||
const item = locationQueueData.value[arg];
|
||||
if (!item || item.latitude == null || item.longitude == null) {
|
||||
return;
|
||||
@@ -708,6 +894,15 @@ function zoomToCoords(arg: string) {
|
||||
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(() => {
|
||||
if (findMyUpdate.value) {
|
||||
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' });
|
||||
}
|
||||
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:
|
||||
$q.notify({ type: 'negative', message: 'Invalid location' });
|
||||
break;
|
||||
@@ -800,4 +988,15 @@ onUnmounted(() => {
|
||||
background-color: $dark
|
||||
.marker-popup .leaflet-popup-content
|
||||
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>
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useSocketioStore } from 'stores/socketio';
|
||||
import { useSimulationStore } from 'stores/simulation';
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
||||
import type { NominatimResponse } from 'components/models';
|
||||
import { Icon, PinCirclePanel, PinStarPanel } from 'leaflet-extra-markers';
|
||||
|
||||
import FormattedAddress from 'components/FormattedAddress.vue';
|
||||
//import NestedKnob from 'components/NestedKnob.vue';
|
||||
|
||||
const socketStore = useSocketioStore();
|
||||
const simulationStore = useSimulationStore();
|
||||
const { currentLocation, locationQueueOrder, locationQueueData, simulationRunning } =
|
||||
storeToRefs(socketStore);
|
||||
storeToRefs(simulationStore);
|
||||
|
||||
const props = defineProps({
|
||||
address: {
|
||||
type: Object as () => NominatimResponse,
|
||||
required: true,
|
||||
required: false,
|
||||
},
|
||||
latitude: {
|
||||
type: Number,
|
||||
@@ -26,7 +27,7 @@ const props = defineProps({
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: 'location_on',
|
||||
default: 'mdi-map-marker',
|
||||
},
|
||||
start: {
|
||||
type: String,
|
||||
@@ -136,19 +137,8 @@ const calculateDeltaTime = computed(() => {
|
||||
}
|
||||
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 secondsToTime = (seconds: number) => {
|
||||
const secondsToHhMmSs = (seconds: number) => {
|
||||
const hours = Math.floor(seconds / 3600);
|
||||
const minutes = Math.floor((seconds % 3600) / 60);
|
||||
const remainingSeconds = seconds % 60;
|
||||
@@ -156,7 +146,17 @@ const secondsToTime = (seconds: number) => {
|
||||
.toString()
|
||||
.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 a = timeIn.split(':');
|
||||
const seconds = +a[0] * 60 * 60 + +a[1] * 60 + +a[2];
|
||||
@@ -173,78 +173,6 @@ const humanReadableDateTime = (iso: string) => {
|
||||
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(() => {
|
||||
return currentLocation.value ? locationQueueOrder.value.indexOf(currentLocation.value.loc_id) : 0;
|
||||
@@ -258,12 +186,6 @@ const myUpdatedIndex = computed(() => {
|
||||
return myIndex.value - currentIndex.value;
|
||||
});
|
||||
|
||||
/*
|
||||
const markerIndex = computed(() => {
|
||||
return props.active ? '*' : myUpdatedIndex.value.toString();
|
||||
});
|
||||
*/
|
||||
|
||||
const itemClass = computed(() => {
|
||||
if (myUpdatedIndex.value > 0) return 'future';
|
||||
else if (myUpdatedIndex.value < 0) return 'past';
|
||||
@@ -313,21 +235,80 @@ onMounted(() => {
|
||||
timerId = requestAnimationFrame(update);
|
||||
});
|
||||
|
||||
const delayValue = computed({
|
||||
get: () => props.delay,
|
||||
set: (val) => socketStore.updateLocationMark(props.loc_id, 'delay', val),
|
||||
const delayConverted = computed({
|
||||
get: () => {
|
||||
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(() => {
|
||||
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) {
|
||||
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 {
|
||||
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(() => {
|
||||
return props.index > currentIndex.value;
|
||||
});
|
||||
@@ -336,58 +317,74 @@ const delayActive = computed(() => {
|
||||
return props.index === currentIndex.value + 1;
|
||||
});
|
||||
|
||||
const canDrag = computed(() => {
|
||||
return props.index > currentIndex.value + 1;
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
cancelAnimationFrame(timerId);
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<q-item v-if="displayDelay" :active="delayActive">
|
||||
<q-item-section avatar>
|
||||
<q-icon name="access_time" color="secondary" />
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-knob
|
||||
show-value
|
||||
v-model="delayValue"
|
||||
size="50px"
|
||||
color="secondary"
|
||||
track-color="dark-page"
|
||||
:step="getDelayAttrs.step"
|
||||
:max="getDelayAttrs.max"
|
||||
<div>
|
||||
<q-item
|
||||
v-if="displayDelay"
|
||||
:active="delayActive"
|
||||
:class="delayActive ? 'text-orange bg-dark' : ''"
|
||||
active-class="text-orange bg-dark"
|
||||
>
|
||||
{{ getDelayAttrs.delay }} {{ getDelayAttrs.units }}
|
||||
</q-knob>
|
||||
<!--
|
||||
<q-item-section class="flex flex-center">
|
||||
<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
|
||||
v-model="delayValue"
|
||||
v-model="delayConverted"
|
||||
:min="0"
|
||||
:step="getDelayAttrs.step"
|
||||
:max="getDelayAttrs.max"
|
||||
label
|
||||
switch-label-side
|
||||
:label-value="delayConverted + ' ' + getDelayAttrs.units"
|
||||
markers
|
||||
snap
|
||||
label-always
|
||||
:label-value="getDelayAttrs.delay + getDelayAttrs.units"
|
||||
:marker-labels="markerLabel"
|
||||
color="primary"
|
||||
style="max-width: 75%"
|
||||
/>
|
||||
-->
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-separator inset spaced v-if="displayDelay" />
|
||||
<q-item v-ripple clickable :active="active" @click="itemClicked" :class="itemClass">
|
||||
<q-item
|
||||
v-ripple
|
||||
clickable
|
||||
:active="active"
|
||||
@click="itemClicked"
|
||||
:class="itemClass"
|
||||
active-class="text-orange bg-dark"
|
||||
>
|
||||
<q-item-section style="width: 70%">
|
||||
<q-item-label>
|
||||
<q-item-label v-if="address">
|
||||
<FormattedAddress :address="props.address" />
|
||||
<q-tooltip> {{ latitude }}, {{ longitude }} </q-tooltip>
|
||||
</q-item-label>
|
||||
<q-item-label v-else> {{ latitude }}, {{ longitude }} </q-item-label>
|
||||
<q-item-label caption lines="1" v-if="start && simulationRunning">
|
||||
start: {{ humanReadableDateTime(start) }}
|
||||
</q-item-label>
|
||||
<q-item-label caption lines="1" v-if="end && simulationRunning">
|
||||
end: {{ humanReadableDateTime(end) }}
|
||||
</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
|
||||
side
|
||||
@@ -400,13 +397,14 @@ onUnmounted(() => {
|
||||
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">
|
||||
{{ calculateDeltaTime }}
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-separator spaced inset v-if="!isLast" />
|
||||
</div>
|
||||
</template>
|
||||
<style lang="sass" scoped>
|
||||
.past
|
||||
|
||||
@@ -1,36 +1,56 @@
|
||||
<script setup lang="ts">
|
||||
import { useLeafletStore } from 'stores/leaflet';
|
||||
import type { coords } from 'components/models';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { socket } from 'boot/socketio';
|
||||
import ConfirmCommandDialog from 'components/ConfirmCommandDiaglog.vue';
|
||||
import { useQuasar } from 'quasar';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useSocketioStore } from 'stores/socketio';
|
||||
import { ref } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import ConfirmCommandDialog from 'components/ConfirmCommandDiaglog.vue';
|
||||
|
||||
import { favorites } from 'constants/favorites';
|
||||
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 $q = useQuasar();
|
||||
|
||||
const deviceStore = useDeviceStore();
|
||||
const leafletStore = useLeafletStore();
|
||||
const socketStore = useSocketioStore();
|
||||
const simulationStore = useSimulationStore();
|
||||
const icloudStore = useIcloudStore();
|
||||
const tunneldStore = useTunneldStore();
|
||||
const { center, markerLatLng, zoom } = storeToRefs(leafletStore);
|
||||
|
||||
const {
|
||||
simulationRunning,
|
||||
simulationState,
|
||||
simulationQueueLength,
|
||||
icloudMonitor,
|
||||
locationQueueOrder,
|
||||
testMode,
|
||||
gpsNoise,
|
||||
} = storeToRefs(socketStore);
|
||||
currentLocation,
|
||||
} = storeToRefs(simulationStore);
|
||||
|
||||
const { icloudMonitor } = storeToRefs(icloudStore);
|
||||
|
||||
const menuOpen = ref(false);
|
||||
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 = {
|
||||
name: string;
|
||||
cmd: string;
|
||||
@@ -67,7 +87,7 @@ function handleFavClick(coords: coords) {
|
||||
}
|
||||
|
||||
function handleTestToggle() {
|
||||
const response = socketStore.simulationControl({
|
||||
const response = simulationStore.simulationControl({
|
||||
command: 'test-mode',
|
||||
latitude: null,
|
||||
longitude: null,
|
||||
@@ -81,7 +101,9 @@ function handleTestToggle() {
|
||||
}
|
||||
|
||||
function handleGpsNoiseToggle() {
|
||||
const response = socketStore.simulationControl({command: 'gps-noise', latitude: null,
|
||||
const response = simulationStore.simulationControl({
|
||||
command: 'gps-noise',
|
||||
latitude: null,
|
||||
longitude: null,
|
||||
loc_id: null,
|
||||
delay: 0,
|
||||
@@ -106,7 +128,14 @@ function handleControlClick(cmdAttr: ControlAction) {
|
||||
let notType: string = 'positive';
|
||||
let notMsg: string = '';
|
||||
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') {
|
||||
notType = 'negative';
|
||||
}
|
||||
@@ -126,15 +155,53 @@ function handleControlClick(cmdAttr: ControlAction) {
|
||||
$q.notify({ type: notType, message: notMsg });
|
||||
}
|
||||
}
|
||||
|
||||
if (cmdAttr.cmdClass === 'dev_cntrl_class') {
|
||||
socket.emit(
|
||||
'device_control',
|
||||
{ command: cmdAttr.cmd as DeviceCommands, delay: 0 },
|
||||
(response) => {
|
||||
console.log(response.command_status, response.command);
|
||||
},
|
||||
);
|
||||
let notType: string = 'positive';
|
||||
let notMsg: string = '';
|
||||
try {
|
||||
const ack = deviceStore.deviceControl(cmdAttr.cmd as DeviceCommands, cmdAttr.delay);
|
||||
if (ack.sts === 'error') {
|
||||
notType = 'negative';
|
||||
}
|
||||
if (ack.msg) {
|
||||
notMsg = ack.msg;
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
notType = 'negative';
|
||||
if (error instanceof Error) {
|
||||
console.error('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(() => {
|
||||
@@ -148,7 +215,7 @@ function handleControlClick(cmdAttr: ControlAction) {
|
||||
let notType: string = 'positive';
|
||||
let notMsg: string = '';
|
||||
try {
|
||||
const ack = socketStore.simulationControl({
|
||||
const ack = simulationStore.simulationControl({
|
||||
command: cmdAttr.cmd,
|
||||
latitude: null,
|
||||
longitude: null,
|
||||
@@ -179,7 +246,7 @@ function handleControlClick(cmdAttr: ControlAction) {
|
||||
let notType: string = 'positive';
|
||||
let notMsg: string = '';
|
||||
try {
|
||||
const ack = socketStore.icloudMonitorControl(cmdAttr.cmd);
|
||||
const ack = icloudStore.icloudMonitorControl(cmdAttr.cmd);
|
||||
if (ack.sts === 'error') {
|
||||
notType = 'negative';
|
||||
}
|
||||
@@ -201,13 +268,52 @@ function handleControlClick(cmdAttr: ControlAction) {
|
||||
}
|
||||
|
||||
if (cmdAttr.cmdClass === 'dev_cntrl_class') {
|
||||
socket.emit(
|
||||
'device_control',
|
||||
{ command: cmdAttr.cmd as DeviceCommands, delay: 0 },
|
||||
(response) => {
|
||||
console.log(response.command_status, response.command);
|
||||
},
|
||||
);
|
||||
let notType: string = 'positive';
|
||||
let notMsg: string = '';
|
||||
try {
|
||||
const ack = deviceStore.deviceControl(cmdAttr.cmd as DeviceCommands, cmdAttr.delay);
|
||||
if (ack.sts === 'error') {
|
||||
notType = 'negative';
|
||||
}
|
||||
if (ack.msg) {
|
||||
notMsg = ack.msg;
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
notType = 'negative';
|
||||
if (error instanceof Error) {
|
||||
console.error('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"
|
||||
/>
|
||||
-->
|
||||
<q-avatar>
|
||||
<q-img src="~assets/simloc_logo2.png" class="logo" />
|
||||
</q-avatar>
|
||||
<q-separator dark inset />
|
||||
<q-space />
|
||||
<q-btn
|
||||
:icon-right="menuOpen ? 'arrow_drop_up' : 'arrow_drop_down'"
|
||||
:icon-right="menuOpen ? 'mdi-menu-up' : 'mdi-menu-down'"
|
||||
stretch
|
||||
flat
|
||||
label="Favorites"
|
||||
v-if="route.name === 'Leaflet'"
|
||||
class="text-weight-bold"
|
||||
>
|
||||
<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">
|
||||
<q-item
|
||||
dense
|
||||
dark
|
||||
v-if="hasCoords(favObj)"
|
||||
clickable
|
||||
@@ -255,7 +364,7 @@ function handleControlClick(cmdAttr: ControlAction) {
|
||||
<q-item-label>{{ favObj.name }}</q-item-label>
|
||||
</q-item-section>
|
||||
</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-avatar :icon="favObj.icon" color="secondary" size="sm" text-color="black" />
|
||||
</q-item-section>
|
||||
@@ -263,12 +372,11 @@ function handleControlClick(cmdAttr: ControlAction) {
|
||||
<q-item-label>{{ favObj.name }}</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section side>
|
||||
<q-icon name="keyboard_arrow_right" />
|
||||
<q-icon name="mdi-chevron-right" />
|
||||
</q-item-section>
|
||||
<q-menu anchor="bottom start" self="bottom end">
|
||||
<q-list dense dark>
|
||||
<q-list dark>
|
||||
<q-item
|
||||
dense
|
||||
dark
|
||||
v-for="(favSubObj, favSubId) in favObj.subitems"
|
||||
:key="favSubId"
|
||||
@@ -296,11 +404,18 @@ function handleControlClick(cmdAttr: ControlAction) {
|
||||
</q-list>
|
||||
</q-menu>
|
||||
</q-btn>
|
||||
<q-btn-dropdown stretch flat label="Controls">
|
||||
<q-list dense dark>
|
||||
<q-item-label header>Simulation Controls</q-item-label>
|
||||
<q-item dense dark tag="label" v-ripple>
|
||||
<q-item-section avatar>
|
||||
<q-btn-dropdown class="text-weight-bold" stretch flat label="Controls">
|
||||
<q-list dark>
|
||||
<q-expansion-item
|
||||
group="controls"
|
||||
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
|
||||
v-model="testMode"
|
||||
size="sm"
|
||||
@@ -308,18 +423,19 @@ function handleControlClick(cmdAttr: ControlAction) {
|
||||
@update:model-value="handleTestToggle"
|
||||
dark
|
||||
dense
|
||||
:disabled="simulationRunning"
|
||||
/>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label>Test Mode</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item dense dark tag="label" v-ripple>
|
||||
<q-item-section avatar>
|
||||
<q-item dark tag="label" v-ripple>
|
||||
<q-item-section avatar class="q-pl-lg">
|
||||
<q-toggle
|
||||
v-model="gpsNoise"
|
||||
size="sm"
|
||||
color="brown"
|
||||
color="accent"
|
||||
@update:model-value="handleGpsNoiseToggle"
|
||||
dark
|
||||
dense
|
||||
@@ -330,7 +446,6 @@ function handleControlClick(cmdAttr: ControlAction) {
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item
|
||||
dense
|
||||
dark
|
||||
v-if="!simulationRunning"
|
||||
clickable
|
||||
@@ -338,7 +453,7 @@ function handleControlClick(cmdAttr: ControlAction) {
|
||||
v-close-popup
|
||||
@click="handleControlClick(controls.simulation.start)"
|
||||
>
|
||||
<q-item-section avatar>
|
||||
<q-item-section avatar class="q-pl-lg">
|
||||
<q-avatar
|
||||
:icon="controls.simulation.start.icon"
|
||||
color="secondary"
|
||||
@@ -351,7 +466,6 @@ function handleControlClick(cmdAttr: ControlAction) {
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item
|
||||
dense
|
||||
dark
|
||||
v-if="simulationState === 'RUNNING' && simulationRunning"
|
||||
clickable
|
||||
@@ -359,7 +473,7 @@ function handleControlClick(cmdAttr: ControlAction) {
|
||||
v-close-popup
|
||||
@click="handleControlClick(controls.simulation.pause)"
|
||||
>
|
||||
<q-item-section avatar>
|
||||
<q-item-section avatar class="q-pl-lg">
|
||||
<q-avatar
|
||||
:icon="controls.simulation.pause.icon"
|
||||
color="secondary"
|
||||
@@ -372,7 +486,6 @@ function handleControlClick(cmdAttr: ControlAction) {
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item
|
||||
dense
|
||||
dark
|
||||
v-if="simulationState === 'PAUSED'"
|
||||
clickable
|
||||
@@ -380,7 +493,7 @@ function handleControlClick(cmdAttr: ControlAction) {
|
||||
v-close-popup
|
||||
@click="handleControlClick(controls.simulation.resume)"
|
||||
>
|
||||
<q-item-section avatar>
|
||||
<q-item-section avatar class="q-pl-lg">
|
||||
<q-avatar
|
||||
:icon="controls.simulation.resume.icon"
|
||||
color="secondary"
|
||||
@@ -393,15 +506,14 @@ function handleControlClick(cmdAttr: ControlAction) {
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item
|
||||
dense
|
||||
dark
|
||||
v-if="simulationQueueLength && simulationQueueLength > 0"
|
||||
v-if="simulationQueueLength && simulationQueueLength > 0 && !isLast"
|
||||
clickable
|
||||
v-ripple
|
||||
v-close-popup
|
||||
@click="handleControlClick(controls.simulation.clear)"
|
||||
>
|
||||
<q-item-section avatar>
|
||||
<q-item-section avatar class="q-pl-lg">
|
||||
<q-avatar
|
||||
:icon="controls.simulation.clear.icon"
|
||||
color="secondary"
|
||||
@@ -414,7 +526,26 @@ function handleControlClick(cmdAttr: ControlAction) {
|
||||
</q-item-section>
|
||||
</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
|
||||
v-if="simulationRunning"
|
||||
clickable
|
||||
@@ -422,7 +553,7 @@ function handleControlClick(cmdAttr: ControlAction) {
|
||||
v-close-popup
|
||||
@click="handleControlClick(controls.simulation.end)"
|
||||
>
|
||||
<q-item-section avatar>
|
||||
<q-item-section avatar class="q-pl-lg">
|
||||
<q-avatar
|
||||
:icon="controls.simulation.end.icon"
|
||||
color="secondary"
|
||||
@@ -434,10 +565,15 @@ function handleControlClick(cmdAttr: ControlAction) {
|
||||
<q-item-label> {{ controls.simulation.end.name }} </q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-separator spaced />
|
||||
<q-item-label header>iCloud Monitor Controls</q-item-label>
|
||||
</q-expansion-item>
|
||||
<q-expansion-item
|
||||
group="controls"
|
||||
icon="mdi-cloud"
|
||||
dense-toggle
|
||||
label="iCloud Monitor Controls"
|
||||
color="accent"
|
||||
>
|
||||
<q-item
|
||||
dense
|
||||
dark
|
||||
v-if="!icloudMonitor"
|
||||
clickable
|
||||
@@ -445,7 +581,7 @@ function handleControlClick(cmdAttr: ControlAction) {
|
||||
v-close-popup
|
||||
@click="handleControlClick(controls.icloudmonitor.start)"
|
||||
>
|
||||
<q-item-section avatar>
|
||||
<q-item-section avatar class="q-pl-lg">
|
||||
<q-avatar
|
||||
:icon="controls.icloudmonitor.start.icon"
|
||||
color="secondary"
|
||||
@@ -458,7 +594,6 @@ function handleControlClick(cmdAttr: ControlAction) {
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item
|
||||
dense
|
||||
dark
|
||||
v-if="icloudMonitor"
|
||||
clickable
|
||||
@@ -467,7 +602,7 @@ function handleControlClick(cmdAttr: ControlAction) {
|
||||
@click="handleControlClick(controls.icloudmonitor.stop)"
|
||||
size="sm"
|
||||
>
|
||||
<q-item-section avatar>
|
||||
<q-item-section avatar class="q-pl-lg">
|
||||
<q-avatar
|
||||
:icon="controls.icloudmonitor.stop.icon"
|
||||
color="secondary"
|
||||
@@ -479,17 +614,163 @@ function handleControlClick(cmdAttr: ControlAction) {
|
||||
<q-item-label> {{ controls.icloudmonitor.stop.name }} </q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-separator spaced />
|
||||
<q-item-label header>Device Controls</q-item-label>
|
||||
</q-expansion-item>
|
||||
<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
|
||||
dense
|
||||
dark
|
||||
clickable
|
||||
v-ripple
|
||||
v-close-popup
|
||||
@click="handleControlClick(controls.device.reboot)"
|
||||
>
|
||||
<q-item-section avatar>
|
||||
<q-item-section avatar class="q-pl-lg">
|
||||
<q-avatar
|
||||
:icon="controls.device.reboot.icon"
|
||||
color="secondary"
|
||||
@@ -502,14 +783,13 @@ function handleControlClick(cmdAttr: ControlAction) {
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item
|
||||
dense
|
||||
dark
|
||||
clickable
|
||||
v-ripple
|
||||
v-close-popup
|
||||
@click="handleControlClick(controls.device.shutdown)"
|
||||
>
|
||||
<q-item-section avatar>
|
||||
<q-item-section avatar class="q-pl-lg">
|
||||
<q-avatar
|
||||
:icon="controls.device.shutdown.icon"
|
||||
color="secondary"
|
||||
@@ -521,9 +801,21 @@ function handleControlClick(cmdAttr: ControlAction) {
|
||||
<q-item-label> {{ controls.device.shutdown.name }} </q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-expansion-item>
|
||||
</q-list>
|
||||
</q-btn-dropdown>
|
||||
</q-toolbar>
|
||||
</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-card class="bg-dark text-grey-1 add-loc-card q-pa-sm">
|
||||
<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>
|
||||
<q-card-section class="q-ml-lg">
|
||||
<div class="q-mb-sm">Are you sure you want to set location to:</div>
|
||||
<div class="q-ml-lg">
|
||||
<formatted-address :address="address" />
|
||||
<div>Are you sure you want to set location to:</div>
|
||||
<div class="q-ml-md">
|
||||
<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
|
||||
class="q-mt-sm"
|
||||
class="q-mt-md"
|
||||
style="max-width: 150px"
|
||||
v-model.number="delay"
|
||||
filled
|
||||
@@ -19,13 +31,30 @@
|
||||
type="number"
|
||||
suffix="seconds"
|
||||
color="grey-4"
|
||||
autofocus
|
||||
/>
|
||||
</div>
|
||||
</q-card-section>
|
||||
<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="Cancel" @click="onDialogCancel" />
|
||||
</div>
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
@@ -34,37 +63,81 @@
|
||||
<script setup lang="ts">
|
||||
import { useDialogPluginComponent } from 'quasar';
|
||||
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 EditFavoriteDialog from 'components/EditFavoriteDialog.vue';
|
||||
import type { NominatimResponse } from 'components/models';
|
||||
|
||||
const props = defineProps({
|
||||
lat: { type: Number, required: true },
|
||||
lng: { type: Number, required: true },
|
||||
});
|
||||
|
||||
const $q = useQuasar();
|
||||
const loading = ref(true);
|
||||
const delay = ref(0);
|
||||
const delay = ref(300);
|
||||
|
||||
defineEmits([...useDialogPluginComponent.emits]);
|
||||
|
||||
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 () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
const response = await reverseGeocodeRateLimited(props.lat, props.lng);
|
||||
console.log('reverse geocode response: ', response);
|
||||
address.value = response;
|
||||
address.value = response.address;
|
||||
if (response.favorite) {
|
||||
favorite.value = response.favorite;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching reverse geocode:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
await sleep(500);
|
||||
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() {
|
||||
onDialogOK({ delay: delay.value, address: address.value });
|
||||
}
|
||||
|
||||
@@ -1,33 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import { socket } from 'boot/socketio';
|
||||
import { useQuasar } from 'quasar';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { useSocketioStore } from 'stores/socketio';
|
||||
import { computed, ref } from 'vue';
|
||||
import { useSocketStore } from 'stores/socket';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import type { ClientToServerEvents } from 'components/models';
|
||||
|
||||
const socketioStore = useSocketioStore();
|
||||
const $q = useQuasar();
|
||||
const msgInput = ref('');
|
||||
const socketStore = useSocketStore();
|
||||
|
||||
const sockEvent = ref('');
|
||||
const eventArgs = ref('');
|
||||
|
||||
const { sockConnected, messageList } = storeToRefs(socketioStore);
|
||||
const { sockConnected } = storeToRefs(socketStore);
|
||||
|
||||
const sockStatColor = computed(() => {
|
||||
return sockConnected.value ? 'green' : 'red';
|
||||
});
|
||||
|
||||
|
||||
function sendMessage() {
|
||||
const msgToSend = msgInput.value;
|
||||
console.log('Sending message... ' + msgToSend);
|
||||
socket.emit('message', msgToSend, (e, r) => {
|
||||
console.log('Message: ' + msgToSend + ' delivered: ' + e + ', response: ' + r);
|
||||
});
|
||||
msgInput.value = '';
|
||||
}
|
||||
|
||||
function handleEmit() {
|
||||
const event = sockEvent.value;
|
||||
const jsonArgs = eventArgs.value;
|
||||
@@ -37,33 +25,14 @@ function handleEmit() {
|
||||
sockEvent.value = '';
|
||||
eventArgs.value = '';
|
||||
}
|
||||
|
||||
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 },
|
||||
);
|
||||
const timeWithSeconds = ref('00:00:45');
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<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 label="Connect" @click="socketioStore.connect()" />
|
||||
<q-btn label="Disconnect" @click="socketioStore.disconnect()" />
|
||||
<q-input v-model="msgInput" filled label="Message" />
|
||||
<div>
|
||||
<q-btn label="Send" @click="sendMessage" />
|
||||
</div>
|
||||
<q-list bordered seperator v-if="socketioStore.messageList.length > 0">
|
||||
<q-item v-for="(msg, index) in socketioStore.messageList" :key="index">
|
||||
<q-item-section>{{ msg }}</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
<q-btn label="Connect" @click="socketStore.connect()" />
|
||||
<q-btn label="Disconnect" @click="socketStore.disconnect()" />
|
||||
</div>
|
||||
<div class="flex flex-center col q-ma-lg q-gutter-md">
|
||||
<div class="text-h3">SocketIO Functions</div>
|
||||
@@ -73,5 +42,7 @@ watch(
|
||||
<q-btn label="Emit" @click="handleEmit" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<q-time v-model="timeWithSeconds" with-seconds format24h default-view="seconds" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,10 +1,23 @@
|
||||
<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';
|
||||
|
||||
const socketioStore = useSocketioStore();
|
||||
const { sockConnected, deviceConnected, tunnelConnected, simulationState, icloudMonitor, testMode } =
|
||||
storeToRefs(socketioStore);
|
||||
const socketStore = useSocketStore();
|
||||
const simulationStore = useSimulationStore();
|
||||
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 {
|
||||
if (state === null || state === undefined) {
|
||||
return 'grey';
|
||||
@@ -28,58 +41,47 @@ function statusDevColor(state: string | boolean | null | undefined): string {
|
||||
|
||||
<template>
|
||||
<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 />
|
||||
<!--
|
||||
<div style="width: 80vw" class="flex justify-end">
|
||||
<q-btn
|
||||
rounded
|
||||
push
|
||||
size="sm"
|
||||
icon="settings"
|
||||
class="q-mr-sm"
|
||||
@click="socketioStore.toggleSock()"
|
||||
>
|
||||
<q-badge :color="statusDevColor(sockConnected)" rounded floating class="q-mr-sm" />
|
||||
-->
|
||||
<div class="q-gutter-x-sm">
|
||||
<q-btn dense rounded push size="sm" icon="mdi-cog" @click="socketStore.toggleSock()">
|
||||
<q-badge :color="statusDevColor(sockConnected)" rounded floating />
|
||||
</q-btn>
|
||||
<q-btn
|
||||
dense
|
||||
rounded
|
||||
push
|
||||
size="sm"
|
||||
icon="phone_iphone"
|
||||
class="q-mr-sm"
|
||||
@click="socketioStore.requestUpdate()"
|
||||
icon="mdi-cellphone"
|
||||
@click="socketStore.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
|
||||
dense
|
||||
rounded
|
||||
push
|
||||
size="sm"
|
||||
icon="subway"
|
||||
class="q-mr-sm"
|
||||
@click="socketioStore.requestUpdate()"
|
||||
icon="mdi-map-marker"
|
||||
@click="socketStore.requestUpdate()"
|
||||
>
|
||||
<q-badge :color="statusDevColor(tunnelConnected)" rounded floating class="q-mr-sm" />
|
||||
<q-badge :color="statusDevColor(simulationState)" rounded floating />
|
||||
</q-btn>
|
||||
<q-btn
|
||||
dense
|
||||
rounded
|
||||
push
|
||||
size="sm"
|
||||
icon="location_on"
|
||||
class="q-mr-sm"
|
||||
@click="socketioStore.requestUpdate()"
|
||||
icon="mdi-cloud"
|
||||
@click="icloudStore.icloudMonitorControl('refresh')"
|
||||
>
|
||||
<q-badge :color="statusDevColor(simulationState)" rounded floating class="q-mr-sm" />
|
||||
</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-badge :color="statusDevColor(icloudMonitor)" rounded floating />
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -26,11 +26,14 @@ export interface DevCtrlAttr {
|
||||
}
|
||||
*/
|
||||
|
||||
export type FavoriteCommands = 'set' | 'delete' | 'get';
|
||||
|
||||
export type SimulationCommands =
|
||||
'restart'
|
||||
| 'start'
|
||||
| 'pause'
|
||||
| 'resume'
|
||||
| 'reset'
|
||||
| 'clear'
|
||||
| 'end'
|
||||
| 'add'
|
||||
@@ -41,7 +44,7 @@ export type SimulationCommands =
|
||||
|
||||
export type DeviceCommands = 'shutdown' | 'reboot';
|
||||
|
||||
export type TunnelCommands =
|
||||
export type TunneldCommands =
|
||||
| 'start'
|
||||
| 'start-watcher'
|
||||
| 'end-watcher'
|
||||
@@ -128,6 +131,14 @@ export interface SimulationStatus {
|
||||
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 {
|
||||
active: boolean;
|
||||
data: LocationQueue;
|
||||
@@ -149,13 +160,7 @@ export interface StatusUpdate {
|
||||
};
|
||||
device_name: string | undefined | null;
|
||||
fmf_location: FindMyUpdate | undefined | null;
|
||||
icloud: {
|
||||
consumer_queue: number | undefined | null;
|
||||
consumer_task: string | undefined | null;
|
||||
monitor_enabled: boolean;
|
||||
monitor_task: string | undefined | null;
|
||||
monitor_running: boolean;
|
||||
};
|
||||
icloud: icloudData;
|
||||
next_move?: number | undefined | null;
|
||||
set_location_enabled: boolean;
|
||||
simulation_queue: QueueData
|
||||
@@ -224,6 +229,20 @@ export interface ClientToServerEvents {
|
||||
// status
|
||||
request_update: (callback: (response: StatusUpdate) => void) => void;
|
||||
// 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: (
|
||||
args: {
|
||||
command: SimulationCommands;
|
||||
@@ -231,7 +250,7 @@ export interface ClientToServerEvents {
|
||||
longitude?: number | null | undefined;
|
||||
delay?: number | null | undefined;
|
||||
loc_id?: string | null | undefined;
|
||||
address?: string | null | undefined;
|
||||
address?: NominatimResponse | string | null | undefined;
|
||||
},
|
||||
callback: (response: SimulationControlResponse) => void,
|
||||
) => void;
|
||||
@@ -242,13 +261,6 @@ export interface ClientToServerEvents {
|
||||
},
|
||||
callback?: (response: DeviceControlResponse) => void,
|
||||
) => void;
|
||||
tunnel_control: (
|
||||
args: {
|
||||
command: TunnelCommands;
|
||||
delay?: number;
|
||||
},
|
||||
callback?: (response: DeviceControlResponse) => void,
|
||||
) => void;
|
||||
icloud_monitor_control: (
|
||||
args: {
|
||||
command: string;
|
||||
@@ -276,17 +288,26 @@ export interface ClientToServerEvents {
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
},
|
||||
callback?: (response: NominatimResponse) => void,
|
||||
callback?: (response: GeoCacheResponse) => void,
|
||||
) => void;
|
||||
}
|
||||
|
||||
export interface FavoriteControlResponse {
|
||||
command_status: string;
|
||||
command: FavoriteCommands;
|
||||
command_class: string;
|
||||
data?: {
|
||||
favorites: Favorite[] | undefined | null;
|
||||
};
|
||||
message?: string | undefined;
|
||||
}
|
||||
|
||||
export interface SimulationControlResponse {
|
||||
command_status: string;
|
||||
command: SimulationCommands;
|
||||
command_class: string;
|
||||
data?: SimulationControlResponseData | undefined | null;
|
||||
message?: string | undefined;
|
||||
|
||||
}
|
||||
|
||||
interface SimulationControlResponseData {
|
||||
@@ -301,6 +322,14 @@ interface DeviceControlResponse {
|
||||
message?: string;
|
||||
}
|
||||
|
||||
|
||||
interface TunneldControlResponse {
|
||||
command_status: string;
|
||||
command_class: string;
|
||||
command: TunneldCommands;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export interface iCloudMonitorResponse {
|
||||
command_status: string;
|
||||
command: string;
|
||||
@@ -401,20 +430,42 @@ export interface LatLng {
|
||||
}
|
||||
|
||||
export interface routeDirections {
|
||||
dirIndex: number;
|
||||
dirIndex?: number | undefined;
|
||||
coordinateIndex: number;
|
||||
text: string;
|
||||
distance: number;
|
||||
time: number;
|
||||
text?: string | undefined;
|
||||
distance?: number | undefined;
|
||||
time?: number | undefined;
|
||||
coordinates: LatLng | null | undefined;
|
||||
}
|
||||
|
||||
export interface routeCoordinates {
|
||||
coordinateIndex: number;
|
||||
lat: number;
|
||||
lng: number;
|
||||
distanceFromPrev: number;
|
||||
timeFromPrev: number;
|
||||
instruction: string;
|
||||
}
|
||||
|
||||
export interface RouteSet {
|
||||
start: [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;
|
||||
}
|
||||
|
||||
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
|
||||
export interface NominatimResponse {
|
||||
shop?: string | null | undefined;
|
||||
@@ -424,8 +475,10 @@ export interface NominatimResponse {
|
||||
neighbourhood?: string | null | undefined;
|
||||
suburb?: string | null | undefined;
|
||||
county: string;
|
||||
town? : string | null | undefined;
|
||||
city?: string | null | undefined;
|
||||
village?: string | null | undefined;
|
||||
municipality?: string | null | undefined;
|
||||
state: string;
|
||||
'ISO3166-2-lvl4': string;
|
||||
postcode: string;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ref, type Ref } from 'vue';
|
||||
import * as LeafLet from 'leaflet';
|
||||
import type { LeafletMouseEvent } from 'leaflet';
|
||||
import { reverseGeocodeRateLimited } from 'functions/reverseGeocodeSocket';
|
||||
|
||||
type RouteSet = {
|
||||
start?: LeafLet.LatLng | null | undefined;
|
||||
@@ -19,11 +20,14 @@ export function useMarkerContextMenu(
|
||||
closeAllPopups: () => void,
|
||||
) {
|
||||
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);
|
||||
closeAllPopups();
|
||||
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);
|
||||
};
|
||||
|
||||
@@ -44,6 +48,7 @@ export function useMarkerContextMenu(
|
||||
};
|
||||
|
||||
return {
|
||||
isFavorite,
|
||||
clickedLatLng,
|
||||
handleMarkerClick,
|
||||
setStartRoute,
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { useLeafletStore } from 'stores/leaflet';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import type { routeCoordinates as StoreRouteCoordinates } from 'components/models';
|
||||
|
||||
const leafletStore = useLeafletStore();
|
||||
const { routeSegments, routeDirections } = storeToRefs(leafletStore);
|
||||
const { routeSegments, routeDirections, routeCoordinates } = storeToRefs(leafletStore);
|
||||
|
||||
type RouteSummary = {
|
||||
totalDistance: number;
|
||||
@@ -37,6 +38,23 @@ type RouteResult = {
|
||||
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() {
|
||||
const handleRoutesFound = (event: { routes?: RouteResult[] }) => {
|
||||
const route = event.routes?.[0];
|
||||
@@ -81,7 +99,73 @@ export function useRoutingEvents() {
|
||||
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 {
|
||||
handleRoutesFound,
|
||||
|
||||
@@ -4,7 +4,7 @@ export const controls = {
|
||||
name: 'Start Location Sim',
|
||||
cmd: 'start',
|
||||
cmdClass: 'sim_cntrl_class',
|
||||
icon: 'play_arrow',
|
||||
icon: 'mdi-play',
|
||||
cnfrm: false,
|
||||
delay: 0,
|
||||
},
|
||||
@@ -12,7 +12,7 @@ export const controls = {
|
||||
name: 'Pause Location Sim',
|
||||
cmd: 'pause',
|
||||
cmdClass: 'sim_cntrl_class',
|
||||
icon: 'pause',
|
||||
icon: 'mdi-pause',
|
||||
cnfrm: false,
|
||||
delay: 0,
|
||||
},
|
||||
@@ -20,23 +20,31 @@ export const controls = {
|
||||
name: 'Resume Location Simulation',
|
||||
cmd: 'resume',
|
||||
cmdClass: 'sim_cntrl_class',
|
||||
icon: 'play_arrow',
|
||||
icon: 'mdi-play-pause',
|
||||
cnfrm: false,
|
||||
delay: 0,
|
||||
},
|
||||
clear: {
|
||||
name: 'Clear Location Queue',
|
||||
name: 'Clear Future Items',
|
||||
cmd: 'clear',
|
||||
cmdClass: 'sim_cntrl_class',
|
||||
icon: 'directions_off',
|
||||
icon: 'msi-map-marker-remove',
|
||||
cnfrm: false,
|
||||
delay: 0,
|
||||
},
|
||||
reset: {
|
||||
name: 'Reset Location Queue',
|
||||
cmd: 'reset',
|
||||
cmdClass: 'sim_cntrl_class',
|
||||
icon: 'mdi-restart',
|
||||
cnfrm: true,
|
||||
delay: 0,
|
||||
},
|
||||
end: {
|
||||
name: 'End Location Sim',
|
||||
cmd: 'end',
|
||||
cmdClass: 'sim_cntrl_class',
|
||||
icon: 'stop',
|
||||
icon: 'mdi-stop',
|
||||
cnfrm: true,
|
||||
delay: 0,
|
||||
},
|
||||
@@ -46,7 +54,7 @@ export const controls = {
|
||||
name: 'Shutdown',
|
||||
cmd: 'shutdown',
|
||||
cmdClass: 'dev_cntrl_class',
|
||||
icon: 'power_settings_new',
|
||||
icon: 'mdi-power',
|
||||
cnfrm: true,
|
||||
delay: 5,
|
||||
},
|
||||
@@ -54,7 +62,7 @@ export const controls = {
|
||||
name: 'Reboot',
|
||||
cmd: 'reboot',
|
||||
cmdClass: 'dev_cntrl_class',
|
||||
icon: 'restart_alt',
|
||||
icon: 'mdi-restart',
|
||||
cnfrm: true,
|
||||
delay: 5,
|
||||
},
|
||||
@@ -64,7 +72,7 @@ export const controls = {
|
||||
name: 'Start iCloud Monitor',
|
||||
cmd: 'start',
|
||||
cmdClass: 'icloud-monitor_cntrl_class',
|
||||
icon: 'play_arrow',
|
||||
icon: 'mdi-play',
|
||||
cnfrm: false,
|
||||
delay: 0,
|
||||
},
|
||||
@@ -72,7 +80,65 @@ export const controls = {
|
||||
name: 'Stop iCloud Monitor',
|
||||
cmd: 'stop',
|
||||
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,
|
||||
delay: 0,
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export const favorites = {
|
||||
home: {
|
||||
name: 'Home',
|
||||
icon: 'home',
|
||||
icon: 'mdi-home',
|
||||
coords: {
|
||||
lat: 40.910773020811,
|
||||
lng: -73.891069806448,
|
||||
@@ -9,11 +9,11 @@ export const favorites = {
|
||||
},
|
||||
work_places: {
|
||||
name: 'Work Places',
|
||||
icon: 'work',
|
||||
icon: 'mdi-briefcase',
|
||||
subitems: {
|
||||
jeong: {
|
||||
name: 'Jeong',
|
||||
icon: 'dermatology',
|
||||
icon: 'mdi-doctor',
|
||||
coords: {
|
||||
lat: 40.76624975651346,
|
||||
lng: -73.81444335286128,
|
||||
@@ -22,7 +22,7 @@ export const favorites = {
|
||||
},
|
||||
santos: {
|
||||
name: 'Santos',
|
||||
icon: 'syringe',
|
||||
icon: 'mdi-doctor',
|
||||
coords: {
|
||||
lat: 40.74504671877868,
|
||||
lng: -73.8880099638491,
|
||||
@@ -31,7 +31,7 @@ export const favorites = {
|
||||
},
|
||||
natalya_qns: {
|
||||
name: 'Natalya (Qns)',
|
||||
icon: 'healing',
|
||||
icon: 'mdi-doctor',
|
||||
coords: {
|
||||
lat: 40.69644966409178,
|
||||
lng: -73.837453217826,
|
||||
@@ -40,7 +40,7 @@ export const favorites = {
|
||||
},
|
||||
natalya_bx: {
|
||||
name: 'Natalya (Bronx)',
|
||||
icon: 'healing',
|
||||
icon: 'mdi-doctor',
|
||||
coords: {
|
||||
lat: 40.85384419116598,
|
||||
lng: -73.86314767911834,
|
||||
@@ -49,7 +49,7 @@ export const favorites = {
|
||||
},
|
||||
office: {
|
||||
name: 'Linwood Plaza',
|
||||
icon: 'allergy',
|
||||
icon: 'mdi-allergy',
|
||||
coords: {
|
||||
lat: 40.86141832913106,
|
||||
lng: -73.96997583196286,
|
||||
@@ -60,7 +60,7 @@ export const favorites = {
|
||||
},
|
||||
strg: {
|
||||
name: 'Man Mini Storage',
|
||||
icon: 'box',
|
||||
icon: 'mdi-dolly',
|
||||
coords: {
|
||||
lat: 40.75158955085288,
|
||||
lng: -73.9328988710467,
|
||||
@@ -69,7 +69,7 @@ export const favorites = {
|
||||
},
|
||||
acme: {
|
||||
name: 'Acme',
|
||||
icon: 'grocery',
|
||||
icon: 'mdi-cart',
|
||||
coords: {
|
||||
lat: 40.90930366920829,
|
||||
lng: -73.87658695470259,
|
||||
|
||||
@@ -1,15 +1,24 @@
|
||||
// services/nominatimService.ts
|
||||
import { useSocketioStore } from 'stores/socketio';
|
||||
import type { NominatimResponse, NominatimRequest } from 'components/models';
|
||||
import { socket } from 'boot/socketio';
|
||||
import type { NominatimRequest, GeoCacheResponse } from 'components/models';
|
||||
|
||||
|
||||
const socketStore = useSocketioStore();
|
||||
|
||||
let lastRequestTime = 0;
|
||||
|
||||
export const reverseGeocodeRateLimited = async (
|
||||
lat: number,
|
||||
lon: number,
|
||||
): Promise<NominatimResponse> => {
|
||||
function revGeoCode(nomRequest: NominatimRequest): Promise<GeoCacheResponse> {
|
||||
return new Promise((resolve) => {
|
||||
socket.emit(
|
||||
'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 timeSinceLast = now - lastRequestTime;
|
||||
|
||||
@@ -17,7 +26,10 @@ export const reverseGeocodeRateLimited = async (
|
||||
if (timeSinceLast < 1000) {
|
||||
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();
|
||||
return response;
|
||||
|
||||
@@ -45,77 +45,26 @@
|
||||
<script setup lang="ts">
|
||||
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 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 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(() => {
|
||||
socketStore.bindEvents();
|
||||
simulationStore.bindEvents();
|
||||
icloudStore.bindEvents();
|
||||
socketStore.connect();
|
||||
favoriteStore.initialize();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -7,25 +7,26 @@
|
||||
<q-btn color="purple" @click="showNotif" label="Show Notification" />
|
||||
</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" />
|
||||
</div>
|
||||
</q-page>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useQuasar } from 'quasar'
|
||||
import { computed } from 'vue'
|
||||
import SocketTest from 'components/SocketTest.vue'
|
||||
const $q = useQuasar()
|
||||
import { useQuasar } from 'quasar';
|
||||
import { computed } from 'vue';
|
||||
import SocketTest from 'components/SocketTest.vue';
|
||||
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() {
|
||||
$q.dark.toggle()
|
||||
$q.dark.toggle();
|
||||
}
|
||||
|
||||
|
||||
|
||||
function showNotif() {
|
||||
$q.notify({
|
||||
message: 'This is a notification',
|
||||
@@ -33,9 +34,16 @@ function showNotif() {
|
||||
position: 'top-right',
|
||||
timeout: 3000,
|
||||
actions: [
|
||||
{ label: 'Dismiss', color: 'white', handler: () => { /* Dismiss action */ } }
|
||||
]
|
||||
})
|
||||
{
|
||||
label: 'Dismiss',
|
||||
color: 'white',
|
||||
handler: () => {
|
||||
/* Dismiss action */
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
</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 { 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 {
|
||||
zoom: number;
|
||||
@@ -13,8 +19,9 @@ interface State {
|
||||
wayPoints?: LatLng[] | null | undefined;
|
||||
};
|
||||
routesSet: RoutesSet[] | null;
|
||||
routeSegments?: routeSegments[] | null;
|
||||
routeDirections?: routeDirections[] | null;
|
||||
routeSegments: routeSegments[] | null;
|
||||
routeDirections: routeDirections[] | null;
|
||||
routeCoordinates: routeCoordinates[] | null;
|
||||
}
|
||||
|
||||
export const useLeafletStore = defineStore('leaflet', {
|
||||
@@ -32,6 +39,7 @@ export const useLeafletStore = defineStore('leaflet', {
|
||||
routesSet: [],
|
||||
routeSegments: [],
|
||||
routeDirections: [],
|
||||
routeCoordinates: [],
|
||||
};
|
||||
},
|
||||
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 { 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', {
|
||||
state: () => {
|
||||
return {
|
||||
sockConnected: false as boolean,
|
||||
socketID: null as string | null | undefined,
|
||||
testMode: null as boolean | undefined | null,
|
||||
gpsNoise: null as boolean | undefined | null,
|
||||
deviceConnected: false as boolean,
|
||||
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[],
|
||||
|
||||
|
||||
|
||||
// nextLocation: null as NextLocation | null,
|
||||
// messageList: [''] as string[],
|
||||
|
||||
leafletZoom: 10 as number,
|
||||
icloudMonitor: false as boolean,
|
||||
findMyUpdate: null as FindMyUpdate | null | undefined,
|
||||
|
||||
};
|
||||
},
|
||||
getters: {
|
||||
sockState: (state) => state.sockConnected,
|
||||
/* sockState: (state) => state.sockConnected,
|
||||
deviceState: (state) => state.deviceConnected,
|
||||
lMarkerLatLng: (state): [number, number] => {
|
||||
if (
|
||||
@@ -64,687 +34,27 @@ export const useSocketioStore = defineStore('socketio', {
|
||||
return this.lMarkerLatLng;
|
||||
},
|
||||
lZoom: (state): number => state.leafletZoom,
|
||||
*/
|
||||
|
||||
},
|
||||
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));
|
||||
}
|
||||