-
-
+
+
+ {{ props.headerTitle }}
+
+
+
+
+
-
+
@@ -23,6 +28,7 @@ import { ref, computed } from 'vue';
const dialogRef = ref();
const open = () => dialogRef.value?.show();
+const close = () => dialogRef.value?.hide();
const minMaxIcon = ref('fullscreen');
const minMaxState = ref(false);
@@ -36,6 +42,10 @@ function minMax() {
}
const props = defineProps({
+ headerTitle: {
+ type: String,
+ default: '',
+ },
width: {
type: String,
default: '300px',
@@ -55,11 +65,18 @@ const cardStyle = computed(() => ({
transform: `translate(${position.value.x}px, ${position.value.y}px)`,
}));
-defineExpose({ open });
+defineExpose({ open, close });
diff --git a/src/vueLib/general/useNotify.ts b/src/vueLib/general/useNotify.ts
new file mode 100644
index 0000000..5fe05b9
--- /dev/null
+++ b/src/vueLib/general/useNotify.ts
@@ -0,0 +1,67 @@
+import type { Response } from '../models/Response';
+import { useQuasar } from 'quasar';
+
+export function useNotify() {
+ const $q = useQuasar();
+ function NotifyResponse(
+ response: Response | string | undefined,
+ type?: 'warning' | 'error',
+ timeout: number = 5000,
+ ) {
+ let color = 'green';
+ let icon = 'check_circle';
+
+ switch (type) {
+ case 'warning':
+ color = 'orange';
+ icon = 'warning';
+ break;
+ case 'error':
+ color = 'red';
+ icon = 'error';
+ break;
+ }
+
+ if (response) {
+ const message = typeof response === 'string' ? response : (response.message ?? '');
+ if (message === '') {
+ return;
+ }
+
+ color = typeof response === 'string' ? color : response?.error ? 'red' : color;
+ icon = typeof response === 'string' ? icon : response?.error ? 'error' : icon;
+ $q?.notify({
+ message: message,
+ color: color,
+ position: 'bottom-right',
+ icon: icon,
+ timeout: timeout,
+ });
+ }
+ }
+
+ function NotifyDialog(title: string, text: string, okText?: string, cancelText?: string) {
+ return new Promise((resolve) => {
+ $q.dialog({
+ title: title,
+ message: text,
+ persistent: true,
+ ok: okText ?? 'OK',
+ cancel: cancelText ?? 'CANCEL',
+ })
+ .onOk(() => {
+ resolve(true);
+ })
+ .onCancel(() => {
+ resolve(false);
+ })
+ .onDismiss(() => {
+ resolve(false);
+ });
+ });
+ }
+ return {
+ NotifyDialog,
+ NotifyResponse,
+ };
+}
diff --git a/src/vueLib/models/Drivers.ts b/src/vueLib/models/Drivers.ts
new file mode 100644
index 0000000..1ff5552
--- /dev/null
+++ b/src/vueLib/models/Drivers.ts
@@ -0,0 +1,5 @@
+export interface Driver {
+ type: string;
+ addess: number;
+ value: number;
+}
diff --git a/src/models/Get.ts b/src/vueLib/models/Get.ts
similarity index 81%
rename from src/models/Get.ts
rename to src/vueLib/models/Get.ts
index a2e3f16..eeca0a3 100644
--- a/src/models/Get.ts
+++ b/src/vueLib/models/Get.ts
@@ -7,7 +7,7 @@ type Get = {
path?: string;
type?: string;
rights?: string;
- value?: undefined;
+ value?: string | number | boolean | null;
query?: Query;
hasChild?: boolean;
};
diff --git a/src/models/Pong.ts b/src/vueLib/models/Pong.ts
similarity index 100%
rename from src/models/Pong.ts
rename to src/vueLib/models/Pong.ts
diff --git a/src/vueLib/models/Publish.ts b/src/vueLib/models/Publish.ts
new file mode 100644
index 0000000..657060c
--- /dev/null
+++ b/src/vueLib/models/Publish.ts
@@ -0,0 +1,50 @@
+export type Publish = {
+ event: string;
+ uuid: string;
+ path: string;
+ type: string;
+ value: string | number | boolean | null;
+ hasChild: boolean;
+};
+export type Pubs = Publish[];
+
+import { updateSubscriptionValue, removeRawSubscriptions } from './Subscriptions';
+import { buildTree, buildTreeWithRawSubs, removeNodes } from '../dbm/dbmTree';
+import type { RawSubs, RawSubscribe } from '../models/Subscribe';
+import { ref } from 'vue';
+import { UpdateTable } from '../dbm/updateTable';
+import { pathIsExpanded } from '../dbm/dbmTree';
+
+export function publishToSubscriptions(pubs: Pubs) {
+ let event = '';
+ const rawSubs = ref
([]);
+ pubs.forEach((pub) => {
+ switch (pub.event) {
+ case 'onCreate':
+ event = 'onCreate';
+ if (!pathIsExpanded(pub.path)) break;
+ pub.hasChild = pubs.length > 0;
+ rawSubs.value.push(pub as RawSubscribe);
+ break;
+ case 'onChange':
+ break;
+ case 'onDelete':
+ event = 'onDelete';
+ rawSubs.value.push(pub as RawSubscribe);
+ break;
+ }
+ updateSubscriptionValue(pub.uuid, pub.value);
+ });
+
+ switch (event) {
+ case 'onCreate':
+ buildTreeWithRawSubs(rawSubs.value);
+ break;
+ case 'onDelete':
+ buildTree(null);
+ removeRawSubscriptions(rawSubs.value);
+ UpdateTable();
+ removeNodes(pubs);
+ break;
+ }
+}
diff --git a/src/vueLib/models/Request.ts b/src/vueLib/models/Request.ts
new file mode 100644
index 0000000..c713a0a
--- /dev/null
+++ b/src/vueLib/models/Request.ts
@@ -0,0 +1,121 @@
+import type { Gets } from './Get';
+import type { Sets } from './Set';
+import type { Subs } from './Subscribe';
+import { api } from 'src/boot/axios';
+
+export type Request = {
+ get?: Gets;
+ set?: Sets;
+ subscribe?: Subs;
+ unsubscribe?: Subs;
+};
+
+const query = '/json_data';
+
+export async function getRequest(
+ uuid: string,
+ path: string = '',
+ depth: number = 1,
+): Promise {
+ let payload = {};
+ if (uuid !== '') {
+ payload = { uuid: uuid, path: path, query: { depth: depth } };
+ } else {
+ payload = { path: path, query: { depth: depth } };
+ }
+
+ const resp = await api.post(query, {
+ get: [payload],
+ });
+
+ if (resp.data.get && resp.data.get.length > 0) {
+ return resp.data.get;
+ } else {
+ throw new Error('No data returned');
+ }
+}
+
+export async function getRequests(gets: Gets): Promise {
+ const resp = await api.post(query, {
+ get: gets,
+ });
+
+ if (resp.data.get && resp.data.get.length > 0) {
+ return resp.data.get;
+ } else {
+ throw new Error('No data returned');
+ }
+}
+
+export async function rawSetsRequest(sets: Sets): Promise {
+ const resp = await api.post(query, {
+ set: sets,
+ });
+
+ if (resp.data.set && resp.data.set.length > 0) {
+ return resp.data.set;
+ } else {
+ throw new Error('No data returned');
+ }
+}
+
+export async function setRequest(
+ path: string,
+ type: string,
+ value: string | number | boolean,
+ rights?: string,
+ uuid?: string,
+ rename?: boolean,
+): Promise {
+ const payload = {
+ path: path,
+ type: type,
+ value: value,
+ rights: rights,
+ uuid: uuid,
+ rename: rename,
+ };
+
+ const resp = await api.post(query, {
+ set: [payload],
+ });
+
+ if (resp.data.set && resp.data.set.length > 0) {
+ return resp.data.set;
+ } else {
+ throw new Error('No data returned');
+ }
+}
+
+export async function setsRequest(sets: Sets): Promise {
+ const resp = await api.post(query, {
+ set: sets,
+ });
+
+ if (resp.data.set && resp.data.set.length > 0) {
+ return resp.data.set;
+ } else {
+ throw new Error('No data returned');
+ }
+}
+
+export async function deleteRequest(uuid?: string, path?: string, rename?: boolean): Promise {
+ let payload = {};
+ if (uuid) {
+ payload = { uuid: uuid, rename: rename };
+ } else if (path) {
+ payload = { path: path };
+ }
+
+ const resp = await api.delete('/json_data', {
+ data: {
+ set: [payload],
+ },
+ });
+
+ if (resp.data.set && resp.data.set.length > 0) {
+ return resp.data.set;
+ } else {
+ throw new Error('No data returned');
+ }
+}
diff --git a/src/models/Response.ts b/src/vueLib/models/Response.ts
similarity index 100%
rename from src/models/Response.ts
rename to src/vueLib/models/Response.ts
diff --git a/src/models/Set.ts b/src/vueLib/models/Set.ts
similarity index 58%
rename from src/models/Set.ts
rename to src/vueLib/models/Set.ts
index a7f2054..5bbbcf6 100644
--- a/src/models/Set.ts
+++ b/src/vueLib/models/Set.ts
@@ -2,8 +2,10 @@ export type Set = {
uuid?: string | undefined;
path?: string;
type?: string;
- value: string | number | boolean | undefined;
+ value: string | number | boolean | null | undefined;
+ rights?: string;
create?: boolean;
+ hasChild?: boolean;
};
export type Sets = Set[];
diff --git a/src/models/Subscribe.ts b/src/vueLib/models/Subscribe.ts
similarity index 71%
rename from src/models/Subscribe.ts
rename to src/vueLib/models/Subscribe.ts
index 142cd7d..cf53b11 100644
--- a/src/models/Subscribe.ts
+++ b/src/vueLib/models/Subscribe.ts
@@ -1,12 +1,14 @@
import { ref } from 'vue';
import type { Ref } from 'vue';
+import type { Driver } from './Drivers';
+import type { Set } from './Set';
export type Subscribe = {
uuid?: string;
path?: string;
depth?: number;
type?: string;
- drivers?: object | undefined;
+ drivers?: Record;
value?: Ref;
hasChild?: boolean;
};
@@ -18,20 +20,26 @@ export type RawSubscribe = {
path?: string;
depth?: number;
value?: string | number | boolean | null;
+ rights?: string;
hasChild?: boolean;
};
export type RawSubs = RawSubscribe[];
-export function convertToSubscribe(raw: RawSubscribe): Subscribe {
+export function convertToSubscribe(raw: RawSubscribe | Set): Subscribe {
return {
...raw,
+ uuid: raw.uuid ?? '',
value: ref(raw.value ?? null),
};
}
export function convertToSubscribes(rawList: RawSubs): Subs {
- const subs = rawList.map(convertToSubscribe);
+ const subs = rawList.map(convertToSubscribe).sort((a, b) => {
+ const aPath = a.path ?? '';
+ const bPath = b.path ?? '';
+ return aPath.localeCompare(bPath);
+ });
return subs as Subs;
}
diff --git a/src/models/Subscriptions.ts b/src/vueLib/models/Subscriptions.ts
similarity index 61%
rename from src/models/Subscriptions.ts
rename to src/vueLib/models/Subscriptions.ts
index 629da25..cda74b1 100644
--- a/src/models/Subscriptions.ts
+++ b/src/vueLib/models/Subscriptions.ts
@@ -1,7 +1,8 @@
import type { Ref } from 'vue';
import { reactive, ref } from 'vue';
-import { convertToSubscribe } from 'src/models/Subscribe';
-import type { Subs, Subscribe, RawSubs } from 'src/models/Subscribe';
+import { convertToSubscribe } from '../models/Subscribe';
+import type { Subscribe, RawSubs, RawSubscribe } from '../models/Subscribe';
+import type { Set } from './Set';
const EMPTYUUID = '00000000-0000-0000-0000-000000000000';
export const Subscriptions = reactive>({});
@@ -11,16 +12,12 @@ export type TableSubscription = {
value: Ref;
};
-export function getRows(): Subs {
- return Object.values(Subscriptions).map((sub) => {
- sub.path = sub.path?.split(':').pop() ?? '';
- if (!sub.type) sub.type = 'None';
- else sub.type = sub.type.toLowerCase();
- return sub;
- });
+export function addRawSubscription(sub: RawSubscribe | Set | undefined) {
+ if (sub === undefined) return;
+ addSubscription(convertToSubscribe(sub as RawSubscribe));
}
-export function addSubscriptions(subs: RawSubs) {
+export function addRawSubscriptions(subs: RawSubs) {
subs.forEach((sub) => addSubscription(convertToSubscribe(sub)));
}
@@ -46,9 +43,13 @@ export function updateSubscriptionValue(
Subscriptions[uuid].value = ref(value);
}
-export function removeSubscriptions(subs: RawSubs) {
+export function removeRawSubscription(sub: RawSubscribe | string) {
+ removeSubscription(typeof sub === 'string' ? sub : sub.uuid);
+}
+
+export function removeRawSubscriptions(subs: RawSubs) {
subs.forEach((sub) => {
- removeSubscription(sub.path);
+ removeSubscription(sub.uuid);
});
}
@@ -56,7 +57,7 @@ export function removeAllSubscriptions() {
Object.keys(Subscriptions).forEach((key) => delete Subscriptions[key]);
}
-function removeSubscription(uuid: string | undefined) {
+export function removeSubscription(uuid: string | undefined) {
if (uuid === undefined) return;
if (!Subscriptions || Subscriptions[uuid] === undefined) return;
delete Subscriptions[uuid];
@@ -65,3 +66,8 @@ function removeSubscription(uuid: string | undefined) {
export function findSubscriptionByPath(path: string): Subscribe | undefined {
return Object.values(Subscriptions).find((sub) => sub.path === path);
}
+
+export function findSubscriptionByUuid(uuid: string): Subscribe | undefined {
+ if (!Subscriptions[uuid]) return;
+ return Subscriptions[uuid];
+}
diff --git a/src/models/Value.ts b/src/vueLib/models/Value.ts
similarity index 100%
rename from src/models/Value.ts
rename to src/vueLib/models/Value.ts
diff --git a/src/vueLib/models/error.ts b/src/vueLib/models/error.ts
new file mode 100644
index 0000000..449fd45
--- /dev/null
+++ b/src/vueLib/models/error.ts
@@ -0,0 +1,13 @@
+import type { Response } from './Response';
+
+export function catchError(data: Error | Response): string {
+ if (data instanceof Response) {
+ if (data.message) return data.message;
+ else console.error(data);
+ } else if (data instanceof Error) {
+ return data.message;
+ } else {
+ console.error(data);
+ }
+ return '';
+}
diff --git a/src/services/websocket.ts b/src/vueLib/services/websocket.ts
similarity index 69%
rename from src/services/websocket.ts
rename to src/vueLib/services/websocket.ts
index 0ac28fb..f69ca6c 100644
--- a/src/services/websocket.ts
+++ b/src/vueLib/services/websocket.ts
@@ -1,42 +1,16 @@
-import type { Response } from 'src/models/Response';
-import { publishToSubscriptions } from 'src/models/Publish';
-import type { Request } from 'src/models/Request';
+import type { Response } from '../models/Response';
+import { publishToSubscriptions } from '../models/Publish';
+import type { Request } from '../models/Request';
import type { QVueGlobals } from 'quasar';
import { ref } from 'vue';
-import { NotifyResponse } from 'src/composables/notify';
-import { type Subs } from 'src/models/Subscribe';
-import type { Sets } from 'src/models/Set';
-import type { PongMessage } from 'src/models/Pong';
-import { addSubscriptions } from 'src/models/Subscriptions';
+import { type Subs } from '../models/Subscribe';
+import type { Sets } from '../models/Set';
+import { addRawSubscriptions } from '../models/Subscriptions';
const pendingResponses = new Map void>();
-//const lastKnownValues: Record = reactive({});
export let socket: WebSocket | null = null;
+
const isConnected = ref(false);
-let lastPongTime = Date.now();
-
-function pingLoop(interval: number = 5000) {
- // Start sending ping every 5 seconds
- setInterval(() => {
- if (!socket || socket.readyState !== WebSocket.OPEN) return;
-
- // If no pong received in last 10 seconds, close
- if (Date.now() - lastPongTime > interval + 10000) {
- console.warn('No pong response, closing socket...');
- socket.close();
- return;
- }
- socket.send(JSON.stringify({ type: 'ping' }));
- }, interval);
-}
-
-function isPong(msg: PongMessage | undefined | null) {
- if (msg?.type === 'pong') {
- lastPongTime = Date.now();
- return true;
- }
- return false;
-}
export function initWebSocket(url: string, $q?: QVueGlobals) {
const connect = () => {
@@ -45,9 +19,8 @@ export function initWebSocket(url: string, $q?: QVueGlobals) {
socket.onopen = () => {
console.log('WebSocket connected');
isConnected.value = true;
- // Start sending ping every 5 seconds
- pingLoop(5000);
};
+
socket.onclose = () => {
isConnected.value = false;
console.log('WebSocket disconnected');
@@ -74,9 +47,6 @@ export function initWebSocket(url: string, $q?: QVueGlobals) {
if (typeof event.data === 'string') {
const message = JSON.parse(event.data);
- // Handle pong
- if (isPong(message)) return;
-
const id = message.id;
if (id && pendingResponses.has(id)) {
pendingResponses.get(id)?.(message); // resolve the promise
@@ -127,7 +97,14 @@ function waitForSocketConnection(): Promise {
});
}
-export function subscribeToPath(q: QVueGlobals, path: string) {
+export function subscribeToPath(
+ NotifyResponse: (
+ resp: Response | string | undefined,
+ type?: 'warning' | 'error',
+ timeout?: 5000,
+ ) => void,
+ path: string,
+) {
subscribe([
{
path: path,
@@ -136,13 +113,13 @@ export function subscribeToPath(q: QVueGlobals, path: string) {
])
.then((response) => {
if (response?.subscribe) {
- addSubscriptions(response.subscribe);
+ addRawSubscriptions(response.subscribe);
} else {
- NotifyResponse(q, response);
+ NotifyResponse(response);
}
})
.catch((err) => {
- NotifyResponse(q, err, 'error');
+ NotifyResponse(err, 'error');
});
}