holy shit it works
This commit is contained in:
parent
7ced5a1662
commit
451b639395
1295
console/package-lock.json
generated
1295
console/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -14,10 +14,16 @@
|
|||||||
"@codemirror/state": "^6.6.0",
|
"@codemirror/state": "^6.6.0",
|
||||||
"@codemirror/theme-one-dark": "^6.1.3",
|
"@codemirror/theme-one-dark": "^6.1.3",
|
||||||
"@codemirror/view": "^6.40.0",
|
"@codemirror/view": "^6.40.0",
|
||||||
|
"@jsonforms/core": "^3.7.0",
|
||||||
|
"@jsonforms/material-renderers": "^3.7.0",
|
||||||
|
"@jsonforms/vanilla-renderers": "^3.7.0",
|
||||||
|
"@jsonforms/vue": "^3.7.0",
|
||||||
|
"@jsonforms/vue-vanilla": "^3.7.0",
|
||||||
"@primeuix/themes": "^1.0.0",
|
"@primeuix/themes": "^1.0.0",
|
||||||
"@primevue/core": "^4.2.5",
|
"@primevue/core": "^4.2.5",
|
||||||
"chart.js": "^4.4.7",
|
"chart.js": "^4.4.7",
|
||||||
"codemirror": "^6.0.2",
|
"codemirror": "^6.0.2",
|
||||||
|
"jsonforms-primevue": "github:kobbejager/jsonforms-primevue",
|
||||||
"mqtt": "^5.15.0",
|
"mqtt": "^5.15.0",
|
||||||
"primeicons": "^7.0.0",
|
"primeicons": "^7.0.0",
|
||||||
"primevue": "^4.2.5",
|
"primevue": "^4.2.5",
|
||||||
|
|||||||
@ -7,24 +7,16 @@ import AccordionPanel from 'primevue/accordionpanel';
|
|||||||
import AccordionHeader from 'primevue/accordionheader';
|
import AccordionHeader from 'primevue/accordionheader';
|
||||||
import AccordionContent from 'primevue/accordioncontent';
|
import AccordionContent from 'primevue/accordioncontent';
|
||||||
|
|
||||||
import { nextTick } from 'vue'
|
|
||||||
|
|
||||||
import {basicSetup} from "codemirror"
|
import { JsonForms } from "@jsonforms/vue";
|
||||||
import { oneDark } from "@codemirror/theme-one-dark"
|
import { defaultStyles, mergeStyles, vanillaRenderers } from "@jsonforms/vue-vanilla";
|
||||||
import { EditorView } from "@codemirror/view"
|
import { primeVueRenderers } from 'jsonforms-primevue'
|
||||||
import { EditorState, StateEffect } from "@codemirror/state"
|
|
||||||
import { useLayout } from "../../composables/useLayout";
|
|
||||||
|
|
||||||
const { isDarkMode } = useLayout();
|
const renderers = Object.freeze([
|
||||||
|
...primeVueRenderers,
|
||||||
|
// here you can add custom renderers
|
||||||
|
]);
|
||||||
|
|
||||||
|
|
||||||
const lightTheme = EditorView.theme({}, { dark: false })
|
|
||||||
const darkTheme = oneDark //EditorView.theme({}, { dark: true })
|
|
||||||
|
|
||||||
import { json } from "@codemirror/lang-json"
|
|
||||||
|
|
||||||
const editorContainers = reactive({})
|
|
||||||
const editors = reactive({})
|
|
||||||
const openTabs = ref([])
|
const openTabs = ref([])
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@ -40,6 +32,8 @@ const commands = ref([])
|
|||||||
|
|
||||||
const commandMap = reactive({})
|
const commandMap = reactive({})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function rebuildList() {
|
function rebuildList() {
|
||||||
commands.value = Object.values(commandMap)
|
commands.value = Object.values(commandMap)
|
||||||
}
|
}
|
||||||
@ -48,9 +42,9 @@ function updateCommand(device, name, field, value) {
|
|||||||
if (!commandMap[name]) {
|
if (!commandMap[name]) {
|
||||||
commandMap[name] = {
|
commandMap[name] = {
|
||||||
name,
|
name,
|
||||||
description: '',
|
description: "No description provided.",
|
||||||
schema: '',
|
schema: {},
|
||||||
input: ''
|
input: {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,78 +56,11 @@ function sendCommand(cmd) {
|
|||||||
|
|
||||||
const topic = `device/${props.deviceId}/command/${cmd.name}`
|
const topic = `device/${props.deviceId}/command/${cmd.name}`
|
||||||
|
|
||||||
mqtt2.publish(topic, cmd.input || '{}')
|
var payload = JSON.stringify(cmd.input, null, 2)
|
||||||
}
|
|
||||||
|
|
||||||
function setEditorRef(name, el) {
|
console.log("Command input", payload)
|
||||||
if (el) {
|
|
||||||
editorContainers[name] = el
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onAccordionChange(value) {
|
|
||||||
|
|
||||||
const opened = Array.isArray(value) ? value : [value]
|
|
||||||
|
|
||||||
opened.forEach(name => {
|
|
||||||
|
|
||||||
if (editors[name]) return
|
|
||||||
|
|
||||||
const cmd = commands.value.find(c => c.name === name)
|
|
||||||
if (!cmd) return
|
|
||||||
|
|
||||||
const el = editorContainers[name]
|
|
||||||
if (!el) return
|
|
||||||
|
|
||||||
nextTick(() => createEditor(cmd, el))
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function createEditor(cmd, el) {
|
|
||||||
if (editors[cmd.name]) return
|
|
||||||
|
|
||||||
const initialDoc =
|
|
||||||
cmd.input ||
|
|
||||||
''
|
|
||||||
|
|
||||||
const state = EditorState.create({
|
|
||||||
doc: initialDoc,
|
|
||||||
extensions: baseExtensions(cmd, isDarkMode.value ? darkTheme : lightTheme)
|
|
||||||
})
|
|
||||||
|
|
||||||
const view = new EditorView({
|
|
||||||
state,
|
|
||||||
parent: el
|
|
||||||
})
|
|
||||||
|
|
||||||
editors[cmd.name] = view
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function baseExtensions(cmd, theme) {
|
|
||||||
return [
|
|
||||||
basicSetup,
|
|
||||||
json(),
|
|
||||||
theme,
|
|
||||||
EditorView.updateListener.of(update => {
|
|
||||||
if (update.docChanged) {
|
|
||||||
cmd.input = update.state.doc.toString()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateEditorTheme() {
|
|
||||||
Object.entries(editors).forEach(([cmd, view]) => {
|
|
||||||
console.log("changing theme of ", view)
|
|
||||||
view.dispatch({
|
|
||||||
effects: StateEffect.reconfigure.of(baseExtensions(cmd, isDarkMode.value ? darkTheme : lightTheme))
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
|
mqtt2.publish(topic, payload || '{}')
|
||||||
}
|
}
|
||||||
|
|
||||||
import Dialog from 'primevue/dialog'
|
import Dialog from 'primevue/dialog'
|
||||||
@ -145,24 +72,13 @@ const schemaDialog = reactive({
|
|||||||
|
|
||||||
function showSchema(cmd) {
|
function showSchema(cmd) {
|
||||||
try {
|
try {
|
||||||
schemaDialog.content = JSON.stringify(JSON.parse(cmd.schema), null, 2)
|
schemaDialog.content = JSON.stringify(cmd.schema, null, 2)
|
||||||
} catch {
|
} catch {
|
||||||
schemaDialog.content = cmd.schema || 'No schema available'
|
schemaDialog.content = cmd.schema || 'No schema available'
|
||||||
}
|
}
|
||||||
schemaDialog.visible = true
|
schemaDialog.visible = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
watch(() => props.deviceId, () => {
|
|
||||||
Object.keys(commandMap).forEach(k => delete commandMap[k])
|
|
||||||
commands.value = []
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(isDarkMode, () => {
|
|
||||||
console.log("Editor mode changed")
|
|
||||||
updateEditorTheme()
|
|
||||||
})
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|
||||||
mqtt2.subscribe('device/+/command/#', (payload, topic) => {
|
mqtt2.subscribe('device/+/command/#', (payload, topic) => {
|
||||||
@ -179,8 +95,8 @@ onMounted(() => {
|
|||||||
updateCommand(device, command, 'description', payload)
|
updateCommand(device, command, 'description', payload)
|
||||||
}
|
}
|
||||||
if (field === 'schema') {
|
if (field === 'schema') {
|
||||||
const pretty = JSON.stringify(JSON.parse(payload), null, 2)
|
const schema = JSON.parse(payload)
|
||||||
updateCommand(device, command, 'schema', pretty)
|
updateCommand(device, command, 'schema', schema)
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
@ -189,12 +105,50 @@ onMounted(() => {
|
|||||||
updateEditorTheme()
|
updateEditorTheme()
|
||||||
})
|
})
|
||||||
|
|
||||||
observer.observe(document.documentElement, {
|
|
||||||
attributes: true,
|
|
||||||
attributeFilter: ['class']
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const schema = {
|
||||||
|
properties: {
|
||||||
|
measRate: {
|
||||||
|
type: "integer",
|
||||||
|
minimum: 50,
|
||||||
|
maximum: 60000,
|
||||||
|
description: "Measurement period in milliseconds"
|
||||||
|
},
|
||||||
|
navRate: {
|
||||||
|
type: "integer",
|
||||||
|
minimum: 1,
|
||||||
|
maximum: 127,
|
||||||
|
description: "Number of measurement cycles per navigation solution"
|
||||||
|
},
|
||||||
|
timeRef: {
|
||||||
|
type: "integer",
|
||||||
|
enum: [
|
||||||
|
0,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
description: "Time reference (0=UTC, 1=GPS)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: [
|
||||||
|
"measRate",
|
||||||
|
"navRate",
|
||||||
|
"timeRef"
|
||||||
|
],
|
||||||
|
additionalProperties: false
|
||||||
|
}
|
||||||
|
|
||||||
|
const jsonFormsConfig = {
|
||||||
|
validationMode: 'ValidateOnTouched',
|
||||||
|
showAllErrors: false,
|
||||||
|
showErrorsOnTouched: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
const onChange = (event) => {
|
||||||
|
console.log(event.data)
|
||||||
|
// state.errors = event.errors || []
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -204,9 +158,9 @@ onMounted(() => {
|
|||||||
:modal="true"
|
:modal="true"
|
||||||
:closable="true"
|
:closable="true"
|
||||||
:style="{width: '400px'}"
|
:style="{width: '400px'}"
|
||||||
>
|
>
|
||||||
<pre>{{ schemaDialog.content }}</pre>
|
<pre>{{ schemaDialog.content }}</pre>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
<div class="layout-card">
|
<div class="layout-card">
|
||||||
<DataView :value="commands">
|
<DataView :value="commands">
|
||||||
<template #header>
|
<template #header>
|
||||||
@ -220,7 +174,7 @@ onMounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
<template #empty> No commands available.</template>
|
<template #empty> No commands available.</template>
|
||||||
<template #list="slotProps">
|
<template #list="slotProps">
|
||||||
<Accordion v-model:value="openTabs" @update:value="onAccordionChange">
|
<Accordion >
|
||||||
|
|
||||||
<AccordionPanel
|
<AccordionPanel
|
||||||
v-for="cmd in slotProps.items"
|
v-for="cmd in slotProps.items"
|
||||||
@ -237,22 +191,29 @@ onMounted(() => {
|
|||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
<div class="command-content">
|
<div class="command-content">
|
||||||
<!-- Left: Code editor -->
|
<!-- Left: Code editor -->
|
||||||
<div class="editor-container" :ref="el => setEditorRef(cmd.name, el)"></div>
|
<JsonForms
|
||||||
|
:data="cmd.input"
|
||||||
|
:schema="cmd.schema"
|
||||||
|
:renderers="renderers"
|
||||||
|
:config="jsonFormsConfig"
|
||||||
|
:onChange="({ data, errors }) => cmd.input = data"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- Right: Buttons -->
|
<!-- Right: Buttons -->
|
||||||
<div class="button-container">
|
<div class="button-container">
|
||||||
<Button
|
<Button
|
||||||
icon="pi pi-question-circle"
|
icon="pi pi-question-circle"
|
||||||
class="p-mb-2"
|
class="p-mb-2"
|
||||||
|
label="Help"
|
||||||
|
severity="secondary"
|
||||||
@click="showSchema(cmd)"
|
@click="showSchema(cmd)"
|
||||||
tooltip="Show schema"
|
tooltip="Show schema"
|
||||||
tooltipOptions="{position:'top'}"
|
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
icon="pi pi-send"
|
icon="pi pi-send"
|
||||||
|
label="Send"
|
||||||
@click="sendCommand(cmd)"
|
@click="sendCommand(cmd)"
|
||||||
tooltip="Show schema"
|
tooltip="Send command"
|
||||||
tooltipOptions="{position:'top'}"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -338,6 +299,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
.command-content {
|
.command-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -347,9 +309,9 @@ onMounted(() => {
|
|||||||
|
|
||||||
.button-container {
|
.button-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: row;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
justify-content: flex-start; /* aligns buttons to the top */
|
justify-content: flex-end; /* aligns buttons to the top */
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
Loading…
Reference in New Issue
Block a user