<template>
<div id="q-app" style="min-height: 100vh;" class="nomy nopx nomx" @keydown.f9="translate">
<q-layout view="hHh Lpr fFf" container style="height: 100vh" class="nomy nopx nomx">
<q-header style="height: 40px;">
<q-toolbar class="nopx" style="min-height:40px;">
<q-btn flat @click="$store.drawer = !$store.drawer" dense icon="menu" />
<q-toolbar-title class="text-subtitle1 nomy">AI4SoilHealth {{ $store.version
}}
</q-toolbar-title>
<q-btn flat dense class="nomy" v-if="!$store.isOnline" icon="wifi_off" />
{{ $store.userData && $store.userData.first_name > '' && $store.userData.last_name > '' ?
(this.$q.screen.width
>= 1024 ? `${$store.userData.first_name} ${$store.userData.last_name}` : $store.userData.first_name.charAt(0)
+
$store.userData.last_name.charAt(0)) :
$t('Guest') }}
<q-btn v-if="$keycloak.token" class="nomy" flat @click="$logout" dense icon="logout" />
<q-btn v-else class="nomy" flat dense icon="login" @click="$keycloak.login()">
</q-btn>
<accessibility />
<lang-switcher ref="langSwitcher" />
<q-btn class="nomy" flat @click="toggleFullscreen" dense
:icon="(fullscreen ? 'fullscreen_exit' : 'fullscreen')" />
</q-toolbar>
</q-header>
<q-drawer style="top: 40px" v-model="$store.drawer" :width="$store.drawerWidth" bordered behavior="desktop"
:breakpoint="breakpoint" :overlay="false">
<q-scroll-area class="fit">
<div v-if="isAdmin" class="row">
<q-input v-model="treeFilter" dense style="width:160px">
<template v-slot:prepend>
<q-icon name="search"></q-icon>
</template>
</q-input>
<q-btn flat dense icon="refresh" @click="getRoutes" />
</div>
<q-tree ref="tree" class="primary text-body2" :nodes="tree" node-key="path" no-connectors :filter="treeFilter"
:default-expand-all="treeFilter.length > 0" v-model:selected="selected" v-if="tree.length > 0"
@update:selected="selectionUpdated" />
</q-scroll-area>
</q-drawer>
<q-scroll-area style="height: 100vh; max-width: 100vw;" :bar-style="{ width: '10px' }"
:thumb-style="{ width: '0px' }">
<q-page-container class="q-pt-none">
<q-page>
<router-view />
</q-page>
</q-page-container>
</q-scroll-area>
</q-layout>
<popup v-if="$store.popup.show" @keydown.f9="translate" />
<chart-popup v-if="$store.chart.show" />
<help-dialog @keydown.f9="translate" />
<task-progress v-if="$store.progress.show" />
</div>
</template>
<script lang="js">
import { setCssVar } from 'quasar';
import ChartPopup from './components/chart-popup.vue';
import LangSwitcher from "./components/lang-switcher.vue";
import Accessibility from './components/accessibility.vue';
import Popup from './components/popup.vue';
import HelpDialog from './components/help-dialog.vue';
import PWAPrompt from './components/PWAPrompt.vue';
import TaskProgress from './components/task-progress.vue';
//import { shallowRef, markRaw } from "vue";
/**
* The main component of the application.
* Renders the layout and handles user interactions.
*
* @component
* @example
* <App />
*/
export default {
name: "App",
components: {
LangSwitcher,
Accessibility,
Popup,
PWAPrompt,
Popup,
HelpDialog,
ChartPopup,
TaskProgress,
},
data: () => ({
selected: null,
fullscreen: false,
canInstall: false,
breakpoint: 1024,
treeFilter: '',
}),
/**
* The created lifecycle hook.
* Checks if the server is online and calls the init method.
*/
async created() {
// window.addEventListener('online', () => {
// this.$store.isOnline = true;
// initKeycloak(this.$store.isOnline);
// });
// window.addEventListener('offline', () => {
// this.$store.isOnline = false;
// initKeycloak(this.$store.isOnline);
// });
// alert(navigator.onLine);
console.log(`App: navigator.onLine: ${navigator.onLine}`);
// console.log(`App: $store.isOnline: ${this.$store.isOnline}`);
// this.$store.isOnline = navigator.onLine;
if (this.$store.isOnline) {
this.axios.API.get("Home/Ping", null)
.then(response => {
this.init();
});
} else {
this.init();
}
},
async mounted() {
setCssVar("tooltip-fontsize", "12px");
this.$store.drawer = this.$q.screen.width >= this.breakpoint;
this.$store.userData = this.$q.localStorage.getItem("userData");
if ('serviceWorker' in navigator) {
navigator.serviceWorker.addEventListener('controllerchange', () => {
this.$q.dialog({
title: this.$t('Update'),
message: this.$t('New version available. Reload now?'),
cancel: true,
persistent: true
}).onOk(() => {
window.location.reload();
});
});
}
},
watch: {
treeFilter(val) {
if (val.length > 0) {
this.$refs.tree.expandAll();
}
}
},
computed: {
/**
* Filters the tree based on the provided filter text.
* @returns {Array} The filtered tree items.
*/
treeFiltered() {
// Filter the tree based on the treeFilter, recusively filtering the children
return this.tree.filter((item) => {
if (item.label.toLowerCase().includes(this.treeFilter.toLowerCase())) return true;
if (item.children) {
item.children = item.children.filter((child) => {
if (child.label.toLowerCase().includes(this.treeFilter.toLowerCase())) return true;
return false;
});
return item.children.length > 0;
}
return false;
});
},
/**
* Retrieves the tree data.
*/
tree() {
let root = this.$store.routes.filter((item) => !item.parent && item.active);
let children = root.map(route => ({
label: this.$t(route.title),
name: route.name,
path: route.path,
icon: route.icon,
iconColor: route.iconColor ?? "primary",
offline: route.offline,
public: route.public,
children: this.getChildRoutes(route.name),
}));
children = children.filter(c => (this.$store.isOnline || c.offline));
return children;
},
},
methods: {
/**
* Initializes the application.
*
* @returns {Promise<void>} A promise that resolves when the initialization is complete.
*/
async init() {
this.$store.localeOptions = await this.get("Home/GetLocaleOptions", null, true);
this.$store.catalogs = await this.get("Home/GetCatalogs", null, true);
this.$store.news = await this.get(`Home/GetNews/${this.$store.news.length}/10`, null, true);
if (this.$keycloak.authenticated) {
let ret = await this.get('Auth/GetUser');
if (ret) {
if (ret.agreement) {
if (await this.confirmDialog(ret.agreement, this.$t('You have to accept the terms and conditions to continue:'),
this.$t('Accept'), this.$t('Decline'))) {
await this.post('Auth/AcceptAgreement');
this.$store.userData = ret;
this.$q.localStorage.set('userData', this.$store.userData);
} else {
this.$logout();
}
} else {
this.$store.userData = ret;
this.$q.localStorage.set('userData', this.$store.userData);
}
} else {
this.$logout();
//this.$store.userData = null;
//this.$q.localStorage.remove('userData');
}
}
this.$refs.langSwitcher.localeChanged();
},
/**
* Updates the selection with the specified ID.
*
* @param {number} id - The ID of the selection to update.
* @returns {Promise<void>} - A promise that resolves when the selection is updated.
*/
async selectionUpdated(id) {
if (id == null) return;
if (this.$store.formChanged) {
if (!await this.confirmDialog(this.$t('Unsaved changes will be lost. Continue?'))) {
this.$store.formChanged = false;
return;
}
this.$store.formChanged = false;
}
let route = this.$store.routes.find((item) => item.path == id);
if (route.component_name > "") {
this.activateRoute(route);
this.$store.drawer = this.$q.screen.width >= this.breakpoint;
} else {
this.$refs.tree.setExpanded(id, !this.$refs.tree.isExpanded(id));
}
this.selected = null;
},
/**
* Retrieves the child routes for a given parent route.
*
* @param {string} parentName - The name of the parent route.
* @returns {Array} - An array of child routes.
*/
getChildRoutes(parentName) {
// Filter and return the child routes for the given parentName
let children = this.$store.routes.filter((route) => route.parent === parentName && route.active);
if (children.length === 0) return [];
return children
.map(route => ({
label: route.title,
name: route.name,
path: route.path,
icon: route.icon,
iconColor: route.iconColor ?? "primary",
offline: route.offline,
children: this.getChildRoutes(route.path.substring(1)),
}));
},
// checkMobileAndEnterFullscreen() {
// if (this.$q.platform.is.mobile) {
// document.documentElement.requestFullscreen();
// }
// },
toggleFullscreen() {
if (this.fullscreen) {
document.exitFullscreen();
} else {
document.documentElement.requestFullscreen();
}
this.fullscreen = !this.fullscreen;
},
},
}
</script>