-
Notifications
You must be signed in to change notification settings - Fork 81
Expand file tree
/
Copy pathdata-tracker.mjs
More file actions
150 lines (134 loc) · 4.78 KB
/
data-tracker.mjs
File metadata and controls
150 lines (134 loc) · 4.78 KB
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
139
140
141
142
143
144
145
146
147
148
149
150
import { inject, onMounted, onUnmounted } from 'vue'
import { useStore } from 'vuex'
// by convention, composable function names start with "use"
export function useDataTracker (widgetId, onInput, onLoad, onDynamicProperties, onSync) {
if (!widgetId) {
throw new Error('widgetId is required')
}
const store = useStore()
/** @type {import('socket.io-client').Socket} */
const socket = inject('$socket')
let emitWidgetLoadOnConnect = false
function checkDynamicProperties (msg) {
// set standard dynamic properties states if passed into msg
if ('enabled' in msg) {
store.commit('ui/widgetState', {
widgetId,
config: {
enabled: msg.enabled
}
})
}
if ('visible' in msg) {
store.commit('ui/widgetState', {
widgetId,
config: {
visible: msg.visible
}
})
}
if ('class' in msg || ('ui_update' in msg && 'class' in msg.ui_update)) {
const cls = msg.class || msg.ui_update?.class
store.commit('ui/widgetState', {
widgetId,
config: {
class: cls
}
})
}
if (onDynamicProperties) {
onDynamicProperties(msg)
}
}
function onWidgetLoad (msg, state) {
// automatic handle state/dynamic updates for ALL widgets
if (state) {
store.commit('ui/widgetState', {
widgetId,
config: state
})
}
// then see if there is custom onLoad functionality to deal with the latest data payloads
if (onLoad) {
onLoad(msg)
} else {
if (msg) {
store.commit('data/bind', {
widgetId,
msg
})
}
}
}
function onWidgetSync (msg) {
// only care about msg.payload here as it's a change of value sync
if (onSync) {
onSync(msg)
} else {
// only need the msg.payload
store.commit('data/bind', {
widgetId,
msg: {
payload: msg.payload
}
})
}
}
function onMsgInput (msg) {
// check for common dynamic properties cross all widget types
checkDynamicProperties(msg)
if (onInput) {
// sometimes we need to have different behaviour
onInput(msg)
} else {
// but most of the time, we just care about the value of msg
store.commit('data/bind', {
widgetId,
msg // TODO: we should sanitise what is stored in the store?
// One way to do this is to permit only keys explicitly listed in the widget's config (default to topic+payload if none are specified)
// A smarter? way to do this is to scan the template for msg.? binds and store only those keys
// For now, we'll just store the whole msg
})
}
}
function onDisconnect () {
// To get a disconnect, we must have previously been connected.
// Set flag to inform onConnect to emit widget-load
emitWidgetLoadOnConnect = true
}
function onConnect () {
// when we unexpectedly disconnect, this is set to true
if (emitWidgetLoadOnConnect) {
emitWidgetLoadOnConnect = false
socket.emit('widget-load', widgetId)
}
}
function removeAllListeners () {
emitWidgetLoadOnConnect = false
socket?.off('disconnect', onDisconnect)
socket?.off('msg-input:' + widgetId, onMsgInput)
socket?.off('widget-load:' + widgetId, onWidgetLoad)
socket?.off('widget-sync:' + widgetId, onWidgetSync)
socket?.off('connect', onConnect)
}
// a composable can also hook into its owner component's
// lifecycle to setup and tear-down side effects.
onMounted(() => {
if (socket && widgetId) {
removeAllListeners()
socket.on('disconnect', onDisconnect)
socket.on('msg-input:' + widgetId, onMsgInput)
socket.on('widget-load:' + widgetId, onWidgetLoad)
// when a widget in a different client has a value change
socket.on('widget-sync:' + widgetId, onWidgetSync)
// When SocketIO connects
socket.on('connect', onConnect)
// let Node-RED know that this widget has loaded
// useful as Node-RED can return (via msg-input) any stored data
socket.emit('widget-load', widgetId)
}
})
onUnmounted(() => {
removeAllListeners()
})
}