layout changes, status bar addition, drawer, and leaflet store
This commit is contained in:
@@ -112,7 +112,7 @@ export default defineConfig((/* ctx */) => {
|
|||||||
config: {
|
config: {
|
||||||
dark: true,
|
dark: true,
|
||||||
},
|
},
|
||||||
// iconSet: 'material-icons', // Quasar icon set
|
iconSet: 'material-icons', // Quasar icon set
|
||||||
// lang: 'en-US', // Quasar language pack
|
// lang: 'en-US', // Quasar language pack
|
||||||
|
|
||||||
// For special cases outside of where the auto-import strategy can have an impact
|
// For special cases outside of where the auto-import strategy can have an impact
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ interface ClientToServerEvents {
|
|||||||
request_update: (callback: (response: { statusUpdate: StatusUpdate }) => void) => void;
|
request_update: (callback: (response: { statusUpdate: StatusUpdate }) => void) => void;
|
||||||
command: (
|
command: (
|
||||||
command: string,
|
command: string,
|
||||||
callback: (response: { success: boolean; message?: string }) => void,
|
callback: (response: { status: boolean; message?: string }) => void,
|
||||||
) => void;
|
) => void;
|
||||||
shutdown: (
|
shutdown: (
|
||||||
delay: number,
|
delay: number,
|
||||||
|
|||||||
32
src/components/ConfirmCommandDiaglog.vue
Normal file
32
src/components/ConfirmCommandDiaglog.vue
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<template>
|
||||||
|
<q-dialog ref="dlgRef" persistent>
|
||||||
|
<q-card>
|
||||||
|
<q-card-section class="row items-center">
|
||||||
|
<q-avatar icon="add_location" color="primary" text-color="white" />
|
||||||
|
<span class="q-ml-sm">
|
||||||
|
Are you sure you want to {{ name }} ?
|
||||||
|
</span>
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-actions align="right">
|
||||||
|
<q-btn flat label="OK" color="primary" @click="onOkClick" />
|
||||||
|
<q-btn flat label="Cancel" color="primary" @click="onDialogCancel" />
|
||||||
|
</q-card-actions>
|
||||||
|
</q-card>
|
||||||
|
</q-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useDialogPluginComponent } from 'quasar';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
name: { type: String, required: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
defineEmits([...useDialogPluginComponent.emits]);
|
||||||
|
|
||||||
|
const { dialogRef: dlgRef, onDialogOK, onDialogCancel } = useDialogPluginComponent();
|
||||||
|
|
||||||
|
function onOkClick() {
|
||||||
|
onDialogOK();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -2,6 +2,13 @@
|
|||||||
import { useLeafletStore } from 'stores/leaflet';
|
import { useLeafletStore } from 'stores/leaflet';
|
||||||
import type { coords } from 'src/types';
|
import type { coords } from 'src/types';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
|
import { socket } from 'boot/socket';
|
||||||
|
import ConfirmCommandDialog from 'components/ConfirmCommandDiaglog.vue';
|
||||||
|
import { useQuasar } from 'quasar';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const $q = useQuasar();
|
||||||
|
|
||||||
const leafletStore = useLeafletStore();
|
const leafletStore = useLeafletStore();
|
||||||
const { center, markerLatLng } = storeToRefs(leafletStore);
|
const { center, markerLatLng } = storeToRefs(leafletStore);
|
||||||
@@ -15,6 +22,38 @@ const home = {
|
|||||||
icon: 'home',
|
icon: 'home',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface Control {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
cmd: string;
|
||||||
|
icon: string;
|
||||||
|
confirm: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const controls: Control[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'Start Location Sim',
|
||||||
|
cmd: 'start_location_simulation',
|
||||||
|
icon: 'play_arrow',
|
||||||
|
confirm: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: 'End Location Sim',
|
||||||
|
cmd: 'end_location_simulation',
|
||||||
|
icon: 'stop',
|
||||||
|
confirm: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: 'Shutdown',
|
||||||
|
cmd: 'shutdown',
|
||||||
|
icon: 'power_settings_new',
|
||||||
|
confirm: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const favorites = [
|
const favorites = [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -49,17 +88,47 @@ function handleFavClick(coords: coords) {
|
|||||||
center.value = [coords.lat, coords.lng];
|
center.value = [coords.lat, coords.lng];
|
||||||
markerLatLng.value = [coords.lat, coords.lng];
|
markerLatLng.value = [coords.lat, coords.lng];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleControlClick(ctrl: Control) {
|
||||||
|
if (ctrl.confirm) {
|
||||||
|
$q.dialog({
|
||||||
|
component: ConfirmCommandDialog,
|
||||||
|
componentProps: {
|
||||||
|
name: ctrl.name,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.onOk(() => {
|
||||||
|
socket.emit('command', ctrl.cmd, (response) => {
|
||||||
|
console.log(response.status);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.onCancel(() => {
|
||||||
|
console.log('Dialog cancelled');
|
||||||
|
})
|
||||||
|
.onDismiss(() => {
|
||||||
|
console.log('Dialog dismissed');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
socket.emit('command', ctrl.cmd, (response) => {
|
||||||
|
console.log(response.status);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<q-toolbar class="bg-primary text-white">
|
<q-toolbar class="bg-primary text-white">
|
||||||
<q-btn flat round dense icon="menu" class="q-mr-sm" />
|
<q-btn @click="$emit('drawer')" flat round dense icon="menu" class="q-mr-sm" />
|
||||||
<q-separator dark vertical inset />
|
<q-separator dark vertical inset />
|
||||||
<q-btn stretch flat :icon="home.icon" @click="handleFavClick(home.coords)" />
|
|
||||||
|
|
||||||
<q-space />
|
<q-space />
|
||||||
|
<q-btn
|
||||||
<q-btn-dropdown stretch flat label="Favorites">
|
stretch
|
||||||
|
flat
|
||||||
|
:icon="home.icon"
|
||||||
|
@click="handleFavClick(home.coords)"
|
||||||
|
v-if="route.name === 'Leaflet'"
|
||||||
|
/>
|
||||||
|
<q-btn-dropdown stretch flat label="Favorites" v-if="route.name === 'Leaflet'">
|
||||||
<q-list>
|
<q-list>
|
||||||
<q-item
|
<q-item
|
||||||
v-for="fav in favorites"
|
v-for="fav in favorites"
|
||||||
@@ -78,6 +147,25 @@ function handleFavClick(coords: coords) {
|
|||||||
</q-item>
|
</q-item>
|
||||||
</q-list>
|
</q-list>
|
||||||
</q-btn-dropdown>
|
</q-btn-dropdown>
|
||||||
|
<q-btn-dropdown stretch flat label="Controls">
|
||||||
|
<q-list>
|
||||||
|
<q-item
|
||||||
|
v-for="ctrl in controls"
|
||||||
|
:key="ctrl.id"
|
||||||
|
clickable
|
||||||
|
v-ripple
|
||||||
|
v-close-popup
|
||||||
|
@click="handleControlClick(ctrl)"
|
||||||
|
>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-avatar :icon="ctrl.icon" color="primary" text-color="white" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>{{ ctrl.name }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</q-btn-dropdown>
|
||||||
</q-toolbar>
|
</q-toolbar>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { computed } from 'vue';
|
|||||||
|
|
||||||
const statusStore = useStatusStore();
|
const statusStore = useStatusStore();
|
||||||
|
|
||||||
const socketStatus = computed(() => statusStore.socketConnected ? 'green' : 'red');
|
const socketStatus = computed(() => statusStore.statusList.socketConnected ? 'green' : 'red');
|
||||||
|
|
||||||
socket.off();
|
socket.off();
|
||||||
statusStore.socketConnect();
|
statusStore.socketConnect();
|
||||||
|
|||||||
@@ -1,38 +1,35 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useQuasar } from 'quasar';
|
import { useStatusStore } from 'stores/status';
|
||||||
|
|
||||||
import { ref, onMounted } from 'vue';
|
const statusStore = useStatusStore();
|
||||||
import { api } from 'boot/axios';
|
const statusList = statusStore.statusList;
|
||||||
|
|
||||||
const $q = useQuasar();
|
function statusDevColor(state: boolean): string {
|
||||||
|
return state ? 'green' : 'red';
|
||||||
const statusDev = ref({ color: 'red', deviceName: 'No connected Device' });
|
|
||||||
|
|
||||||
async function getStatus() {
|
|
||||||
try {
|
|
||||||
const { data } = await api({
|
|
||||||
method: 'get',
|
|
||||||
url: '/status',
|
|
||||||
});
|
|
||||||
console.log('API Call: usbmux/status returned:', data);
|
|
||||||
} catch (error: unknown) {
|
|
||||||
if (error instanceof Error) {
|
|
||||||
console.error('Error setting location:', error.message);
|
|
||||||
$q.notify({ type: 'negative', message: error.message });
|
|
||||||
} else {
|
|
||||||
console.error('Error setting location:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
await getStatus();
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<q-toolbar class="bg-primary text-white">
|
<q-toolbar class="bg-primary text-white">
|
||||||
<q-badge :color="statusDev.color" rounded class="q-mr-sm" />{{ statusDev.deviceName }}
|
<div class="flex col q-gutter-md align-center justify-start content-center">
|
||||||
|
<span>Status:</span>
|
||||||
|
<span>
|
||||||
|
<q-badge :color="statusDevColor(statusList.socketConnected)" rounded class="q-mr-sm" />
|
||||||
|
WebSocket
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<q-badge :color="statusDevColor(statusList.deviceConnected)" rounded class="q-mr-sm" />
|
||||||
|
Device Connection
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<q-badge :color="statusDevColor(statusList.tunnelConnected)" rounded class="q-mr-sm" />
|
||||||
|
tunneld
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<q-badge :color="statusDevColor(statusList.simulationRunning)" rounded class="q-mr-sm" />
|
||||||
|
Location Simulation
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</q-toolbar>
|
</q-toolbar>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -12,12 +12,12 @@
|
|||||||
// to match your app's branding.
|
// to match your app's branding.
|
||||||
// Tip: Use the "Theme Builder" on Quasar's documentation website.
|
// Tip: Use the "Theme Builder" on Quasar's documentation website.
|
||||||
|
|
||||||
$primary: #1976d2;
|
$primary: #02006c;
|
||||||
$secondary: #26a69a;
|
$secondary: #010057;
|
||||||
$accent: #9c27b0;
|
$accent: #9c27b0;
|
||||||
|
|
||||||
$dark: #1d1d1d;
|
$dark: #1d1d1d;
|
||||||
$dark-page: #121212;
|
$dark-page: #03002e;
|
||||||
|
|
||||||
$positive: #21ba45;
|
$positive: #21ba45;
|
||||||
$negative: #c10015;
|
$negative: #c10015;
|
||||||
|
|||||||
@@ -1,12 +1,34 @@
|
|||||||
<template>
|
<template>
|
||||||
<q-layout view="lHh lpr lFf">
|
<q-layout view="hHh Lpr fff">
|
||||||
<q-header elevated>
|
<q-header>
|
||||||
<MenuBar />
|
<MenuBar @drawer="drawer = !drawer" />
|
||||||
</q-header>
|
</q-header>
|
||||||
<q-footer elevated>
|
<q-footer>
|
||||||
<StatusBar />
|
<StatusBar />
|
||||||
</q-footer>
|
</q-footer>
|
||||||
|
<q-drawer
|
||||||
|
v-model="drawer"
|
||||||
|
:width="200"
|
||||||
|
:breakpoint="500"
|
||||||
|
overlay
|
||||||
|
:class="$q.dark.isActive ? 'bg-grey-8' : 'bg-grey-3'"
|
||||||
|
>
|
||||||
|
<q-scroll-area class="fit">
|
||||||
|
<q-list>
|
||||||
|
<template v-for="(menuItem, index) in menuList" :key="index">
|
||||||
|
<q-item clickable :active="menuItem.route === route.name" v-ripple @click="$router.push({ name: menuItem.route })">
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon :name="menuItem.icon" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
{{ menuItem.label }}
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-separator :key="'sep' + index" v-if="menuItem.separator" />
|
||||||
|
</template>
|
||||||
|
</q-list>
|
||||||
|
</q-scroll-area>
|
||||||
|
</q-drawer>
|
||||||
<q-page-container>
|
<q-page-container>
|
||||||
<router-view />
|
<router-view />
|
||||||
</q-page-container>
|
</q-page-container>
|
||||||
@@ -14,6 +36,69 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import MenuBar from 'components/MenuBar.vue'
|
import MenuBar from 'components/MenuBar.vue';
|
||||||
import StatusBar from 'components/StatusBar.vue'
|
import StatusBar from 'components/StatusBar.vue';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
const drawer = ref(false);
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
const menuList = [
|
||||||
|
{
|
||||||
|
icon: 'map',
|
||||||
|
label: 'Map',
|
||||||
|
separator: false,
|
||||||
|
route: 'Leaflet',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'report_problem',
|
||||||
|
label: 'Test',
|
||||||
|
separator: false,
|
||||||
|
route: 'Test',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
];
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -5,8 +5,23 @@ const routes: RouteRecordRaw[] = [
|
|||||||
path: '/',
|
path: '/',
|
||||||
component: () => import('layouts/MainLayout.vue'),
|
component: () => import('layouts/MainLayout.vue'),
|
||||||
children: [
|
children: [
|
||||||
{ path: '', component: () => import('pages/IndexPage.vue') },
|
{
|
||||||
{ path: 'test', component: () => import('pages/TestPage.vue') },
|
path: '',
|
||||||
|
name: 'home',
|
||||||
|
redirect: {
|
||||||
|
name: 'Leaflet',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'leaflet',
|
||||||
|
name: 'Leaflet',
|
||||||
|
component: () => import('pages/IndexPage.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'test',
|
||||||
|
name: 'Test',
|
||||||
|
component: () => import('pages/TestPage.vue')
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -14,6 +29,7 @@ const routes: RouteRecordRaw[] = [
|
|||||||
// but you can also remove it
|
// but you can also remove it
|
||||||
{
|
{
|
||||||
path: '/:catchAll(.*)*',
|
path: '/:catchAll(.*)*',
|
||||||
|
name: 'ErrorNotFound',
|
||||||
component: () => import('pages/ErrorNotFound.vue'),
|
component: () => import('pages/ErrorNotFound.vue'),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -4,15 +4,20 @@ import { socket } from 'boot/socket';
|
|||||||
export const useStatusStore = defineStore('status', {
|
export const useStatusStore = defineStore('status', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
device: {},
|
device: {},
|
||||||
socketConnected: false,
|
statusList: {
|
||||||
|
socketConnected: false,
|
||||||
|
deviceConnected: false,
|
||||||
|
tunnelConnected: false,
|
||||||
|
simulationRunning: false,
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
bindEvents() {
|
bindEvents() {
|
||||||
socket.on('connect', () => {
|
socket.on('connect', () => {
|
||||||
this.socketConnected = true;
|
this.statusList.socketConnected = true;
|
||||||
});
|
});
|
||||||
socket.on('disconnect', () => {
|
socket.on('disconnect', () => {
|
||||||
this.socketConnected = false;
|
this.statusList.socketConnected = false;
|
||||||
});
|
});
|
||||||
socket.on('status_update', (data) => {
|
socket.on('status_update', (data) => {
|
||||||
this.$patch((state) => {
|
this.$patch((state) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user