abstract dbm and light composable and components and upgrade light user gui for fine graining with + and -

This commit is contained in:
Adrian Zürcher
2025-05-28 21:56:05 +02:00
parent d7565ed09c
commit 03f23d6d5a
24 changed files with 1324 additions and 841 deletions

View File

View File

@@ -0,0 +1,246 @@
<template>
<q-card>
<div class="row">
<q-card-section class="col-4 scroll tree-container">
<q-tree
class="text-blue text-bold"
dense
:nodes="dbmData"
node-key="key"
no-transition
:default-expand-all="false"
v-model:expanded="expanded"
@lazy-load="onLazyLoad"
>
<template v-slot:[`default-header`]="props">
<div
class="row items-center text-blue"
@contextmenu.prevent.stop="openContextMenu($event, props.node)"
>
<div class="row items-center text-blue"></div>
<div>{{ props.node.path }}</div>
</div>
<q-popup-edit
v-if="props.node.value !== undefined && props.node.value !== ''"
v-model="props.node.value"
class="q-ml-xl bg-grey text-white"
@save="(val) => onValueEdit(val, props.node)"
>
<template v-slot="scope">
<q-input
dark
color="white"
v-model="scope.value"
dense
autofocus
counter
@keyup.enter="scope.set"
>
<template v-slot:append>
<q-icon name="edit" />
</template>
</q-input>
</template>
</q-popup-edit>
</template>
</q-tree>
<sub-menu :node="selectedNode"></sub-menu>
</q-card-section>
<DataTable :rows="Subscriptions" class="col-8" />
</div>
</q-card>
</template>
<script setup lang="ts">
import { watch, onMounted, ref } from 'vue';
import DataTable from './dataTable.vue';
import type { TreeNode } from 'src/composables/dbm/dbmTree';
import {
dbmData,
buildTree,
getSubscriptionsByUuid,
addChildrentoTree,
removeSubtreeByParentKey,
getAllSubscriptions,
} from 'src/composables/dbm/dbmTree';
import { useQuasar } from 'quasar';
import { openContextMenu } from 'src/composables/dbm/useContextMenu';
import { NotifyResponse } from 'src/composables/notify';
import SubMenu from 'src/components/dbm/SubMenu.vue';
import { QCard } from 'quasar';
import { subscribe, unsubscribe, setValues } from 'src/services/websocket';
import { onBeforeRouteLeave } from 'vue-router';
import { api } from 'boot/axios';
import type { Subs } from 'src/models/Subscribe';
const $q = useQuasar();
const expanded = ref<string[]>([]);
const selectedNode = ref<TreeNode | null>(null);
const ZERO_UUID = '00000000-0000-0000-0000-000000000000';
const Subscriptions = ref<Subs>([]);
onMounted(() => {
const payload = {
get: [
{
path: '.*',
query: { depth: 1 },
},
],
};
api
.post('/json_data', payload)
.then((res) => {
if (res.data.get) {
dbmData.value = buildTree(res.data.get);
}
})
.catch((err) => {
NotifyResponse($q, err, 'error');
});
});
onBeforeRouteLeave(() => {
unsubscribe([
{
path: '.*',
depth: 0,
},
]).catch((err) => {
NotifyResponse($q, err, 'error');
});
});
function onLazyLoad({
node,
done,
fail,
}: {
node: TreeNode;
done: (children: TreeNode[]) => void;
fail: () => void;
}): void {
//first unsubsrice nodes
unsubscribe([
{
path: '.*',
depth: 0,
},
])
.then(() => {
Subscriptions.value = [];
})
.catch((err) => {
NotifyResponse($q, err, 'error');
});
// now subscribe nodes
subscribe([
{
uuid: node.key ?? ZERO_UUID,
path: '',
depth: 2,
},
])
.then((resp) => {
if (resp?.subscribe) {
// Optional: update your internal store too
addChildrentoTree(resp?.subscribe);
const toRemove = new Set(
resp.subscribe.filter((sub) => sub.uuid !== ZERO_UUID).map((sub) => sub.uuid),
);
Subscriptions.value = getAllSubscriptions().filter((sub) => toRemove.has(sub.uuid));
done(dbmData.value);
} else {
done([]); // no children returned
}
})
.catch((err) => {
NotifyResponse($q, err, 'error');
fail(); // trigger the fail handler
});
}
function onValueEdit(newValue: undefined, node: TreeNode) {
console.log(node.value, node.value === undefined);
const sub = getSubscriptionsByUuid(node.key);
if (sub) {
setValues([
{
path: sub.path ?? '',
value: newValue,
},
]).catch((err) => {
NotifyResponse($q, err, 'error');
});
}
}
watch(
expanded,
(newVal, oldVal) => {
const collapsedKeys = oldVal.filter((key) => !newVal.includes(key));
collapsedKeys.forEach((key: string) => {
// WebSocket unsubscribe
unsubscribe([
{
uuid: key,
path: '.*',
depth: 0,
},
])
.then((resp) => {
// Remove children of this node from the tree
removeSubtreeByParentKey(key);
if (resp?.unsubscribe) {
const toRemove = new Set(
resp.unsubscribe.filter((sub) => sub.uuid !== ZERO_UUID).map((sub) => sub.uuid),
);
Subscriptions.value = Subscriptions.value.filter((sub) => !toRemove.has(sub.uuid));
}
})
.catch((err) => {
NotifyResponse($q, err, 'error');
});
});
},
{ deep: false },
);
</script>
<style scoped>
.tree-container {
overflow-y: auto;
}
@media (max-width: 599px) {
.tree-container {
max-height: 50vh;
}
}
@media (min-width: 600px) and (max-width: 1023px) {
.tree-container {
max-height: 60vh;
}
}
@media (min-width: 1024px) and (max-width: 1439px) {
.tree-container {
max-height: 70vh;
}
}
@media (min-width: 1440px) and (max-width: 1919px) {
.tree-container {
max-height: 80vh;
}
}
@media (min-width: 1920px) {
.tree-container {
max-height: 90vh;
}
}
</style>

View File

@@ -0,0 +1,53 @@
<template>
<q-menu ref="contextMenuRef" context-menu>
<q-list>
<q-item clickable v-close-popup @click="handleAction('Add')">
<q-item-section>Add Datapoint</q-item-section>
</q-item>
<q-item clickable v-close-popup @click="handleAction('Delete')">
<q-item-section>Delete Datapoint</q-item-section>
</q-item>
</q-list>
</q-menu>
</template>
<script setup lang="ts">
//import { useQuasar } from 'quasar';
//import { NotifyResponse, NotifyError } from 'src/composables/notify';
import { contextMenuState, contextMenuRef } from 'src/composables/dbm/useContextMenu';
//import AddDatapoint from 'src/components/dbm/AddDatapoint.vue';
//import { send } from 'src/services/websocket';
//const $q = useQuasar();
function handleAction(action: string) {
console.log(`Action '${action}' on node:`, contextMenuState.value);
// Add your actual logic here
switch (action) {
case 'Add':
// send({
// set: [
// {
// uuid: contextMenuState.value?.key,
// path: 'New',
// type: 'BIT',
// value: true,
// create: true,
// },
// ],
// })
// .then((response) => {
// if (response?.set) {
// console.log(response);
// } else {
// NotifyResponse($q, response);
// }
// })
// .catch((err) => {
// NotifyError($q, err);
// });
console.log(4);
}
}
</script>

View File

@@ -0,0 +1,36 @@
<template>
<div class="q-pa-md">
<q-table
v-if="props.rows.length > 0"
style="height: 600px"
flat
bordered
:title="props.rows[0]?.path"
:rows="props.rows ?? []"
:columns="columns"
row-key="path"
virtual-scroll
:rows-per-page-options="[0]"
/>
</div>
</template>
<script setup lang="ts">
import type { QTableProps } from 'quasar';
import type { Subscribe } from 'src/models/Subscribe';
// we generate lots of rows here
const props = defineProps<{
rows: Subscribe[];
}>();
const columns = [
{ name: 'path', label: 'Path', field: 'path', align: 'left' },
{
name: 'value',
label: 'Value',
field: 'value',
align: 'left',
},
] as QTableProps['columns'];
</script>