From 38610471f3bf5066a8ac97a59360db6efb1c1452 Mon Sep 17 00:00:00 2001 From: Adrian Zuercher Date: Fri, 27 Jun 2025 11:02:27 +0200 Subject: [PATCH] fix scene load and add first version UpdateValueDialog --- backend/scenes/scenes.go | 28 +- backend/utils/utils.go | 15 +- package-lock.json | 12 +- package.json | 4 +- src/components/dbm/UpdateValue.vue | 13 + src/components/dbm/dataTable.vue | 27 +- src/components/dialog/UpdateValueDialog.vue | 101 ++++++ src/components/lights/MovingHead.vue | 58 +--- src/components/scenes/ScenesPage.vue | 356 ++++++++++++++++++++ src/components/scenes/ScenesTab.vue | 292 ---------------- src/composables/dbm/dbmTree.ts | 10 +- src/models/Publish.ts | 8 +- src/router/routes.ts | 2 +- src/services/websocket.ts | 54 +-- 14 files changed, 592 insertions(+), 388 deletions(-) create mode 100644 src/components/dbm/UpdateValue.vue create mode 100644 src/components/dialog/UpdateValueDialog.vue create mode 100644 src/components/scenes/ScenesPage.vue delete mode 100644 src/components/scenes/ScenesTab.vue diff --git a/backend/scenes/scenes.go b/backend/scenes/scenes.go index 9dd29c6..6a1daa0 100644 --- a/backend/scenes/scenes.go +++ b/backend/scenes/scenes.go @@ -47,10 +47,23 @@ func (sh *ScenesHandler) SaveScene(c *gin.Context) { } if _, err := os.Stat(path.Join(sh.dir)); err != nil { - os.MkdirAll(sh.dir, 666) + err := os.MkdirAll(sh.dir, 0755) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "error": err.Error(), + }) + return + } } - f, err := os.OpenFile(path.Join(sh.dir, scene.Name+".scene"), os.O_CREATE|os.O_TRUNC|os.O_RDWR, 666) + f, err := os.OpenFile(path.Join(sh.dir, scene.Name+".scene"), os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0644) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "error": err.Error(), + }) + return + } + defer f.Close() _, err = f.Write(body) if err != nil { @@ -59,7 +72,6 @@ func (sh *ScenesHandler) SaveScene(c *gin.Context) { }) return } - defer f.Close() c.JSON(http.StatusOK, gin.H{ "message": fmt.Sprintf("Scene '%s' saved", scene.Name), @@ -155,6 +167,7 @@ func (sh *ScenesHandler) LoadScene(c *gin.Context) { } var scene models.Scene + err = json.Unmarshal(body, &scene) if err != nil { c.JSON(http.StatusBadRequest, gin.H{ @@ -190,6 +203,13 @@ 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.StatusOK, scene) } diff --git a/backend/utils/utils.go b/backend/utils/utils.go index a9067dc..72e9db5 100644 --- a/backend/utils/utils.go +++ b/backend/utils/utils.go @@ -3,6 +3,7 @@ package utils import ( "fmt" "io/fs" + "os" "os/exec" "path/filepath" "runtime" @@ -27,6 +28,10 @@ func OpenBrowser(url string, logger *logging.Logger) error { {"open", url}, // fallback } default: // Linux + if os.Getenv("DISPLAY") == "" && os.Getenv("WAYLAND_DISPLAY") == "" && os.Getenv("XDG_SESSION_TYPE") != "wayland" { + + return fmt.Errorf("os is running i headless mode do not start browser") + } commands = [][]string{ {"chromium-browser", "--kiosk", url}, {"google-chrome", "--kiosk", url}, @@ -47,14 +52,14 @@ func OpenBrowser(url string, logger *logging.Logger) error { return fmt.Errorf("could not open browser") } -func FindAllFiles(rootDir, fileExtention string) (files []string, err error){ - err = filepath.WalkDir(rootDir, func(path string, d fs.DirEntry, err error) error { - if d.IsDir() { +func FindAllFiles(rootDir, fileExtention string) (files []string, err error) { + err = filepath.WalkDir(rootDir, func(path string, d fs.DirEntry, err error) error { + if d.IsDir() { return nil - } else if filepath.Ext(d.Name()) == fileExtention{ + } else if filepath.Ext(d.Name()) == fileExtention { files = append(files, path) } return err }) - return + return } diff --git a/package-lock.json b/package-lock.json index 5550254..7f56cc4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,16 @@ { "name": "lightcontrol", - "version": "0.0.5", + "version": "0.0.14", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "lightcontrol", - "version": "0.0.5", + "version": "0.0.14", "hasInstallScript": true, "dependencies": { "@quasar/extras": "^1.16.4", - "axios": "^1.9.0", + "axios": "^1.10.0", "quasar": "^2.16.0", "vue": "^3.4.18", "vue-router": "^4.0.12" @@ -2312,9 +2312,9 @@ } }, "node_modules/axios": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", - "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz", + "integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", diff --git a/package.json b/package.json index ee952f9..8bf1ebe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lightcontrol", - "version": "0.0.14", + "version": "0.0.15", "description": "A Tecamino App", "productName": "Light Control", "author": "A. Zuercher", @@ -16,7 +16,7 @@ }, "dependencies": { "@quasar/extras": "^1.16.4", - "axios": "^1.9.0", + "axios": "^1.10.0", "quasar": "^2.16.0", "vue": "^3.4.18", "vue-router": "^4.0.12" diff --git a/src/components/dbm/UpdateValue.vue b/src/components/dbm/UpdateValue.vue new file mode 100644 index 0000000..af20b1c --- /dev/null +++ b/src/components/dbm/UpdateValue.vue @@ -0,0 +1,13 @@ + + + diff --git a/src/components/dbm/dataTable.vue b/src/components/dbm/dataTable.vue index b25a4df..cda59ad 100644 --- a/src/components/dbm/dataTable.vue +++ b/src/components/dbm/dataTable.vue @@ -11,14 +11,24 @@ row-key="path" virtual-scroll :rows-per-page-options="[0]" - /> + > + + + diff --git a/src/components/dialog/UpdateValueDialog.vue b/src/components/dialog/UpdateValueDialog.vue new file mode 100644 index 0000000..e4506e3 --- /dev/null +++ b/src/components/dialog/UpdateValueDialog.vue @@ -0,0 +1,101 @@ + + + diff --git a/src/components/lights/MovingHead.vue b/src/components/lights/MovingHead.vue index cb91c94..e1ed573 100644 --- a/src/components/lights/MovingHead.vue +++ b/src/components/lights/MovingHead.vue @@ -104,17 +104,17 @@ select import { useQuasar } from 'quasar'; import LightSlider from './LightSlider.vue'; import { NotifyResponse } from 'src/composables/notify'; -import { computed, onMounted, onUnmounted, ref } from 'vue'; -import { subscribe, unsubscribe, setValues } from 'src/services/websocket'; +import { onBeforeUpdate, computed, onMounted, onUnmounted, ref } from 'vue'; +import { subscribeToPath, unsubscribe, setValues } from 'src/services/websocket'; import { LocalStorage } from 'quasar'; -import { getSubscriptionsByPath, buildTree, dbmData } from 'src/composables/dbm/dbmTree'; +import { getSubscriptionsByPath, updateValue } from 'src/composables/dbm/dbmTree'; import DragPad from 'src/components/lights/DragPad.vue'; import SettingDialog from './SettingMovingHead.vue'; import type { Settings } from 'src/models/MovingHead'; const $q = useQuasar(); const brightness = updateBrightnessValue('MovingHead:Brightness'); -const state = updateValue('MovingHead:State'); +const state = updateValue('MovingHead:State', $q); const settings = ref({ show: false, reversePan: false, @@ -125,24 +125,11 @@ const settings = ref({ onMounted(() => { settings.value.reversePan = LocalStorage.getItem('reversePan') ?? false; settings.value.reverseTilt = LocalStorage.getItem('reverseTilt') ?? false; + subscribeToPath($q, 'MovingHead:.*'); +}); - subscribe([ - { - path: 'MovingHead:.*', - depth: 0, - }, - ]) - .then((response) => { - console.log(response); - if (response?.subscribe) { - dbmData.splice(0, dbmData.length, ...buildTree(response.subscribe)); - } else { - NotifyResponse($q, response); - } - }) - .catch((err) => { - NotifyResponse($q, err, 'error'); - }); +onBeforeUpdate(() => { + subscribeToPath($q, 'MovingHead:.*'); }); onUnmounted(() => { @@ -157,45 +144,28 @@ onUnmounted(() => { }); function changeState() { + console.log(55, brightness.value); + console.log(56, state.value); if (brightness.value === 0) { if (state.value === 0) { brightness.value = 255; return; } brightness.value = state.value; + console.log(57, brightness.value); return; } state.value = brightness.value; + console.log(58, state.value); brightness.value = 0; } -function updateValue(path: string, isDouble = false) { - return computed({ - get() { - const sub = getSubscriptionsByPath(path); - const value = sub ? Number(sub.value ?? 0) : 0; - return isDouble ? value : value; - }, - set(val) { - const setPaths = [{ path, value: val }]; - - if (isDouble) { - setPaths.push({ path: `${path}Fine`, value: val }); - } - - setValues(setPaths) - .then((response) => NotifyResponse($q, response)) - .catch((err) => console.error(`Failed to update ${path.split(':')[1]}:`, err)); - }, - }); -} - function updateBrightnessValue(path: string) { return computed({ get() { const sub = getSubscriptionsByPath(path); - const value = sub ? Number(sub.value ?? 0) : 0; - return value; + if (!sub.value) return 0; + return Number(sub.value.value); }, set(val) { const setPaths = [{ path, value: val }]; diff --git a/src/components/scenes/ScenesPage.vue b/src/components/scenes/ScenesPage.vue new file mode 100644 index 0000000..3d6b545 --- /dev/null +++ b/src/components/scenes/ScenesPage.vue @@ -0,0 +1,356 @@ + + + diff --git a/src/components/scenes/ScenesTab.vue b/src/components/scenes/ScenesTab.vue deleted file mode 100644 index 6f2294c..0000000 --- a/src/components/scenes/ScenesTab.vue +++ /dev/null @@ -1,292 +0,0 @@ - - - diff --git a/src/composables/dbm/dbmTree.ts b/src/composables/dbm/dbmTree.ts index 13871f6..c57785f 100644 --- a/src/composables/dbm/dbmTree.ts +++ b/src/composables/dbm/dbmTree.ts @@ -31,7 +31,7 @@ export function buildTree(subs: Subs): TreeNode[] { for (const item of subs) { if (item.path) { - Subscriptions[item.path] = item; + addNewSubscription(item); } const pathParts = item.path?.split(':') ?? []; let current = root; @@ -130,6 +130,11 @@ export function getSubscriptionsByPath(path: string) { return ref(Subscriptions[path]); } +export function addNewSubscription(sub: Subscribe) { + if (!sub.path) return; + Subscriptions[sub.path] = sub; +} + export function getAllSubscriptions() { return Object.values(Subscriptions); } @@ -145,8 +150,7 @@ export function updateValue( return computed({ get() { const sub = getSubscriptionsByPath(toggle?.value && path2 ? path2 : path1); - const value = sub?.value ? Number(sub.value.value ?? 0) : 0; - return value; + return sub?.value ? Number(sub.value.value ?? 0) : 0; }, set(val) { const baseValue = val; diff --git a/src/models/Publish.ts b/src/models/Publish.ts index 636bca6..009c07d 100644 --- a/src/models/Publish.ts +++ b/src/models/Publish.ts @@ -1,8 +1,8 @@ export type Publish = { event: string; - uuid?: string; - path?: string; - type?: string; - value?: undefined; + uuid: string; + path: string; + type: string; + value: undefined; }; export type Pubs = Publish[]; diff --git a/src/router/routes.ts b/src/router/routes.ts index 3227cbf..5c54edb 100644 --- a/src/router/routes.ts +++ b/src/router/routes.ts @@ -7,7 +7,7 @@ const routes: RouteRecordRaw[] = [ children: [ { path: '', component: () => import('pages/MainPage.vue') }, { path: '/data', component: () => import('pages/DataPage.vue') }, - { path: '/scenes', component: () => import('components/scenes/ScenesTab.vue') }, + { path: '/scenes', component: () => import('components/scenes/ScenesPage.vue') }, ], }, diff --git a/src/services/websocket.ts b/src/services/websocket.ts index c0511fe..6f4af78 100644 --- a/src/services/websocket.ts +++ b/src/services/websocket.ts @@ -3,18 +3,19 @@ import type { Publish } from 'src/models/Publish'; import type { Request } from 'src/models/Request'; import type { QVueGlobals } from 'quasar'; import { - getAllSubscriptions, buildTree, dbmData, - getSubscriptionsByUuid, + getSubscriptionsByPath, + getAllSubscriptions, } from 'src/composables/dbm/dbmTree'; -import { ref, reactive } from 'vue'; +import { ref } from 'vue'; import type { Subs } from 'src/models/Subscribe'; import type { Sets } from 'src/models/Set'; import type { PongMessage } from 'src/models/Pong'; +import { NotifyResponse } from 'src/composables/notify'; const pendingResponses = new Map void>(); -const lastKnownValues: Record = reactive({}); +//const lastKnownValues: Record = reactive({}); export let socket: WebSocket | null = null; const isConnected = ref(false); @@ -88,29 +89,14 @@ export function initWebSocket(url: string, $q?: QVueGlobals) { pendingResponses.delete(id); return; } + if (message.publish) { (message.publish as Publish[]).forEach((pub) => { - const uuid = pub.uuid; - const value = pub.value ?? ''; - - if (uuid === undefined) { - return; - } - - const oldValue = lastKnownValues[uuid]; - if (oldValue !== value) { - lastKnownValues[uuid] = value; // this is now reactive - if (pub.uuid) { - const existing = getSubscriptionsByUuid(pub.uuid); - - if (existing.value) { - existing.value.value = value; - } - } else { - getAllSubscriptions().push({ value, uuid: uuid }); - } - dbmData.splice(0, dbmData.length, ...buildTree(getAllSubscriptions())); // rebuild reactive tree + const sub = getSubscriptionsByPath(pub.path); + if (sub.value && pub.value) { + sub.value.value = pub.value; } + dbmData.splice(0, dbmData.length, ...buildTree(getAllSubscriptions())); // rebuild reactive tree }); } } @@ -157,6 +143,26 @@ export function subscribe(data: Subs): Promise { return send({ subscribe: data }); } +export function subscribeToPath(q: QVueGlobals, path: string) { + subscribe([ + { + path: path, + depth: 0, + }, + ]) + .then((response) => { + console.log(response); + if (response?.subscribe) { + dbmData.splice(0, dbmData.length, ...buildTree(response.subscribe)); + } else { + NotifyResponse(q, response); + } + }) + .catch((err) => { + NotifyResponse(q, err, 'error'); + }); +} + export function unsubscribe(data: Subs): Promise { return send({ unsubscribe: data }); }