diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b68140f..a8ea283 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,7 +2,7 @@ name: Build Quasar SPA and Go Backend for lightController on: push: - branches: [ main ] + branches: [main] pull_request: jobs: @@ -36,7 +36,7 @@ jobs: - name: Build Quasar SPA run: quasar build - + - name: Set up Go uses: actions/setup-go@v5 with: @@ -57,9 +57,9 @@ jobs: working-directory: ./backend run: | if [ "${{ matrix.goos }}" == "windows" ]; then - GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} go build -o ../server-${{ matrix.goos }}-${{ matrix.goarch }}.exe main.go + GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} go build -ldflags="-s -w" -trimpath -o ../server-${{ matrix.goos }}-${{ matrix.goarch }}.exe main.go else - GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} go build -o ../server-${{ matrix.goos }}-${{ matrix.goarch }} main.go + GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} go build -ldflags="-s -w" -trimpath -o ../server-${{ matrix.goos }}-${{ matrix.goarch }} main.go fi - name: Upload build artifacts diff --git a/backend/main.go b/backend/main.go index b094130..1a7d984 100644 --- a/backend/main.go +++ b/backend/main.go @@ -2,6 +2,7 @@ package main import ( "backend/login" + "backend/models" secenes "backend/scenes" "backend/server" "backend/utils" @@ -20,6 +21,11 @@ import ( ) func main() { + + var allowOrigins models.StringSlice + + flag.Var(&allowOrigins, "allowOrigin", "Allowed origin (can repeat this flag)") + spa := flag.String("spa", "./dist/spa", "quasar spa files") workingDir := flag.String("workingDirectory", ".", "quasar spa files") ip := flag.String("ip", "0.0.0.0", "server listening ip") @@ -32,7 +38,7 @@ func main() { fmt.Println(1, *workingDir) os.Chdir(*workingDir) } - fmt.Println(1.1, *workingDir) + wd, err := os.Getwd() if err != nil { log.Fatalf("Could not get working directory: %v", err) @@ -67,19 +73,17 @@ func main() { s := server.NewServer() //get local ip - origins := []string{"http://localhost:9000"} - origins = append(origins, "http://localhost:9500") + allowOrigins = append(allowOrigins, "http://localhost:9000", "http://localhost:9500") localIP, err := utils.GetLocalIP() if err != nil { logger.Error("main", fmt.Sprintf("get local ip : %s", err.Error())) } else { - origins = append(origins, fmt.Sprintf("http://%s:9000", localIP)) - origins = append(origins, fmt.Sprintf("http://%s:9500", localIP)) + allowOrigins = append(allowOrigins, fmt.Sprintf("http://%s:9000", localIP), fmt.Sprintf("http://%s:9500", localIP)) } - fmt.Println(123, origins) + s.Routes.Use(cors.New(cors.Config{ - AllowOrigins: origins, + AllowOrigins: allowOrigins, AllowMethods: []string{"POST", "GET", "DELETE", "OPTIONS"}, AllowHeaders: []string{"Origin", "Content-Type"}, AllowCredentials: true, diff --git a/backend/models/stringSlice.go b/backend/models/stringSlice.go new file mode 100644 index 0000000..329a7a1 --- /dev/null +++ b/backend/models/stringSlice.go @@ -0,0 +1,14 @@ +package models + +import "strings" + +type StringSlice []string + +func (s *StringSlice) String() string { + return strings.Join(*s, ",") +} + +func (s *StringSlice) Set(value string) error { + *s = append(*s, value) + return nil +} diff --git a/backend/scenes/scenes.go b/backend/scenes/scenes.go index 6a1daa0..d048c6b 100644 --- a/backend/scenes/scenes.go +++ b/backend/scenes/scenes.go @@ -204,12 +204,10 @@ func (sh *ScenesHandler) LoadScene(c *gin.Context) { return } c.JSON(http.StatusOK, scene) - break - } - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "error": fmt.Errorf("scene '%s' not found", scene.Name), - }) return } + + c.JSON(http.StatusBadRequest, gin.H{ + "error": fmt.Errorf("scene '%s' not found", scene.Name), + }) } diff --git a/package.json b/package.json index 7753f1f..104abf9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lightcontrol", - "version": "0.0.18", + "version": "0.0.19", "description": "A Tecamino App", "productName": "Light Control", "author": "A. Zuercher", diff --git a/src/boot/axios.ts b/src/boot/axios.ts index 71175f8..311c4e2 100644 --- a/src/boot/axios.ts +++ b/src/boot/axios.ts @@ -7,7 +7,7 @@ const baseURL = `http://${host}:${port}`; const api = axios.create({ baseURL: baseURL, - timeout: 10000, + timeout: 30000, headers: { 'Content-Type': 'application/json', }, diff --git a/src/boot/websocket.ts b/src/boot/websocket.ts index b6dd43b..a4ee6c8 100644 --- a/src/boot/websocket.ts +++ b/src/boot/websocket.ts @@ -1,6 +1,6 @@ import { boot } from 'quasar/wrappers'; import type { QVueGlobals } from 'quasar'; -import { initWebSocket } from 'src/services/websocket'; +import { initWebSocket } from '../vueLib/services/websocket'; export default boot(({ app }) => { const $q = app.config.globalProperties.$q as QVueGlobals; diff --git a/src/components/dbm/DBMTree.vue b/src/components/dbm/DBMTree.vue deleted file mode 100644 index c2a9214..0000000 --- a/src/components/dbm/DBMTree.vue +++ /dev/null @@ -1,180 +0,0 @@ - - - - - diff --git a/src/components/dbm/SubMenu.vue b/src/components/dbm/SubMenu.vue deleted file mode 100644 index 33fe24f..0000000 --- a/src/components/dbm/SubMenu.vue +++ /dev/null @@ -1,52 +0,0 @@ - - - diff --git a/src/components/dbm/dataTable.vue b/src/components/dbm/dataTable.vue deleted file mode 100644 index d61f57e..0000000 --- a/src/components/dbm/dataTable.vue +++ /dev/null @@ -1,82 +0,0 @@ - - - diff --git a/src/components/dialog/AddDatapoint.vue b/src/components/dialog/AddDatapoint.vue deleted file mode 100644 index 5ac1e11..0000000 --- a/src/components/dialog/AddDatapoint.vue +++ /dev/null @@ -1,101 +0,0 @@ - - - diff --git a/src/components/dialog/UpdateValueDialog.vue b/src/components/dialog/UpdateValueDialog.vue deleted file mode 100644 index 2f8a5eb..0000000 --- a/src/components/dialog/UpdateValueDialog.vue +++ /dev/null @@ -1,126 +0,0 @@ - - - - - diff --git a/src/components/lights/DomeLight.vue b/src/components/lights/DomeLight.vue index d644df0..d1ba0f9 100644 --- a/src/components/lights/DomeLight.vue +++ b/src/components/lights/DomeLight.vue @@ -105,14 +105,14 @@ diff --git a/src/components/lights/DragPad.vue b/src/components/lights/DragPad.vue index 964edfe..6390eb2 100644 --- a/src/components/lights/DragPad.vue +++ b/src/components/lights/DragPad.vue @@ -109,8 +109,8 @@ diff --git a/src/composables/dbm/dbmTree.ts b/src/composables/dbm/dbmTree.ts deleted file mode 100644 index 948d0d0..0000000 --- a/src/composables/dbm/dbmTree.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { reactive, computed, type Ref } from 'vue'; -import type { QVueGlobals } from 'quasar'; -import type { Subs } from 'src/models/Subscribe'; -import { NotifyResponse } from '../notify'; -import { setValues } from 'src/services/websocket'; -import { findSubscriptionByPath } from 'src/models/Subscriptions'; - -export const dbmData = reactive([]); -//export const reactiveValues = new Map>(); - -export type TreeNode = { - path: string | undefined; - key?: string; // optional: useful for QTree's node-key - lazy: boolean; - children?: TreeNode[]; -}; - -type TreeMap = { - [key: string]: { - __children: TreeMap; - uuid?: string; - value?: string | number | boolean | null; - lazy: boolean; - }; -}; - -const root: TreeMap = {}; - -export function buildTree(subs: Subs): TreeNode[] { - for (const { path, uuid, value, hasChild } of subs) { - if (!path) continue; - - const parts = path.split(':'); - let current = root; - - parts.forEach((part, idx) => { - if (!part) return; - - if (!current[part]) { - current[part] = { __children: {}, lazy: true }; - } - - if (idx === parts.length - 1 && uuid) { - if (current[part].uuid === uuid) return; - - current[part].uuid = uuid; - current[part].value = value?.value ?? null; - current[part].lazy = hasChild ?? false; - } - - current = current[part].__children; - }); - } - - function mapToTree(map: TreeMap): TreeNode[] { - return Object.entries(map).map(([key, node]) => ({ - path: key, - key: node.uuid ?? key, - value: node.value, - lazy: node.lazy, - children: mapToTree(node.__children), - })); - } - - const newTree = [ - { - path: 'DBM', - key: '00000000-0000-0000-0000-000000000000', - lazy: true, - children: mapToTree(root), - }, - ]; - return dbmData.splice(0, dbmData.length, ...newTree); -} - -export function removeSubtreeByParentKey(parentKey: string) { - function removeChildrenAndMarkLazy(nodes: TreeNode[], targetKey: string): boolean { - for (const node of nodes) { - if (node.key === targetKey) { - delete node.children; - node.lazy = true; - return true; - } - if (node.children) { - const found = removeChildrenAndMarkLazy(node.children, targetKey); - if (found) return true; - } - } - return false; - } - removeChildrenAndMarkLazy(dbmData, parentKey); -} - -export function updateValue( - path1: string, - $q: QVueGlobals, - toggle?: Ref, - path2?: string, - path3?: string, - value3?: number, -) { - return computed({ - get() { - const sub = findSubscriptionByPath(toggle?.value && path2 ? path2 : path1); - return sub?.value ? Number(sub.value ?? 0) : 0; - }, - set(val) { - const baseValue = val; - const setPaths = []; - if (toggle?.value && path2) { - setPaths.push({ path: path2, value: baseValue }); - } else { - setPaths.push({ path: path1, value: baseValue }); - } - - if (path3) { - setPaths.push({ path: path3, value: value3 ? value3 : baseValue }); - } - - setValues(setPaths) - .then((response) => NotifyResponse($q, response)) - .catch((err) => { - NotifyResponse( - $q, - `Failed to update [${path1 + ' ' + path2 + ' ' + path3}]: ${err}`, - 'error', - ); - }); - }, - }); -} diff --git a/src/composables/notify.ts b/src/composables/notify.ts deleted file mode 100644 index d8b8072..0000000 --- a/src/composables/notify.ts +++ /dev/null @@ -1,67 +0,0 @@ -import type { Response } from 'src/models/Response'; -import type { QVueGlobals } from 'quasar'; - -export function NotifyResponse( - $q: QVueGlobals, - 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, - }); - } -} - -export function NotifyDialog( - $q: QVueGlobals, - 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); - }); - }); -} diff --git a/src/models/Publish.ts b/src/models/Publish.ts deleted file mode 100644 index 13b0c81..0000000 --- a/src/models/Publish.ts +++ /dev/null @@ -1,16 +0,0 @@ -export type Publish = { - event: string; - uuid: string; - path: string; - type: string; - value: string | number | boolean | null; -}; -export type Pubs = Publish[]; - -import { updateSubscriptionValue } from './Subscriptions'; - -export function publishToSubscriptions(pubs: Pubs) { - pubs.forEach((pub) => { - updateSubscriptionValue(pub.uuid, pub.value); - }); -} diff --git a/src/models/Request.ts b/src/models/Request.ts deleted file mode 100644 index 56bdaa0..0000000 --- a/src/models/Request.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { Gets } from './Get'; -import type { Sets } from './Set'; -import type { Subs } from './Subscribe'; - -export type Request = { - get?: Gets; - set?: Sets; - subscribe?: Subs; - unsubscribe?: Subs; -}; diff --git a/src/models/Scene.ts b/src/models/Scene.ts index d93c54a..e370a8b 100644 --- a/src/models/Scene.ts +++ b/src/models/Scene.ts @@ -1,4 +1,4 @@ -import type { Value } from './Value'; +import type { Value } from '../vueLib/models/Value'; export interface Scene { name: string; diff --git a/src/pages/DataPage.vue b/src/pages/DataPage.vue index 84afd57..1670094 100644 --- a/src/pages/DataPage.vue +++ b/src/pages/DataPage.vue @@ -4,17 +4,17 @@ diff --git a/src/vueLib/buttons/DataTypes.vue b/src/vueLib/buttons/DataTypes.vue index 8acdec9..b7eaf91 100644 --- a/src/vueLib/buttons/DataTypes.vue +++ b/src/vueLib/buttons/DataTypes.vue @@ -3,22 +3,90 @@
Datatypes *
- - +
+
General
+ - + - + - - - - - - - - - +
+
+
Numbers
+
+ + + + + + +
+
+ + + + + + +
+
+ + + + +
+
@@ -26,7 +94,10 @@ diff --git a/src/vueLib/buttons/RadioButton.vue b/src/vueLib/buttons/RadioButton.vue index 24c75ec..bed9352 100644 --- a/src/vueLib/buttons/RadioButton.vue +++ b/src/vueLib/buttons/RadioButton.vue @@ -1,7 +1,7 @@ @@ -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'); }); }