fix scene load and add first version UpdateValueDialog
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
|
@@ -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},
|
||||
|
12
package-lock.json
generated
12
package-lock.json
generated
@@ -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",
|
||||
|
@@ -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"
|
||||
|
13
src/components/dbm/UpdateValue.vue
Normal file
13
src/components/dbm/UpdateValue.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<QCard v-if="props.display"> Test </QCard>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps({
|
||||
display: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
</script>
|
@@ -11,14 +11,24 @@
|
||||
row-key="path"
|
||||
virtual-scroll
|
||||
:rows-per-page-options="[0]"
|
||||
/>
|
||||
>
|
||||
<template v-slot:body-cell-value="props">
|
||||
<q-td :props="props" @click="openDialog(props.row)">
|
||||
<span :class="['cursor-pointer', open ? 'text-green' : '']"> {{ props.row.value }}</span>
|
||||
</q-td>
|
||||
</template>
|
||||
</q-table>
|
||||
<Dialog dialogLabel="Update Value" :show-dialog="open" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Dialog from 'src/components/dialog/UpdateValueDialog.vue';
|
||||
import type { QTableProps } from 'quasar';
|
||||
import type { Subs } from 'src/models/Subscribe';
|
||||
import { computed } from 'vue';
|
||||
import type { Subs, Subscribe } from 'src/models/Subscribe';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
const open = ref(false);
|
||||
|
||||
// we generate lots of rows here
|
||||
const props = defineProps<{
|
||||
@@ -35,5 +45,16 @@ const columns = [
|
||||
field: 'value',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
name: 'test',
|
||||
label: '',
|
||||
field: 'test',
|
||||
align: 'left',
|
||||
},
|
||||
] as QTableProps['columns'];
|
||||
|
||||
function openDialog(item: Subscribe) {
|
||||
console.log(77, item);
|
||||
open.value = true;
|
||||
}
|
||||
</script>
|
||||
|
101
src/components/dialog/UpdateValueDialog.vue
Normal file
101
src/components/dialog/UpdateValueDialog.vue
Normal file
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<q-dialog v-model="internalShowDialog">
|
||||
<q-card :style="'width:' + props.width">
|
||||
<q-card-section
|
||||
v-if="props.dialogLabel"
|
||||
class="text-h6 text-center"
|
||||
:class="'text-' + props.labelColor"
|
||||
>{{ props.dialogLabel }}</q-card-section
|
||||
>
|
||||
<q-card-section>
|
||||
<q-input v-model="inputValue"></q-input>
|
||||
</q-card-section>
|
||||
<q-card-section v-if="props.text" class="text-center" style="white-space: pre-line">{{
|
||||
props.text
|
||||
}}</q-card-section>
|
||||
<q-card-actions align="right" class="text-primary">
|
||||
<q-btn v-if="props.buttonCancelLabel" flat :label="props.buttonCancelLabel" v-close-popup>
|
||||
</q-btn>
|
||||
<q-btn
|
||||
v-if="props.buttonOkLabel"
|
||||
flat
|
||||
:label="props.buttonOkLabel"
|
||||
v-close-popup
|
||||
@click="closeDialog"
|
||||
>
|
||||
</q-btn>
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
showDialog: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
buttonOkLabel: {
|
||||
type: String,
|
||||
default: 'OK',
|
||||
},
|
||||
labelColor: {
|
||||
type: String,
|
||||
default: 'primary',
|
||||
},
|
||||
dialogLabel: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
text: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
buttonCancelLabel: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '300px',
|
||||
},
|
||||
value: {
|
||||
type: [String, Number],
|
||||
},
|
||||
});
|
||||
|
||||
const inputValue = ref(props.value);
|
||||
|
||||
const emit = defineEmits(['update:showDialog', 'update:value', 'confirmed', 'cancel']);
|
||||
const internalShowDialog = ref(props.showDialog);
|
||||
|
||||
watch(inputValue, (val) => {
|
||||
emit('update:value', val);
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.showDialog,
|
||||
(newValue) => {
|
||||
console.log('watch showDialog', newValue);
|
||||
internalShowDialog.value = newValue;
|
||||
},
|
||||
);
|
||||
watch(internalShowDialog, (newValue) => {
|
||||
console.log('watch internalShowDialog', newValue);
|
||||
emit('update:showDialog', newValue);
|
||||
if (!newValue) {
|
||||
console.log('emit cancel');
|
||||
emit('cancel');
|
||||
} else {
|
||||
console.log('emit confirmed');
|
||||
emit('confirmed');
|
||||
}
|
||||
});
|
||||
|
||||
function closeDialog() {
|
||||
internalShowDialog.value = false;
|
||||
emit('update:showDialog', false);
|
||||
}
|
||||
</script>
|
@@ -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<Settings>({
|
||||
show: false,
|
||||
reversePan: false,
|
||||
@@ -125,24 +125,11 @@ const settings = ref<Settings>({
|
||||
onMounted(() => {
|
||||
settings.value.reversePan = LocalStorage.getItem('reversePan') ?? false;
|
||||
settings.value.reverseTilt = LocalStorage.getItem('reverseTilt') ?? false;
|
||||
|
||||
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');
|
||||
subscribeToPath($q, 'MovingHead:.*');
|
||||
});
|
||||
|
||||
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 }];
|
||||
|
356
src/components/scenes/ScenesPage.vue
Normal file
356
src/components/scenes/ScenesPage.vue
Normal file
@@ -0,0 +1,356 @@
|
||||
<template>
|
||||
<!-- new edit scene dialog-->
|
||||
<q-dialog v-model="showDialog" persistent>
|
||||
<q-card style="min-width: 350px">
|
||||
<q-card-section>
|
||||
<div class="text-primary text-h6">{{ dialogLabel }}</div>
|
||||
</q-card-section>
|
||||
|
||||
<q-card-section class="q-pt-none">
|
||||
<q-input
|
||||
class="q-mb-md"
|
||||
dense
|
||||
v-model="newScene.name"
|
||||
placeholder="Name"
|
||||
autofocus
|
||||
:rules="[(val) => !!val || 'Field is required']"
|
||||
@keyup.enter="saveScene"
|
||||
/>
|
||||
<q-input
|
||||
dense
|
||||
v-model="newScene.description"
|
||||
placeholder="Description"
|
||||
autofocus
|
||||
@keyup.enter="saveScene"
|
||||
/>
|
||||
<div class="q-py-md">
|
||||
<div class="q-gutter-sm">
|
||||
<q-checkbox v-model="newScene.movingHead" label="Moving Head" />
|
||||
<q-checkbox v-model="newScene.lightBar" label="Light Bar" />
|
||||
</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
|
||||
<q-card-actions align="right" class="text-primary">
|
||||
<q-btn flat label="Cancel" v-close-popup />
|
||||
<q-btn flat :label="dialogLabel" @click="saveScene()" />
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
<Dialog
|
||||
dialogLabel="Duplicate Scene"
|
||||
:text="`Scene '${newScene.name}' exists already`"
|
||||
:show-dialog="existsAlready"
|
||||
v-on:update:show-dialog="existsAlready = $event"
|
||||
/>
|
||||
<q-list
|
||||
bordered
|
||||
v-if="scenes?.length > 0"
|
||||
class="q-mx-auto"
|
||||
style="max-width: 100%; max-width: 500px"
|
||||
>
|
||||
<q-btn
|
||||
rounded
|
||||
color="primary"
|
||||
:class="['q-ma-md', 'text-bold', 'text-white']"
|
||||
@click="openDialog('add')"
|
||||
>Add New Scene</q-btn
|
||||
>
|
||||
<q-item class="row">
|
||||
<q-item-section :class="['text-black', 'text-bold', 'col-5']">Name</q-item-section>
|
||||
<q-item-section :class="['text-black', 'text-left', 'text-bold', 'text-left']"
|
||||
>Description</q-item-section
|
||||
>
|
||||
</q-item>
|
||||
<q-item
|
||||
v-for="(item, index) in scenes"
|
||||
:key="item.name"
|
||||
bordered
|
||||
style="border: 0.1px solid lightgray; border-radius: 5px; margin-bottom: 1px"
|
||||
>
|
||||
<q-item-section
|
||||
@click="openDialog('load', item)"
|
||||
:class="['text-black', 'text-left', 'cursor-pointer']"
|
||||
style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis"
|
||||
>{{ item.name }}</q-item-section
|
||||
>
|
||||
<q-item-section
|
||||
@click="openDialog('load', item)"
|
||||
:class="['text-black', 'text-left', 'cursor-pointer']"
|
||||
left
|
||||
style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis"
|
||||
>{{ item.description }}</q-item-section
|
||||
>
|
||||
<q-item-section top side>
|
||||
<div class="text-grey-8 q-gutter-xs">
|
||||
<q-btn size="12px" flat dense round icon="delete" @click="removeScene(item.name)" />
|
||||
<q-btn
|
||||
size="12px"
|
||||
flat
|
||||
dense
|
||||
round
|
||||
icon="more_vert"
|
||||
@click="openDialog('edit', item, index)"
|
||||
/>
|
||||
</div>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
<!-- Fallback if list is empty -->
|
||||
<div v-else class="q-pa-md text-grey text-center">
|
||||
<div>No scenes available</div>
|
||||
<q-btn
|
||||
rounded
|
||||
color="primary"
|
||||
:class="['q-ma-md', 'text-bold', 'text-white']"
|
||||
@click="openDialog('add')"
|
||||
>Add First Scene</q-btn
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { NotifyDialog } from 'src/composables/notify';
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { useQuasar } from 'quasar';
|
||||
import type { Scene } from 'src/models/Scene';
|
||||
import type { Set } from 'src/models/Set';
|
||||
import axios from 'axios';
|
||||
import { api } from 'boot/axios';
|
||||
import { NotifyResponse } from 'src/composables/notify';
|
||||
import Dialog from 'src/components/dialog/OkDialog.vue';
|
||||
import { setValues } from 'src/services/websocket';
|
||||
const $q = useQuasar();
|
||||
const showDialog = ref(false);
|
||||
const dialog = ref('');
|
||||
const existsAlready = ref(false);
|
||||
const editIndex = ref(-1);
|
||||
const dialogLabel = ref('');
|
||||
const newScene = reactive<Scene>({
|
||||
name: '',
|
||||
movingHead: false,
|
||||
lightBar: false,
|
||||
});
|
||||
|
||||
const scenes = ref<Scene[]>([]);
|
||||
|
||||
const quasarApi = axios.create({
|
||||
baseURL: `http://localhost:9500`,
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
quasarApi
|
||||
.get('/api/loadScenes')
|
||||
.then((resp) => {
|
||||
if (resp.data) {
|
||||
scenes.value = resp.data;
|
||||
}
|
||||
})
|
||||
.catch((err) => NotifyResponse($q, err.response.data.error, 'error'));
|
||||
});
|
||||
|
||||
function removeScene(name: string) {
|
||||
dialog.value = '';
|
||||
NotifyDialog($q, 'Delete', 'Do you want to delete scene: ' + name, 'YES', 'NO')
|
||||
.then((res) => {
|
||||
if (res) {
|
||||
scenes.value = scenes.value.filter((s) => s.name !== name);
|
||||
|
||||
quasarApi
|
||||
.delete('/api/deleteScene', {
|
||||
data: { name },
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.data) {
|
||||
NotifyResponse($q, res.data, 'warning');
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
NotifyResponse($q, err.response.data.error, 'error');
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((err) => NotifyResponse($q, err.resp, 'warning'));
|
||||
}
|
||||
|
||||
function openDialog(dialogType: string, scene?: Scene, index?: number) {
|
||||
switch (dialogType) {
|
||||
case 'add':
|
||||
dialog.value = 'add';
|
||||
dialogLabel.value = 'Add Scene';
|
||||
newScene.name = '';
|
||||
newScene.movingHead = true;
|
||||
newScene.lightBar = true;
|
||||
showDialog.value = true;
|
||||
break;
|
||||
case 'edit':
|
||||
if (!scene) return;
|
||||
if (index === undefined) return;
|
||||
dialog.value = 'edit';
|
||||
dialogLabel.value = 'Update Scene';
|
||||
editIndex.value = index;
|
||||
Object.assign(newScene, JSON.parse(JSON.stringify(scene)));
|
||||
showDialog.value = true;
|
||||
break;
|
||||
case 'load':
|
||||
if (!scene) return;
|
||||
dialog.value = 'load';
|
||||
dialogLabel.value = 'Load Scene';
|
||||
quasarApi
|
||||
.post('/api/loadScene', scene)
|
||||
.then((res) => {
|
||||
if (res.data) {
|
||||
Object.assign(newScene, JSON.parse(JSON.stringify(res.data)));
|
||||
showDialog.value = true;
|
||||
}
|
||||
})
|
||||
.catch((err) => NotifyResponse($q, err.response.data.error, 'error'));
|
||||
break;
|
||||
default:
|
||||
showDialog.value = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const saveScene = async () => {
|
||||
const sendValues = [];
|
||||
|
||||
if (!newScene.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
const exists = scenes.value.some(
|
||||
(item, index) => item.name === newScene.name && index !== editIndex.value,
|
||||
);
|
||||
|
||||
switch (dialog.value) {
|
||||
case 'add':
|
||||
if (exists) {
|
||||
existsAlready.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (newScene.movingHead) {
|
||||
sendValues.push({
|
||||
path: 'MovingHead',
|
||||
query: { depth: 0 },
|
||||
});
|
||||
}
|
||||
|
||||
if (newScene.lightBar) {
|
||||
sendValues.push({
|
||||
path: 'LightBar',
|
||||
query: { depth: 0 },
|
||||
});
|
||||
}
|
||||
|
||||
if (sendValues.length > 0) {
|
||||
try {
|
||||
const res = await api.post('/json_data', { get: sendValues });
|
||||
newScene.values = res.data.get;
|
||||
} catch (err) {
|
||||
NotifyResponse($q, err as Error, 'error');
|
||||
}
|
||||
} else {
|
||||
newScene.values = [];
|
||||
}
|
||||
|
||||
scenes.value = [...scenes.value, JSON.parse(JSON.stringify(newScene))];
|
||||
|
||||
// Sort alphabetically by scene name
|
||||
scenes.value.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
quasarApi
|
||||
.post('/api/saveScene', JSON.stringify(newScene))
|
||||
.then((res) => {
|
||||
if (res.data) {
|
||||
NotifyResponse($q, res.data);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
NotifyResponse($q, err.response.data.error, 'error');
|
||||
});
|
||||
scenes.value = [...scenes.value];
|
||||
break;
|
||||
case 'edit':
|
||||
if (exists) {
|
||||
existsAlready.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (newScene.movingHead) {
|
||||
sendValues.push({
|
||||
path: 'MovingHead',
|
||||
query: { depth: 0 },
|
||||
});
|
||||
}
|
||||
|
||||
if (newScene.lightBar) {
|
||||
sendValues.push({
|
||||
path: 'LightBar',
|
||||
query: { depth: 0 },
|
||||
});
|
||||
}
|
||||
|
||||
if (sendValues.length > 0) {
|
||||
try {
|
||||
const res = await api.post('/json_data', { get: sendValues });
|
||||
newScene.values = res.data.get;
|
||||
} catch (err) {
|
||||
NotifyResponse($q, err as Error, 'error');
|
||||
}
|
||||
} else {
|
||||
newScene.values = [];
|
||||
}
|
||||
scenes.value.splice(editIndex.value, 1, JSON.parse(JSON.stringify(newScene)));
|
||||
scenes.value.sort((a, b) => a.name.localeCompare(b.name));
|
||||
scenes.value = [...scenes.value];
|
||||
|
||||
quasarApi
|
||||
.post('/api/saveScene', JSON.stringify(newScene))
|
||||
.then((res) => {
|
||||
if (res.data) {
|
||||
NotifyResponse($q, res.data);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
NotifyResponse($q, err.response.data.error, 'error');
|
||||
});
|
||||
scenes.value = [...scenes.value];
|
||||
break;
|
||||
case 'load':
|
||||
{
|
||||
const setPaths = <Set[]>[];
|
||||
|
||||
if (newScene.movingHead) {
|
||||
newScene.values?.forEach((element) => {
|
||||
if (element.path && element.path.includes('MovingHead')) {
|
||||
setPaths.push({ path: element.path, value: element.value });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (newScene.lightBar) {
|
||||
newScene.values?.forEach((element) => {
|
||||
if (element.path && element.path.includes('LightBar')) {
|
||||
setPaths.push({ path: element.path, value: element.value });
|
||||
}
|
||||
});
|
||||
}
|
||||
setValues(setPaths)
|
||||
.then((response) => {
|
||||
NotifyResponse($q, response);
|
||||
})
|
||||
.catch((err) => console.error(`Failed to load scene ${newScene.name}`, err));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
dialog.value = '';
|
||||
showDialog.value = false;
|
||||
};
|
||||
</script>
|
@@ -1,292 +0,0 @@
|
||||
<template>
|
||||
<!-- new edit scene dialog-->
|
||||
<q-dialog v-model="showDialog" persistent>
|
||||
<q-card style="min-width: 350px">
|
||||
<q-card-section>
|
||||
<div class="text-primary text-h6">{{ dialogLabel }}</div>
|
||||
</q-card-section>
|
||||
|
||||
<q-card-section class="q-pt-none">
|
||||
<q-input
|
||||
class="q-mb-md"
|
||||
dense
|
||||
v-model="newScene.name"
|
||||
placeholder="Name"
|
||||
autofocus
|
||||
:rules="[(val) => !!val || 'Field is required']"
|
||||
@keyup.enter="saveScene"
|
||||
/>
|
||||
<q-input
|
||||
dense
|
||||
v-model="newScene.description"
|
||||
placeholder="Description"
|
||||
autofocus
|
||||
@keyup.enter="saveScene"
|
||||
/>
|
||||
<div class="q-py-md">
|
||||
<div class="q-gutter-sm">
|
||||
<q-checkbox v-model="newScene.movingHead" label="Moving Head" />
|
||||
<q-checkbox v-model="newScene.lightBar" label="Light Bar" />
|
||||
</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
|
||||
<q-card-actions align="right" class="text-primary">
|
||||
<q-btn flat label="Cancel" v-close-popup />
|
||||
<q-btn flat :label="dialogLabel" @click="saveScene" />
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
<Dialog
|
||||
dialogLabel="Duplicate Scene"
|
||||
:text="`Scene '${newScene.name}' exists already`"
|
||||
:show-dialog="existsAlready"
|
||||
v-on:update:show-dialog="existsAlready = $event"
|
||||
/>
|
||||
<q-list
|
||||
bordered
|
||||
v-if="items.length > 0"
|
||||
class="q-mx-auto"
|
||||
style="max-width: 100%; max-width: 500px"
|
||||
>
|
||||
<q-btn
|
||||
rounded
|
||||
color="primary"
|
||||
:class="['q-ma-md', 'text-bold', 'text-white']"
|
||||
@click="openAddDialog"
|
||||
>Add New Scene</q-btn
|
||||
>
|
||||
<q-item class="row">
|
||||
<q-item-section :class="['text-black', 'text-bold', 'col-5']">Name</q-item-section>
|
||||
<q-item-section :class="['text-black', 'text-left', 'text-bold', 'text-left']"
|
||||
>Description</q-item-section
|
||||
>
|
||||
</q-item>
|
||||
<q-item
|
||||
v-for="(item, index) in items"
|
||||
:key="item.name"
|
||||
bordered
|
||||
style="border: 0.1px solid lightgray; border-radius: 5px; margin-bottom: 1px"
|
||||
>
|
||||
<q-item-section
|
||||
@click="openLoadDialog(item.name)"
|
||||
:class="['text-black', 'text-left', 'cursor-pointer']"
|
||||
style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis"
|
||||
>{{ item.name }}</q-item-section
|
||||
>
|
||||
<q-item-section
|
||||
@click="openLoadDialog(item.name)"
|
||||
:class="['text-black', 'text-left', 'cursor-pointer']"
|
||||
left
|
||||
style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis"
|
||||
>{{ item.description }}</q-item-section
|
||||
>
|
||||
<q-item-section top side>
|
||||
<div class="text-grey-8 q-gutter-xs">
|
||||
<q-btn size="12px" flat dense round icon="delete" @click="removeScene(item.name)" />
|
||||
<q-btn
|
||||
size="12px"
|
||||
flat
|
||||
dense
|
||||
round
|
||||
icon="more_vert"
|
||||
@click="openEditDialog(item, index)"
|
||||
/>
|
||||
</div>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
<!-- Fallback if list is empty -->
|
||||
<div v-else class="q-pa-md text-grey text-center">
|
||||
<div>No scenes available</div>
|
||||
<q-btn
|
||||
rounded
|
||||
color="primary"
|
||||
:class="['q-ma-md', 'text-bold', 'text-white']"
|
||||
@click="openAddDialog"
|
||||
>Add First Scene</q-btn
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { NotifyDialog } from 'src/composables/notify';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useQuasar } from 'quasar';
|
||||
import type { Scene } from 'src/models/Scene';
|
||||
import type { Set } from 'src/models/Set';
|
||||
import axios from 'axios';
|
||||
import { api } from 'boot/axios';
|
||||
import { NotifyResponse } from 'src/composables/notify';
|
||||
import Dialog from 'src/components/dialog/okDialog.vue';
|
||||
import { setValues } from 'src/services/websocket';
|
||||
const $q = useQuasar();
|
||||
const showDialog = ref(false);
|
||||
const isEdit = ref(false);
|
||||
const isLoad = ref(false);
|
||||
const existsAlready = ref(false);
|
||||
const editIndex = ref(-1);
|
||||
const dialogLabel = ref('');
|
||||
const newScene = ref<Scene>({
|
||||
name: '',
|
||||
movingHead: false,
|
||||
lightBar: false,
|
||||
});
|
||||
|
||||
const items = ref<Scene[]>([]);
|
||||
|
||||
const quasarApi = axios.create({
|
||||
baseURL: `http://localhost:9500`,
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
quasarApi
|
||||
.get('/api/loadScenes')
|
||||
.then((resp) => {
|
||||
if (resp.data) {
|
||||
items.value = resp.data as Scene[];
|
||||
}
|
||||
})
|
||||
.catch((err) => NotifyResponse($q, err.response.data.error, 'error'));
|
||||
});
|
||||
|
||||
function removeScene(name: string) {
|
||||
NotifyDialog($q, 'Delete', 'Do you want to delete scene: ' + name, 'YES', 'NO')
|
||||
.then((res) => {
|
||||
if (res) {
|
||||
items.value = items.value.filter((s) => s.name !== name);
|
||||
quasarApi
|
||||
.delete('/api/deleteScene', {
|
||||
data: { name },
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.data) {
|
||||
NotifyResponse($q, res.data, 'warning');
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
NotifyResponse($q, err.response.data.error, 'error');
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((err) => NotifyResponse($q, err.resp, 'warning'));
|
||||
}
|
||||
function openAddDialog() {
|
||||
isEdit.value = false;
|
||||
dialogLabel.value = 'Add Scene';
|
||||
|
||||
newScene.value = {
|
||||
name: '',
|
||||
movingHead: true,
|
||||
lightBar: true,
|
||||
};
|
||||
showDialog.value = true;
|
||||
}
|
||||
|
||||
function openEditDialog(scene: Scene, index: number) {
|
||||
isEdit.value = true;
|
||||
dialogLabel.value = 'Update Scene';
|
||||
console.log(76, scene);
|
||||
newScene.value = { ...scene };
|
||||
editIndex.value = index;
|
||||
showDialog.value = true;
|
||||
}
|
||||
|
||||
const saveScene = async () => {
|
||||
if (!newScene.value.name) {
|
||||
return;
|
||||
}
|
||||
const exists = items.value.some(
|
||||
(item, index) => item.name === newScene.value.name && index !== editIndex.value,
|
||||
);
|
||||
|
||||
if (exists) {
|
||||
if (isLoad.value) {
|
||||
console.log(44, 'load');
|
||||
const setPaths = <Set[]>[];
|
||||
|
||||
newScene.value.values?.forEach((element) => {
|
||||
setPaths.push({ path: element.path, value: element.value });
|
||||
});
|
||||
|
||||
setValues(setPaths)
|
||||
.then((response) => NotifyResponse($q, response))
|
||||
.catch((err) => console.error(`Failed to load scene ${newScene.value.name}`, err));
|
||||
isLoad.value = false;
|
||||
showDialog.value = false;
|
||||
return;
|
||||
}
|
||||
existsAlready.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (isEdit.value && editIndex.value !== -1) {
|
||||
items.value[editIndex.value] = { ...newScene.value };
|
||||
} else {
|
||||
items.value.push(newScene.value);
|
||||
}
|
||||
|
||||
// Sort alphabetically by scene name
|
||||
items.value.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
const sendValues = [];
|
||||
if (newScene.value.movingHead) {
|
||||
sendValues.push({
|
||||
path: 'MovingHead',
|
||||
query: { depth: 0 },
|
||||
});
|
||||
}
|
||||
|
||||
if (newScene.value.lightBar) {
|
||||
sendValues.push({
|
||||
path: 'LightBar',
|
||||
query: { depth: 0 },
|
||||
});
|
||||
}
|
||||
console.log(33, sendValues);
|
||||
if (sendValues.length > 0) {
|
||||
try {
|
||||
const res = await api.post('/json_data', { get: sendValues });
|
||||
newScene.value.values = res.data.get;
|
||||
} catch (err) {
|
||||
NotifyResponse($q, err as Error, 'error');
|
||||
}
|
||||
} else {
|
||||
newScene.value.values = [];
|
||||
}
|
||||
|
||||
quasarApi
|
||||
.post('/api/saveScene', JSON.stringify(newScene.value))
|
||||
.then((res) => {
|
||||
if (res.data) {
|
||||
NotifyResponse($q, res.data);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
NotifyResponse($q, err.response.data.error, 'error');
|
||||
});
|
||||
|
||||
showDialog.value = false;
|
||||
};
|
||||
|
||||
function openLoadDialog(name: string) {
|
||||
isLoad.value = true;
|
||||
quasarApi
|
||||
.post('/api/loadScene', { name })
|
||||
.then((res) => {
|
||||
if (res.data) {
|
||||
const scene = res.data as Scene;
|
||||
newScene.value = scene;
|
||||
newScene.value.name = name;
|
||||
showDialog.value = true;
|
||||
isEdit.value = true;
|
||||
dialogLabel.value = 'Load Scene';
|
||||
}
|
||||
})
|
||||
.catch((err) => NotifyResponse($q, err.response.data.error, 'error'));
|
||||
}
|
||||
</script>
|
@@ -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;
|
||||
|
@@ -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[];
|
||||
|
@@ -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') },
|
||||
],
|
||||
},
|
||||
|
||||
|
@@ -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<string, (data: Response | undefined) => void>();
|
||||
const lastKnownValues: Record<string, string> = reactive({});
|
||||
//const lastKnownValues: Record<string, string> = 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 });
|
||||
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<Response | undefined> {
|
||||
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<Response | undefined> {
|
||||
return send({ unsubscribe: data });
|
||||
}
|
||||
|
Reference in New Issue
Block a user