@flowfuse/node-red-dashboard 1.33.0 → 1.33.1-215bca8-202610211539.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/nodes/config/locales/en-US/ui_base.html +9 -0
- package/nodes/plugins/vue-editor.html +108 -0
- package/nodes/widgets/locales/en-US/ui_template.json +3 -0
- package/nodes/widgets/ui_template.html +20 -6
- package/package.json +19 -11
- package/resources/vue-editor/vue-script.mjs +148 -0
- package/resources/vue-editor/vue-sfc.mjs +618 -0
- package/dist/apple-touch-icon.png +0 -0
- package/dist/assets/abnfDiagram-N423BO3Z-D6p5Ez-2.js +0 -1
- package/dist/assets/architectureDiagram-T3A2C74G-BxKJJ8QP.js +0 -36
- package/dist/assets/blockDiagram-VBNYF7ZC-DHq5Ey-Y.js +0 -132
- package/dist/assets/c4Diagram-5PPSVZJV-CMejj2Pw.js +0 -10
- package/dist/assets/chunk-2GRJ4B5K-DfKrdElA.js +0 -1
- package/dist/assets/chunk-2Q5K7J3B-Nm9OtfXP.js +0 -1
- package/dist/assets/chunk-5RXB4S5H-90AhyXTK.js +0 -231
- package/dist/assets/chunk-5VM5RSS4-B-TvGvkW.js +0 -15
- package/dist/assets/chunk-6Q2QTUOP-ikWdh2bV.js +0 -88
- package/dist/assets/chunk-GF5L2VYU-Bkx4752U.js +0 -206
- package/dist/assets/chunk-JWPE2WC7-CXOxwsI-.js +0 -1
- package/dist/assets/chunk-KBJHAD2P-CZxnMJoj.js +0 -1
- package/dist/assets/chunk-RYQCIY6F-DQKvWoM9.js +0 -1
- package/dist/assets/chunk-XXDRQBXY-CvLqB_x5.js +0 -1
- package/dist/assets/classDiagram-JCYQIIEL-DzhOke_T.js +0 -1
- package/dist/assets/classDiagram-v2-OCEON4UE-DzhOke_T.js +0 -1
- package/dist/assets/cose-bilkent-JH36ORCC-BRLAqXvh.js +0 -1
- package/dist/assets/cynefin-VYW2F7L2-BRmQaHNR.js +0 -178
- package/dist/assets/cynefinDiagram-MW4NZA55-YeJQ9awi.js +0 -62
- package/dist/assets/cytoscape.esm-D_LviqZs.js +0 -331
- package/dist/assets/dagre-VZM6K2ZE-h6IXMveB.js +0 -4
- package/dist/assets/diagram-7IWD3JNH-BCS0_bvz.js +0 -30
- package/dist/assets/diagram-B4RE2ZJO-HD8yOGXS.js +0 -3
- package/dist/assets/diagram-LBJQPF4R-D0UTsV2t.js +0 -24
- package/dist/assets/diagram-Q27KOJAE-Bmb3LIFV.js +0 -24
- package/dist/assets/diagram-UB23O5K3-DM2d8yVM.js +0 -41
- package/dist/assets/disconnected-BuxohaUu.png +0 -0
- package/dist/assets/ebnfDiagram-BXEA7PRR-DGZG2WqV.js +0 -1
- package/dist/assets/echarts-BB8VCqSB.js +0 -73
- package/dist/assets/erDiagram-JOGREHBK-BmfqN4Ac.js +0 -85
- package/dist/assets/flowDiagram-UKHOOZJN-CJNXFf5I.js +0 -156
- package/dist/assets/ganttDiagram-PKOTCBZU-BxdjClye.js +0 -292
- package/dist/assets/gitGraphDiagram-DS77QQ5N-5YVrNwZn.js +0 -106
- package/dist/assets/graph-C9eacEi8.js +0 -1
- package/dist/assets/index-C-4W3slC.css +0 -13
- package/dist/assets/index-gXtuwxvQ.js +0 -124
- package/dist/assets/infoDiagram-6WML65LV-CTv9fO4U.js +0 -2
- package/dist/assets/ishikawaDiagram-WSZJBQD7-8cPVb_uM.js +0 -70
- package/dist/assets/journeyDiagram-NVQOT4AX-Djlzn218.js +0 -139
- package/dist/assets/kanban-definition-27J2QSJJ-7xuKelm_.js +0 -89
- package/dist/assets/katex-C5jXJg4s.js +0 -257
- package/dist/assets/layout-DEXfKzaS.js +0 -1
- package/dist/assets/logo-DIAzbBuw.png +0 -0
- package/dist/assets/map-Czzmt4hB.js +0 -1
- package/dist/assets/materialdesignicons-webfont-B7mPwVP_.ttf +0 -0
- package/dist/assets/materialdesignicons-webfont-CSr8KVlo.eot +0 -0
- package/dist/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 +0 -0
- package/dist/assets/materialdesignicons-webfont-PXm3-2wK.woff +0 -0
- package/dist/assets/mermaid-DUvIMwef.js +0 -314
- package/dist/assets/mindmap-definition-FAOFIHXS-tb_oy-Th.js +0 -96
- package/dist/assets/pegDiagram-VL7TDLO6-CVSX7i5k.js +0 -1
- package/dist/assets/pieDiagram-7S7Q4E2Y-D1szTwph.js +0 -39
- package/dist/assets/quadrantDiagram-CIZ2JOQS-CBHKJ-Lm.js +0 -7
- package/dist/assets/railroadDiagram-AXF67PYL-CtYWo-kB.js +0 -1
- package/dist/assets/requirementDiagram-LRYGKXZP-C_ClMADK.js +0 -84
- package/dist/assets/sankeyDiagram-W5VNT64P-uSc3f8Ek.js +0 -40
- package/dist/assets/sequenceDiagram-SI44F4Z6-DOatJfyp.js +0 -162
- package/dist/assets/sizeCapture-X5ZJPWSS-BuwX5NSJ.js +0 -1
- package/dist/assets/stateDiagram-OKZ733FA-DOA9ImyT.js +0 -1
- package/dist/assets/stateDiagram-v2-UEYNNEHI-_iiqH1Kh.js +0 -1
- package/dist/assets/swimlanes-SLNWSIFB-pn0ZRURI.js +0 -2
- package/dist/assets/swimlanesDiagram-ULZ7WXOC-B1KWR-ew.js +0 -8
- package/dist/assets/timeline-definition-Z64GVDOM-BMuORx8j.js +0 -120
- package/dist/assets/vennDiagram-T6HMQDX7-CX3BUH0P.js +0 -34
- package/dist/assets/vue-vendor-Bmy9n80X.js +0 -49
- package/dist/assets/wardleyDiagram-T6FBY63Y-BQg29rBi.js +0 -78
- package/dist/assets/workbox-window.prod.es5-BBnX5xw4.js +0 -2
- package/dist/assets/xychartDiagram-ELKLHX3M-XASTxFDn.js +0 -7
- package/dist/favicon.ico +0 -0
- package/dist/favicon.svg +0 -482
- package/dist/index.html +0 -24
- package/dist/logo-512x512.png +0 -0
- package/dist/logo.svg +0 -482
- package/dist/maskable-icon-512x512.png +0 -0
- package/dist/pwa-192x192.png +0 -0
- package/dist/pwa-512x512.png +0 -0
- package/dist/pwa-64x64.png +0 -0
- package/dist/sw.js +0 -2
|
@@ -63,4 +63,13 @@
|
|
|
63
63
|
</ul>
|
|
64
64
|
</dd>
|
|
65
65
|
</dl>
|
|
66
|
+
<dl class="message-properties">
|
|
67
|
+
<dt>
|
|
68
|
+
Notification Display Time
|
|
69
|
+
<span class="property-type">number</span>
|
|
70
|
+
</dt>
|
|
71
|
+
<dd>
|
|
72
|
+
Number of seconds the "Connected" notification is shown after the Dashboard reconnects. Set to 0 to keep it open until dismissed.
|
|
73
|
+
</dd>
|
|
74
|
+
</dl>
|
|
66
75
|
</script>
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
2
|
+
/**
|
|
3
|
+
* Registers a "vue-sfc" language with Node-RED's Monaco code editor, used by ui-template.
|
|
4
|
+
* Up front this only registers the language id and Monaco's HTML language service for it. The tokenizer,
|
|
5
|
+
* Vue/Vuetify providers and Vuetify data are loaded from resources/vue-editor the first time an editor uses it.
|
|
6
|
+
*/
|
|
7
|
+
(function () {
|
|
8
|
+
const PLUGIN_ID = 'ui-template-vue-editor'
|
|
9
|
+
const LANGUAGE_ID = 'vue-sfc'
|
|
10
|
+
const MODULE = '@flowfuse/node-red-dashboard'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Monaco asks MonacoEnvironment.getWorkerUrl/getWorker for a language worker using the language id as the
|
|
14
|
+
* label. Node-RED only knows its built-in labels, so map ours onto the html worker that serves it.
|
|
15
|
+
* Done on first use, when Node-RED has already set up MonacoEnvironment.
|
|
16
|
+
*/
|
|
17
|
+
function shimWorkerLabel () {
|
|
18
|
+
const env = window.MonacoEnvironment
|
|
19
|
+
if (!env || env.vueSfcWorkerShim) {
|
|
20
|
+
return
|
|
21
|
+
}
|
|
22
|
+
for (const fn of ['getWorkerUrl', 'getWorker']) {
|
|
23
|
+
if (typeof env[fn] === 'function') {
|
|
24
|
+
const original = env[fn]
|
|
25
|
+
env[fn] = function (moduleId, label) {
|
|
26
|
+
return original.call(this, moduleId, label === LANGUAGE_ID ? 'html' : label)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
env.vueSfcWorkerShim = true
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Newer Node-RED versions register HTML-like languages itself, including the worker mapping */
|
|
34
|
+
function nodeRedRegistersHtmlLanguages () {
|
|
35
|
+
return typeof RED.editor?.codeEditor?.monaco?.registerHTMLLanguage === 'function'
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function registerHtmlLanguageService (monaco) {
|
|
39
|
+
const options = { data: { useDefaultDataProvider: true } }
|
|
40
|
+
if (nodeRedRegistersHtmlLanguages()) {
|
|
41
|
+
RED.editor.codeEditor.monaco.registerHTMLLanguage(LANGUAGE_ID, options)
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
// Monaco 0.55 moved monaco.languages.html to monaco.html
|
|
45
|
+
const html = monaco.html?.registerHTMLLanguageService ? monaco.html : monaco.languages.html
|
|
46
|
+
if (!html?.registerHTMLLanguageService) {
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
html.registerHTMLLanguageService(LANGUAGE_ID, Object.assign({}, html.htmlDefaults?.options, options))
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Registers the language if Monaco is loaded. Safe to call more than once.
|
|
54
|
+
* @returns {boolean} true when the vue-sfc language is available
|
|
55
|
+
*/
|
|
56
|
+
function ensure () {
|
|
57
|
+
const monaco = window.monaco
|
|
58
|
+
if (!monaco?.languages?.register) {
|
|
59
|
+
return false
|
|
60
|
+
}
|
|
61
|
+
if (monaco.languages.getLanguages().some(l => l.id === LANGUAGE_ID)) {
|
|
62
|
+
return true
|
|
63
|
+
}
|
|
64
|
+
monaco.languages.register({ id: LANGUAGE_ID, aliases: ['Vue', LANGUAGE_ID], extensions: ['.vue'] })
|
|
65
|
+
try {
|
|
66
|
+
registerHtmlLanguageService(monaco)
|
|
67
|
+
} catch (err) {
|
|
68
|
+
console.warn('Dashboard: HTML language service not available for Vue templates', err)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const version = RED.nodes.registry?.getModule?.(MODULE)?.version
|
|
72
|
+
const query = version ? '?v=' + encodeURIComponent(version) : ''
|
|
73
|
+
const base = new URL('resources/' + MODULE + '/vue-editor/', document.baseURI)
|
|
74
|
+
monaco.languages.onLanguage(LANGUAGE_ID, function () {
|
|
75
|
+
if (!nodeRedRegistersHtmlLanguages()) {
|
|
76
|
+
shimWorkerLabel()
|
|
77
|
+
}
|
|
78
|
+
import(new URL('vue-sfc.mjs' + query, base).href)
|
|
79
|
+
.then(m => m.activate(monaco, { dataUrl: new URL('vuetify.json' + query, base).href }))
|
|
80
|
+
.catch(err => console.warn('Dashboard: could not load the Vue editor support', err))
|
|
81
|
+
})
|
|
82
|
+
return true
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* nr-assistant before 0.21.0 only gives ui-template Expert completions and its CodeLens for html,
|
|
87
|
+
* so keep ui-template on html when an older nr-assistant is installed.
|
|
88
|
+
*/
|
|
89
|
+
function assistantSupportsVueSfc () {
|
|
90
|
+
const assistant = RED.plugins.getPlugin('flowfuse-nr-assistant')
|
|
91
|
+
return !assistant || assistant.supportsVueSfc === true
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Registers the language if needed. Safe to call more than once.
|
|
96
|
+
* @returns {boolean} true when ui-template should use the vue-sfc language
|
|
97
|
+
*/
|
|
98
|
+
function useVueSfc () {
|
|
99
|
+
return RED.editor.codeEditor?.editor?.type === 'monaco' && assistantSupportsVueSfc() && ensure()
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
RED.plugins.registerPlugin(PLUGIN_ID, {
|
|
103
|
+
onadd: ensure,
|
|
104
|
+
ensure,
|
|
105
|
+
useVueSfc
|
|
106
|
+
})
|
|
107
|
+
})()
|
|
108
|
+
</script>
|
|
@@ -149,6 +149,14 @@
|
|
|
149
149
|
height: { value: 0 },
|
|
150
150
|
head: { value: '' },
|
|
151
151
|
format: { value: defaultTemplate },
|
|
152
|
+
// number of errors the code editor reported when the edit dialog was last saved (set in oneditsave)
|
|
153
|
+
noerr: {
|
|
154
|
+
value: 0,
|
|
155
|
+
// two parameters, so Node-RED shows the returned message (a one-parameter validator's result is coerced to a boolean)
|
|
156
|
+
validate: function (v, opt) {
|
|
157
|
+
return !v ? true : c_('errors.invalid-template')
|
|
158
|
+
}
|
|
159
|
+
},
|
|
152
160
|
storeOutMessages: { value: true },
|
|
153
161
|
passthru: { value: true },
|
|
154
162
|
resendOnRefresh: { value: true },
|
|
@@ -183,6 +191,11 @@
|
|
|
183
191
|
const that = this
|
|
184
192
|
const $templateScope = $('#node-input-templateScope')
|
|
185
193
|
|
|
194
|
+
// Monaco gets Vue/Vuetify syntax and completions from the ui-template-vue-editor plugin (nodes/plugins/vue-editor.html).
|
|
195
|
+
// ACE, Monaco without the plugin, or an nr-assistant too old for vue-sfc keeps the HTML mode.
|
|
196
|
+
const vueEditor = RED.plugins.getPlugin('ui-template-vue-editor')
|
|
197
|
+
const htmlMode = vueEditor?.useVueSfc?.() ? 'ace/mode/vue-sfc' : 'ace/mode/html'
|
|
198
|
+
|
|
186
199
|
// if this groups parent is a subflow template, set the node-config-input-width and node-config-input-height up
|
|
187
200
|
// as typedInputs and hide the elementSizer (as it doesn't make sense for subflow templates)
|
|
188
201
|
if (RED.nodes.subflow(this.z)) {
|
|
@@ -239,15 +252,15 @@
|
|
|
239
252
|
break
|
|
240
253
|
case 'widget:ui':
|
|
241
254
|
$('#template-row-ui').show()
|
|
242
|
-
that.editor.getSession().setMode(
|
|
255
|
+
that.editor.getSession().setMode(htmlMode)
|
|
243
256
|
break
|
|
244
257
|
case 'widget:page':
|
|
245
258
|
$('#template-row-page').show()
|
|
246
|
-
that.editor.getSession().setMode(
|
|
259
|
+
that.editor.getSession().setMode(htmlMode)
|
|
247
260
|
break
|
|
248
261
|
default:
|
|
249
262
|
$('#template-row-group, #template-row-class').show()
|
|
250
|
-
that.editor.getSession().setMode(
|
|
263
|
+
that.editor.getSession().setMode(htmlMode)
|
|
251
264
|
break
|
|
252
265
|
}
|
|
253
266
|
resize.call(that)
|
|
@@ -255,7 +268,7 @@
|
|
|
255
268
|
|
|
256
269
|
this.editor = RED.editor.createEditor({
|
|
257
270
|
id: 'node-input-format-editor',
|
|
258
|
-
mode:
|
|
271
|
+
mode: htmlMode,
|
|
259
272
|
value: $('#node-input-format').val()
|
|
260
273
|
})
|
|
261
274
|
|
|
@@ -263,7 +276,7 @@
|
|
|
263
276
|
url: 'uitemplates', // where to get the data from
|
|
264
277
|
type: 'ui-template', // the type of object the library is for
|
|
265
278
|
editor: this.editor, // the field name the main text body goes to
|
|
266
|
-
mode:
|
|
279
|
+
mode: htmlMode,
|
|
267
280
|
fields: ['name']
|
|
268
281
|
})
|
|
269
282
|
|
|
@@ -275,7 +288,7 @@
|
|
|
275
288
|
e.preventDefault()
|
|
276
289
|
const value = that.editor.getValue()
|
|
277
290
|
RED.editor.editText({
|
|
278
|
-
mode: $templateScope.val() === 'global' ? 'css' : '
|
|
291
|
+
mode: $templateScope.val() === 'global' ? 'css' : htmlMode.replace('ace/mode/', ''),
|
|
279
292
|
value,
|
|
280
293
|
width: 'Infinity',
|
|
281
294
|
cursor: that.editor.getCursorPosition(),
|
|
@@ -423,6 +436,7 @@
|
|
|
423
436
|
<div class="form-row" style="margin-bottom:0px;">
|
|
424
437
|
<label for="node-input-format" style="width:110px;"><i class="fa fa-copy"></i> <span data-i18n="ui-template.label.template"></span></label>
|
|
425
438
|
<input type="hidden" id="node-input-format">
|
|
439
|
+
<input type="hidden" id="node-input-noerr">
|
|
426
440
|
<button id="node-template-expand-editor" class="red-ui-button red-ui-button-small" style="float:right"><i class="fa fa-expand"></i></button>
|
|
427
441
|
</div>
|
|
428
442
|
<div class="form-row node-text-editor-row" style="display: block;">
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flowfuse/node-red-dashboard",
|
|
3
|
-
"version": "1.33.0",
|
|
3
|
+
"version": "1.33.1-215bca8-202610211539.0",
|
|
4
4
|
"description": "FlowFuse Dashboard - A collection of Node-RED nodes that provide functionality to build your own UI applications (inc. forms, buttons, charts).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"node-red"
|
|
@@ -31,15 +31,17 @@
|
|
|
31
31
|
],
|
|
32
32
|
"files": [
|
|
33
33
|
"dist/*",
|
|
34
|
-
"nodes/*"
|
|
34
|
+
"nodes/*",
|
|
35
|
+
"resources/*"
|
|
35
36
|
],
|
|
36
37
|
"scripts": {
|
|
37
|
-
"build": "vite build",
|
|
38
|
-
"build:dev": "cross-env NODE_ENV=development vite build --mode development",
|
|
38
|
+
"build": "npm run build:vue-editor && vite build",
|
|
39
|
+
"build:dev": "npm run build:vue-editor && cross-env NODE_ENV=development vite build --mode development",
|
|
40
|
+
"build:vue-editor": "node scripts/build-vue-editor-data.js",
|
|
39
41
|
"cy:open": "cypress open",
|
|
40
42
|
"cy:run": "cypress run",
|
|
41
43
|
"cy:server": "node-red -u ./cypress/fixtures/user-dir/ -p 1881",
|
|
42
|
-
"dev": "cross-env NODE_ENV=development vite build --mode development --watch",
|
|
44
|
+
"dev": "npm run build:vue-editor && cross-env NODE_ENV=development vite build --mode development --watch",
|
|
43
45
|
"dev:prod": "vite build --watch",
|
|
44
46
|
"docs:build": "vitepress build docs",
|
|
45
47
|
"docs:dev": "vitepress dev docs",
|
|
@@ -99,7 +101,7 @@
|
|
|
99
101
|
"medium-zoom": "^1.1.0",
|
|
100
102
|
"mermaid": "^11.12.2",
|
|
101
103
|
"mocha": "^12.0.1",
|
|
102
|
-
"node-red": "^4.1.
|
|
104
|
+
"node-red": "^4.1.16",
|
|
103
105
|
"node-red-node-test-helper": "^0.3.5",
|
|
104
106
|
"npm-run-all": "^4.1.5",
|
|
105
107
|
"sass": "^1.96.0",
|
|
@@ -108,9 +110,10 @@
|
|
|
108
110
|
"should-sinon": "^0.0.6",
|
|
109
111
|
"sinon": "^21.0.0",
|
|
110
112
|
"socket.io-client": "^4.8.1",
|
|
113
|
+
"socket.io-parser": "~4.2.6",
|
|
111
114
|
"sort-package-json": "^2.15.1",
|
|
112
115
|
"svgo": "^4.0.0",
|
|
113
|
-
"vite": "^
|
|
116
|
+
"vite": "^6.4.4",
|
|
114
117
|
"vite-plugin-image-optimizer": "^2.0.2",
|
|
115
118
|
"vite-plugin-pwa": "^1.0.0",
|
|
116
119
|
"vitepress": "^1.0.2",
|
|
@@ -156,18 +159,23 @@
|
|
|
156
159
|
"ui-event": "nodes/widgets/ui_event.js",
|
|
157
160
|
"ui-control": "nodes/widgets/ui_control.js",
|
|
158
161
|
"ui-progress": "nodes/widgets/ui_progress.js"
|
|
162
|
+
},
|
|
163
|
+
"plugins": {
|
|
164
|
+
"ui-template-vue-editor": "nodes/plugins/vue-editor.html"
|
|
159
165
|
}
|
|
160
166
|
},
|
|
161
167
|
"overrides": {
|
|
162
|
-
"node-red-admin@4.1.2": {
|
|
163
|
-
"axios": "^1.13.2"
|
|
164
|
-
},
|
|
165
168
|
"nise@5.1.9": {
|
|
166
169
|
"path-to-regexp": "6.3.0"
|
|
167
170
|
},
|
|
168
171
|
"esbuild": ">=0.25.0",
|
|
169
172
|
"cross-spawn": "~7.0.5",
|
|
170
173
|
"js-yaml": ">=5.4.1",
|
|
171
|
-
"tar": ">=7.5.4"
|
|
174
|
+
"tar": ">=7.5.4",
|
|
175
|
+
"vitepress": {
|
|
176
|
+
".": "$vitepress",
|
|
177
|
+
"vite": "$vite"
|
|
178
|
+
},
|
|
179
|
+
"npm@<11.21.0": "11.21.0"
|
|
172
180
|
}
|
|
173
181
|
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/* eslint-env browser */
|
|
2
|
+
/* global RED */
|
|
3
|
+
/**
|
|
4
|
+
* JavaScript and CSS editing for the <script> and <style> blocks of a ui-template (`vue-sfc` language).
|
|
5
|
+
*
|
|
6
|
+
* Node-RED provides the machinery (RED.editor.codeEditor.monaco.registerEmbeddedLanguages, in newer
|
|
7
|
+
* Node-RED versions). This module only adds what is specific to Dashboard: the type of `this`. Dashboard runs a
|
|
8
|
+
* ui-template script in one of two ways (ui/src/widgets/ui-template/UITemplate.vue, ui/src/util/vue.acorn.js):
|
|
9
|
+
* - a script whose only statement is `export default { ... }` is a Vue component; Node-RED's hidden copy
|
|
10
|
+
* of it becomes `export default __uiTemplate({ ... })`, which types `this` in data, computed, methods,
|
|
11
|
+
* watch, mounted and unmounted
|
|
12
|
+
* - any other script runs inside `mounted`, so it is wrapped in a function whose `this` is the component
|
|
13
|
+
* Both see what Dashboard adds to the component: send(), submit(), msg, value, id, props, $socket, ...
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const TYPES_PATH = 'file:///node_modules/@types/node-red-dashboard-ui-template/index.d.ts'
|
|
17
|
+
|
|
18
|
+
/** Type declarations for the ui-template component instance */
|
|
19
|
+
export const UI_TEMPLATE_TYPES = `
|
|
20
|
+
/** Members that Dashboard adds to every ui-template component */
|
|
21
|
+
interface UiTemplateContext {
|
|
22
|
+
/** The ID of this ui-template node, assigned by Node-RED */
|
|
23
|
+
readonly id: string
|
|
24
|
+
/** The configuration of this ui-template node */
|
|
25
|
+
readonly props: { name?: string, templateScope?: string, [key: string]: any }
|
|
26
|
+
/** The last message received by this ui-template node */
|
|
27
|
+
readonly msg: { payload?: any, topic?: string, [key: string]: any }
|
|
28
|
+
/** Get or set \`msg.payload\` of the last message */
|
|
29
|
+
value: any
|
|
30
|
+
/** Send a message to the Node-RED flow. A non-object value is sent as \`msg.payload\`. */
|
|
31
|
+
send(msg: any): void
|
|
32
|
+
/** Send an event to the Node-RED flow, with \`topic\` set to the event type */
|
|
33
|
+
submit(event: Event): void
|
|
34
|
+
/** The socket.io connection to Node-RED */
|
|
35
|
+
readonly $socket: {
|
|
36
|
+
on(event: string, listener: (...args: any[]) => void): any
|
|
37
|
+
off(event: string, listener?: (...args: any[]) => void): any
|
|
38
|
+
emit(event: string, ...args: any[]): any
|
|
39
|
+
[key: string]: any
|
|
40
|
+
}
|
|
41
|
+
/** Last message received by every widget, by widget ID */
|
|
42
|
+
readonly messages: Record<string, any>
|
|
43
|
+
/** Dashboard setup information */
|
|
44
|
+
readonly setup: any
|
|
45
|
+
readonly $dataTracker: any
|
|
46
|
+
/** The Dashboard Vuex store */
|
|
47
|
+
readonly $store: any
|
|
48
|
+
/** The Dashboard router */
|
|
49
|
+
readonly $router: any
|
|
50
|
+
/** The current route */
|
|
51
|
+
readonly $route: any
|
|
52
|
+
/** The Vuetify instance */
|
|
53
|
+
readonly $vuetify: any
|
|
54
|
+
/** The root DOM element of this component */
|
|
55
|
+
readonly $el: any
|
|
56
|
+
/** Elements and components registered with \`ref\` */
|
|
57
|
+
readonly $refs: Record<string, any>
|
|
58
|
+
readonly $data: Record<string, any>
|
|
59
|
+
readonly $parent: any
|
|
60
|
+
readonly $root: any
|
|
61
|
+
readonly $options: Record<string, any>
|
|
62
|
+
/** Trigger a custom event on this component */
|
|
63
|
+
$emit(event: string, ...args: any[]): void
|
|
64
|
+
/** Wait for the next DOM update */
|
|
65
|
+
$nextTick(fn?: () => void): Promise<void>
|
|
66
|
+
/** Watch a reactive property or getter */
|
|
67
|
+
$watch(source: string | (() => any), callback: (value: any, oldValue: any) => void, options?: { immediate?: boolean, deep?: boolean }): () => void
|
|
68
|
+
/** Force this component to re-render */
|
|
69
|
+
$forceUpdate(): void
|
|
70
|
+
}
|
|
71
|
+
type UiTemplateComputed<C> = { [K in keyof C]: C[K] extends (...args: any[]) => infer R ? R : C[K] extends { get(): infer G } ? G : never }
|
|
72
|
+
type UiTemplateInstance<D, C, M> = D & UiTemplateComputed<C> & M & UiTemplateContext
|
|
73
|
+
type UiTemplateWatcher = ((value: any, oldValue: any) => void) | { handler: (value: any, oldValue: any) => void, deep?: boolean, immediate?: boolean }
|
|
74
|
+
/** The options that a ui-template component supports */
|
|
75
|
+
declare function __uiTemplate<D = {}, C = {}, M = {}>(component: {
|
|
76
|
+
name?: string
|
|
77
|
+
data?: (this: UiTemplateContext) => D
|
|
78
|
+
computed?: C & ThisType<UiTemplateInstance<D, C, M>>
|
|
79
|
+
methods?: M & ThisType<UiTemplateInstance<D, C, M>>
|
|
80
|
+
// \`this\` in these comes from the ThisType below; typing it here as well stops TypeScript inferring D, C and M
|
|
81
|
+
watch?: Record<string, UiTemplateWatcher>
|
|
82
|
+
mounted?: () => void
|
|
83
|
+
unmounted?: () => void
|
|
84
|
+
/** Dashboard ignores other options (it logs a warning); allowed here so that one doesn't stop \`this\` being typed */
|
|
85
|
+
[option: string]: any
|
|
86
|
+
} & ThisType<UiTemplateInstance<D, C, M>>): void
|
|
87
|
+
`
|
|
88
|
+
|
|
89
|
+
// whitespace and comments, which Dashboard allows around a component (they aren't statements)
|
|
90
|
+
const GAP = String.raw`(?:\s|\/\/[^\n]*(?:\n|$)|\/\*(?:[^*]|\*(?!\/))*\*\/)*`
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Where a ui-template script is a component: Dashboard treats it as one when its only statement is
|
|
94
|
+
* `export default { ... }`.
|
|
95
|
+
* @param {string} body - the script's code
|
|
96
|
+
* @returns {{ open: number, close: number } | null} offsets (in body) of the object's `{` and `}`
|
|
97
|
+
*/
|
|
98
|
+
export function findComponent (body) {
|
|
99
|
+
const head = new RegExp(`^${GAP}export\\s+default\\s*(?=\\{)`).exec(body)
|
|
100
|
+
const tail = new RegExp(`\\}\\s*;?${GAP}$`).exec(body)
|
|
101
|
+
return head && tail ? { open: head[0].length, close: tail.index } : null
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The `wrap` hook for registerEmbeddedLanguages: text to insert around a script so `this` has its type.
|
|
106
|
+
* @param {string} text - the ui-template
|
|
107
|
+
* @param {{start: number, end: number}} region - the script's code
|
|
108
|
+
* @param {number} index - the script's position in the template
|
|
109
|
+
* @returns {{at: number, text: string}[]} insertions at offsets in the ui-template
|
|
110
|
+
*/
|
|
111
|
+
export function wrapScript (text, region, index) {
|
|
112
|
+
const component = findComponent(text.slice(region.start, region.end))
|
|
113
|
+
if (component) {
|
|
114
|
+
return [
|
|
115
|
+
{ at: region.start + component.open, text: '__uiTemplate(' },
|
|
116
|
+
{ at: region.start + component.close + 1, text: ')' }
|
|
117
|
+
]
|
|
118
|
+
}
|
|
119
|
+
return [
|
|
120
|
+
{ at: region.start, text: `/** @this {UiTemplateContext} */ function __uiTemplateScript${index} () {` },
|
|
121
|
+
{ at: region.end, text: '\n}' }
|
|
122
|
+
]
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Registers Dashboard's script and style support with Node-RED's code editor.
|
|
127
|
+
* @param {object} monaco - the monaco namespace (unused, kept for the same signature as vue-sfc.mjs)
|
|
128
|
+
* @param {object} options
|
|
129
|
+
* @param {string} options.languageId - the ui-template language id (vue-sfc)
|
|
130
|
+
* @returns {boolean} false if this Node-RED doesn't provide registerEmbeddedLanguages
|
|
131
|
+
*/
|
|
132
|
+
export function activate (monaco, { languageId }) {
|
|
133
|
+
const register = RED.editor?.codeEditor?.monaco?.registerEmbeddedLanguages
|
|
134
|
+
if (typeof register !== 'function') {
|
|
135
|
+
return false
|
|
136
|
+
}
|
|
137
|
+
register(languageId, {
|
|
138
|
+
javascript: {
|
|
139
|
+
wrap: wrapScript,
|
|
140
|
+
extraLibs: [{ content: UI_TEMPLATE_TYPES, filePath: TYPES_PATH }],
|
|
141
|
+
hiddenNames: /^__uiTemplate/,
|
|
142
|
+
// list the component's own members before the Vue/Dashboard `$` members
|
|
143
|
+
demotedNames: /^\$/
|
|
144
|
+
},
|
|
145
|
+
css: {}
|
|
146
|
+
})
|
|
147
|
+
return true
|
|
148
|
+
}
|