diff --git a/src/components/DBMTree.vue b/src/components/DBMTree.vue
deleted file mode 100644
index 689b651..0000000
--- a/src/components/DBMTree.vue
+++ /dev/null
@@ -1,197 +0,0 @@
-
-
-
-
-
-
-
-
-
{{ props.node.path }}
-
-
- onValueEdit(val, props.node)"
- >
-
-
-
-
-
-
-
-
-
-
-
-
-
Test
-
-
-
-
-
diff --git a/src/components/DragPad.vue b/src/components/DragPad.vue
deleted file mode 100644
index bf7923a..0000000
--- a/src/components/DragPad.vue
+++ /dev/null
@@ -1,133 +0,0 @@
-
-
-
-
-
diff --git a/src/components/MovingHead.vue b/src/components/MovingHead.vue
deleted file mode 100644
index 294afea..0000000
--- a/src/components/MovingHead.vue
+++ /dev/null
@@ -1,241 +0,0 @@
-// channel description // 1 Red // 2 Red fine // 3 Green // 4 Green fine // 5 Blue // 6 Blue fine //
-7 White // 8 White fine // 9 Linear CTO ??? // 10 Macro Color ??? // 11 Strobe // 12 Dimmer // 13
-Dimer fine // 14 Pan // 15 Pan fine // 16 Tilt // 17 Tilt fine // 18 Function // 19 Reset // 20 Zoom
-// 21 Zoom rotation // 22 Shape selection // 23 Shape speed // 24 Shape fade // 25 Shape Red // 26
-Shape Green // 27 Shape Blue // 28 Shape White // 29 Shape Dimmer // 30 Background dimmer // 31
-Shape transition // 32 Shape Offset // 33 Foreground strobe // 34 Background strobe // 35 Background
-select
-
-
-
-
-
-
-
-
- Dimmer
-
- Red
-
- Green
-
- Blue
-
- White
-
- Zoom
-
-
-
-
-
- Settings
-
-
-
-
-
-
-
-
diff --git a/src/components/SettingMovingHead.vue b/src/components/SettingMovingHead.vue
deleted file mode 100644
index 6143329..0000000
--- a/src/components/SettingMovingHead.vue
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-
-
- Settings
-
-
-
- {{ reverseTilt ? 'Reversed Tilt' : 'Normal Tilt' }}
-
-
- {{ reversePan ? 'Reversed Pan' : 'Normal Pan' }}
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/components/SubMenu.vue b/src/components/SubMenu.vue
deleted file mode 100644
index 0ec04fb..0000000
--- a/src/components/SubMenu.vue
+++ /dev/null
@@ -1,48 +0,0 @@
-
-
-
-
- Add Datapoint
-
-
- Delete Datapoint
-
-
-
-
-
-
diff --git a/src/components/dbm/AddDatapoint.vue b/src/components/dbm/AddDatapoint.vue
new file mode 100644
index 0000000..e69de29
diff --git a/src/components/dbm/DBMTree.vue b/src/components/dbm/DBMTree.vue
new file mode 100644
index 0000000..711fd25
--- /dev/null
+++ b/src/components/dbm/DBMTree.vue
@@ -0,0 +1,246 @@
+
+
+
+
+
+
+
+
+
{{ props.node.path }}
+
+ onValueEdit(val, props.node)"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/dbm/SubMenu.vue b/src/components/dbm/SubMenu.vue
new file mode 100644
index 0000000..3c96254
--- /dev/null
+++ b/src/components/dbm/SubMenu.vue
@@ -0,0 +1,53 @@
+
+
+
+
+ Add Datapoint
+
+
+ Delete Datapoint
+
+
+
+
+
+
diff --git a/src/components/dbm/dataTable.vue b/src/components/dbm/dataTable.vue
new file mode 100644
index 0000000..7766fe1
--- /dev/null
+++ b/src/components/dbm/dataTable.vue
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
diff --git a/src/components/DomeLight.vue b/src/components/lights/DomeLight.vue
similarity index 72%
rename from src/components/DomeLight.vue
rename to src/components/lights/DomeLight.vue
index c878f61..0d5b3e9 100644
--- a/src/components/DomeLight.vue
+++ b/src/components/lights/DomeLight.vue
@@ -105,11 +105,14 @@
diff --git a/src/components/lights/DragPad.vue b/src/components/lights/DragPad.vue
new file mode 100644
index 0000000..fe3c1c9
--- /dev/null
+++ b/src/components/lights/DragPad.vue
@@ -0,0 +1,238 @@
+
+
+
+
+
+
+
diff --git a/src/components/lights/LightBarCBL.vue b/src/components/lights/LightBarCBL.vue
new file mode 100644
index 0000000..f2e617b
--- /dev/null
+++ b/src/components/lights/LightBarCBL.vue
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Settings
+
+
+
+
+
+
+
+
diff --git a/src/components/lights/LightSlider.vue b/src/components/lights/LightSlider.vue
new file mode 100644
index 0000000..8696235
--- /dev/null
+++ b/src/components/lights/LightSlider.vue
@@ -0,0 +1,121 @@
+
+
+
+
+
diff --git a/src/components/lights/MovingHead.vue b/src/components/lights/MovingHead.vue
new file mode 100644
index 0000000..6ea0b61
--- /dev/null
+++ b/src/components/lights/MovingHead.vue
@@ -0,0 +1,203 @@
+// channel description // 1 Red // 2 Red fine // 3 Green // 4 Green fine // 5 Blue // 6 Blue fine //
+7 White // 8 White fine // 9 Linear CTO ??? // 10 Macro Color ??? // 11 Strobe // 12 Dimmer // 13
+Dimer fine // 14 Pan // 15 Pan fine // 16 Tilt // 17 Tilt fine // 18 Function // 19 Reset // 20 Zoom
+// 21 Zoom rotation // 22 Shape selection // 23 Shape speed // 24 Shape fade // 25 Shape Red // 26
+Shape Green // 27 Shape Blue // 28 Shape White // 29 Shape Dimmer // 30 Background dimmer // 31
+Shape transition // 32 Shape Offset // 33 Foreground strobe // 34 Background strobe // 35 Background
+select
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Settings
+
+
+
+
+
+
+
+
diff --git a/src/components/SettingDomeLight.vue b/src/components/lights/SettingDomeLight.vue
similarity index 100%
rename from src/components/SettingDomeLight.vue
rename to src/components/lights/SettingDomeLight.vue
diff --git a/src/components/lights/SettingMovingHead.vue b/src/components/lights/SettingMovingHead.vue
new file mode 100644
index 0000000..d9fb49f
--- /dev/null
+++ b/src/components/lights/SettingMovingHead.vue
@@ -0,0 +1,58 @@
+
+
+
+
+ Settings
+
+
+
+ {{ settings.reverseTilt ? 'Reversed Tilt' : 'Normal Tilt' }}
+
+
+ {{ settings.reversePan ? 'Reversed Pan' : 'Normal Pan' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/composables/dbm/dbmTree.ts b/src/composables/dbm/dbmTree.ts
new file mode 100644
index 0000000..ff0d18a
--- /dev/null
+++ b/src/composables/dbm/dbmTree.ts
@@ -0,0 +1,158 @@
+import type { Subs } from 'src/models/Subscribe';
+import { ref, nextTick, computed } from 'vue';
+import { setValues } from 'src/services/websocket';
+import { NotifyResponse } from 'src/composables/notify';
+import type { QVueGlobals } from 'quasar';
+
+const Subscriptions = ref([]);
+
+export const dbmData = ref([]);
+
+export interface TreeNode {
+ path: string | undefined;
+ key?: string; // optional: useful for QTree's node-key
+ value?: string | number | boolean | undefined;
+ lazy: boolean;
+ children?: TreeNode[];
+}
+
+export function buildTree(subs: Subs): TreeNode[] {
+ type TreeMap = {
+ [key: string]: {
+ __children: TreeMap;
+ uuid?: string;
+ value?: string | undefined;
+ lazy: boolean;
+ };
+ };
+
+ const root: TreeMap = {};
+
+ Subscriptions.value = subs;
+
+ for (const item of subs) {
+ const pathParts = item.path?.split(':') ?? [];
+ let current = root;
+
+ for (let i = 0; i < pathParts.length; i++) {
+ const part = pathParts[i];
+
+ if (!part) continue;
+
+ if (!current[part]) {
+ current[part] = { __children: {}, lazy: true };
+ }
+
+ // Optionally attach uuid only at the final part
+ if (i === pathParts.length - 1 && item.uuid) {
+ current[part].uuid = item.uuid;
+ current[part].value = item.value !== undefined ? String(item.value) : '';
+ current[part].lazy = item.hasChild ?? false;
+ }
+ current = current[part].__children;
+ }
+ }
+
+ function convert(map: TreeMap): TreeNode[] {
+ return Object.entries(map).map(([path, node]) => ({
+ path,
+ key: node.uuid ?? path, // `key` is used by QTree
+ value: node.value,
+ lazy: node.lazy,
+ children: convert(node.__children),
+ }));
+ }
+
+ return [
+ {
+ path: 'DBM',
+ key: '00000000-0000-0000-0000-000000000000',
+ lazy: true,
+ children: convert(root),
+ },
+ ];
+}
+
+export function getTreeElementByPath(path: string) {
+ return dbmData.value.find((s) => s.path === path);
+}
+
+export function getSubscriptionsByUuid(uid: string | undefined) {
+ return Subscriptions.value.find((s) => s.uuid === uid);
+}
+
+export function addChildrentoTree(subs: Subs) {
+ const ZERO_UUID = '00000000-0000-0000-0000-000000000000';
+ const existingIds = new Set(Subscriptions.value.map((sub) => sub.uuid));
+ const newSubs = subs
+ .filter((sub) => sub.uuid !== ZERO_UUID) // Skip UUIDs with all zeroes
+ .filter((sub) => !existingIds.has(sub.uuid));
+
+ Subscriptions.value.push(...newSubs);
+
+ void nextTick(() => {
+ dbmData.value = buildTree(Subscriptions.value);
+ });
+}
+
+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.value, parentKey);
+}
+
+export function getSubscriptionsByPath(path: string | undefined) {
+ return Subscriptions.value.find((s) => s.path === path);
+}
+
+export function getAllSubscriptions() {
+ return Subscriptions.value;
+}
+
+export function updateValue(
+ path1: string,
+ $q: QVueGlobals,
+ path2?: string,
+ path3?: string,
+ value3?: number,
+) {
+ return computed({
+ get() {
+ const sub = getSubscriptionsByPath(path1);
+ const value = sub ? Number(sub.value ?? 0) : 0;
+ return Math.round((100 / 255) * value);
+ },
+ set(val) {
+ const baseValue = Math.round((255 / 100) * val);
+ const setPaths = [{ path: path1, value: baseValue }];
+ if (path2) {
+ setPaths.push({ path: path2, 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/dbm/useContextMenu.ts b/src/composables/dbm/useContextMenu.ts
new file mode 100644
index 0000000..8ed739f
--- /dev/null
+++ b/src/composables/dbm/useContextMenu.ts
@@ -0,0 +1,12 @@
+import { ref } from 'vue';
+import type { TreeNode } from './dbmTree';
+
+export const contextMenuRef = ref();
+
+export const contextMenuState = ref();
+
+export function openContextMenu(event: MouseEvent, node: undefined) {
+ event.preventDefault();
+ contextMenuState.value = node;
+ contextMenuRef.value?.show(event);
+}
diff --git a/src/composables/dbmTree.ts b/src/composables/dbmTree.ts
deleted file mode 100644
index 28ed466..0000000
--- a/src/composables/dbmTree.ts
+++ /dev/null
@@ -1,110 +0,0 @@
-import type { Subs } from 'src/models/Subscribe';
-import { ref, nextTick } from 'vue';
-
-const Subscriptions = ref([]);
-
-export const dbmData = ref([]);
-
-export interface TreeNode {
- path: string | undefined;
- key?: string; // optional: useful for QTree's node-key
- value?: string | number | boolean | undefined;
- children?: TreeNode[];
-}
-
-export function buildTree(subs: Subs): TreeNode[] {
- type TreeMap = {
- [key: string]: {
- __children: TreeMap;
- uuid?: string;
- value?: string | undefined;
- };
- };
-
- const root: TreeMap = {};
-
- Subscriptions.value = subs;
-
- for (const item of subs) {
- const pathParts = item.path?.split(':') ?? [];
- let current = root;
-
- for (let i = 0; i < pathParts.length; i++) {
- const part = pathParts[i];
-
- if (!part) continue;
-
- if (!current[part]) {
- current[part] = { __children: {} };
- }
-
- // Optionally attach uuid only at the final part
- if (i === pathParts.length - 1 && item.uuid) {
- current[part].uuid = item.uuid;
- current[part].value = item.value !== undefined ? String(item.value) : '';
- }
-
- current = current[part].__children;
- }
- }
-
- function convert(map: TreeMap): TreeNode[] {
- return Object.entries(map).map(([path, node]) => ({
- path,
- key: node.uuid ?? path, // `key` is used by QTree
- value: node.value,
- children: convert(node.__children),
- }));
- }
-
- return [
- {
- path: 'DBM',
- key: 'DBM',
- children: convert(root),
- },
- ];
-}
-
-export function getTreeElementByPath(path: string) {
- return dbmData.value.find((s) => s.path === path);
-}
-
-export function getSubscriptionsByUuid(uid: string | undefined) {
- return Subscriptions.value.find((s) => s.uuid === uid);
-}
-
-export function addChildrentoTree(subs: Subs) {
- Subscriptions.value.push(...subs);
- void nextTick(() => {
- dbmData.value = buildTree(Subscriptions.value);
- });
-}
-
-export function removeSubtreeByParentKey(parentKey: string) {
- // Find the parent node using its uuid
- const parent = Subscriptions.value.find((s) => s.uuid === parentKey);
- if (!parent || !parent.path) return;
-
- const parentPath = parent.path;
-
- // Now filter out the children, but NOT the parent itself
- Subscriptions.value = Subscriptions.value.filter((s) => {
- // Keep the parent itself (don't remove it)
- if (s.uuid === parentKey) return true;
-
- // Remove any child whose path starts with parentPath + '/' (descendants)
- return !s.path?.startsWith(parentPath + ':');
- });
-
- // Rebuild the tree after removing children, but keeping the parent
- dbmData.value = buildTree(Subscriptions.value);
-}
-
-export function getSubscriptionsByPath(path: string) {
- return Subscriptions.value.find((s) => s.path === path);
-}
-
-export function getAllSubscriptions() {
- return Subscriptions.value;
-}
diff --git a/src/composables/useContextMenu.ts b/src/composables/useContextMenu.ts
deleted file mode 100644
index 7cd3263..0000000
--- a/src/composables/useContextMenu.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { ref } from 'vue';
-
-export const contextMenu = ref({
- show: false,
- x: 0,
- y: 0,
- anchor: 'top left',
- self: 'top left',
- node: null,
-});
-
-export function openContextMenu(event: MouseEvent, node: undefined) {
- contextMenu.value = {
- show: true,
- x: event.clientX,
- y: event.clientY,
- anchor: 'top left',
- self: 'top left',
- node: node ?? null,
- };
-}
diff --git a/src/layouts/MainLayout.vue b/src/layouts/MainLayout.vue
index e6ea116..ed306cb 100644
--- a/src/layouts/MainLayout.vue
+++ b/src/layouts/MainLayout.vue
@@ -6,11 +6,11 @@
Light Control
- Version 0.0.1
+ Version {{ version }}
-
+
Home
@@ -29,6 +29,7 @@
diff --git a/src/pages/IndexPage.vue b/src/pages/IndexPage.vue
index a0f5b40..db9be6f 100644
--- a/src/pages/IndexPage.vue
+++ b/src/pages/IndexPage.vue
@@ -2,22 +2,36 @@
-
+
-
-
+
+