-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathflexdash-ctrl.js
138 lines (127 loc) · 5.48 KB
/
flexdash-ctrl.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
// FlexDash ctrl node for Node-RED
// Control some innards of FlexDash, such as config nodes.
// Copyright ©2021-2022 by Thorsten von Eicken, see LICENSE
module.exports = function (RED) {
class flexdashCtrl {
// config: name, fd_container
constructor(config) {
RED.nodes.createNode(this, config)
this.fd = RED.nodes.getNode(config.fd)
if (!this.fd) {
this.warn(`Ctrl node is not associated with any dashboard ${JSON.stringify(config)}`)
return null
}
this.config = config
this.on("close", () => {
RED.plugins.get("flexdash").destroyCtrl(this)
})
this.on("input", msg => {
if (!msg.action || typeof msg.action != "string") {
this.error(`invalid msg.action, must be string identifying action to perform`)
return
}
let kind, filter, getter, setter
if (msg.tab) {
;(kind = "tab"), (filter = node => node.type == "flexdash tab")
getter = this.fd.store.tabByID
setter = this.fd.store.updateTab
} else if (msg.grid) {
kind = "grid"
filter = node => node.type == "flexdash container" && node.config?.kind?.endsWith("Grid")
getter = this.fd.store.gridByID
setter = this.fd.store.updateGrid
} else if (msg.panel) {
kind = "panel"
filter = node => node.type == "flexdash container" && node.config?.kind?.endsWith("Panel")
getter = this.fd.store.widgetByID
setter = this.fd.store.updateWidget
} else {
this.error(`invalid msg, must contain tab, grid or panel`)
return
}
if (typeof msg[kind] != "string") {
this.error(`invalid msg.${kind}, must be string identifying target FlexDash element`)
return
}
// see whether we can identify the target of the message: check against node.id,
// node.name, node.title, node.icon in that order
let target = RED.nodes.getNode(msg[kind]) // shot in the dark: is it a node ID?
if (target) {
if (!target.fd || !filter(target)) {
this.error(`node "${msg[kind]}" is not a FlexDash ${kind}`)
return
}
} else {
// iterate through FlexDash config nodes and see whether something matches uniquely
for (const field of ["name", "title", "icon"]) {
let error = false
RED.plugins.get("flexdash")._forAllContainers(node => {
if (filter(node) && node.config && node.config[field] == msg[kind]) {
if (target) {
this.error(
`the ${field} of multiple FlexDash ${kind}s matches "${msg[kind]}", message ignored`
)
error = true
} else {
target = node
}
}
})
if (error) return
if (target) break
}
if (!target) {
// a warning is probably good here, but it's not necessarily an error
this.warn(`no FlexDash ${kind} with id/name/title/icon "${msg[kind]}" found`)
return
}
}
// perform
switch (msg.action) {
// open/close are ctrl messages that change the browsing state without affecting the config
case "open":
case "close":
if ((kind == "tab" && msg.action == "open") || kind == "grid") {
const payload = { action: msg.action, type: kind, id: target.fd_id }
this.fd._send("ctrl", null, payload, msg._fd_socket)
} else {
this.error(`cannot ${msg.action} a ${kind}`)
}
break
// edit changes the config and as a side-effect changes the browser state
case "edit":
const el = getter(target.fd_id) // get the FlexDash object we're editing
// perform the update
const update = {}
const exclude = ["id", "kind", "static", "dynamic", "dyn_root", "output"]
for (const prop in msg) {
if (prop in el && !exclude.includes(prop)) update[prop] = msg[prop]
}
this.log(`updating ${kind} ${target.fd_id} with ${JSON.stringify(update)}`)
if (update) {
if (msg._fd_socket) {
// implementing this is messy, would have to refactor the code in the store that
// constructs the correct store path so that it can be used without causing a mutation
// until someone has a pertinent use-case for this, punt...
this.error(`msg._fd_socket not supported for action ${msg.action}`)
} else {
setter(fd_id, update)
}
}
break
default:
this.error(`unknown action "${msg.action}"`)
}
})
const ctrl = RED.plugins.get("flexdash").initCtrl(this)
ctrl.onInput((topic, payload, socket) => {
// propagate the payload into the flow and attach the FD socket ID
let msg = { payload, _fd_socket: socket }
if (topic != undefined) msg.topic = topic // FD topic has priority (unused?)
else if (config.fd_output_topic) msg.topic = config.fd_output_topic // optional configured topic
this.send(msg)
})
}
}
RED.nodes.registerType("flexdash ctrl", flexdashCtrl)
}