@aaqu/fromcubes-portal-react 0.1.0-alpha.1 → 0.1.0-alpha.10
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/LICENSE +1 -1
- package/README.md +68 -33
- package/examples/chart-portal-flow.json +74 -0
- package/examples/d3-poland-flow.json +80 -0
- package/examples/sensor-portal-flow.json +2 -2
- package/examples/threejs-portal-flow.json +61 -0
- package/nodes/portal-react.html +553 -166
- package/nodes/portal-react.js +341 -125
- package/package.json +8 -10
- package/nodes/vendor/react-19.production.min.js +0 -55
- package/scripts/bundle-react.js +0 -31
package/nodes/portal-react.html
CHANGED
|
@@ -26,7 +26,10 @@
|
|
|
26
26
|
// Idempotent — safe to call multiple times, only runs setup once.
|
|
27
27
|
function ensureJsxSetup() {
|
|
28
28
|
if (jsxSetupDone) {
|
|
29
|
-
console.log(
|
|
29
|
+
console.log(
|
|
30
|
+
PREFIX,
|
|
31
|
+
"ensureJsxSetup: already done, re-applying compiler+diag only",
|
|
32
|
+
);
|
|
30
33
|
// Always re-apply compiler/diag in case Node-RED reset them
|
|
31
34
|
applyCompilerAndDiag();
|
|
32
35
|
return;
|
|
@@ -36,8 +39,16 @@
|
|
|
36
39
|
var jsDef = monaco.typescript.javascriptDefaults;
|
|
37
40
|
|
|
38
41
|
// Log initial state
|
|
39
|
-
console.log(
|
|
40
|
-
|
|
42
|
+
console.log(
|
|
43
|
+
PREFIX,
|
|
44
|
+
"INITIAL compilerOptions:",
|
|
45
|
+
JSON.stringify(jsDef.getCompilerOptions()),
|
|
46
|
+
);
|
|
47
|
+
console.log(
|
|
48
|
+
PREFIX,
|
|
49
|
+
"INITIAL diagnosticsOptions:",
|
|
50
|
+
JSON.stringify(jsDef.getDiagnosticsOptions()),
|
|
51
|
+
);
|
|
41
52
|
|
|
42
53
|
applyCompilerAndDiag();
|
|
43
54
|
|
|
@@ -45,7 +56,7 @@
|
|
|
45
56
|
var libContent = [
|
|
46
57
|
"declare var React: any;",
|
|
47
58
|
"declare var ReactDOM: any;",
|
|
48
|
-
"declare function useNodeRed(): { data: any; send: (payload: any, topic?: string) => void };",
|
|
59
|
+
"declare function useNodeRed(): { data: any; send: (payload: any, topic?: string) => void; user: { userId?: string; userName?: string; username?: string; email?: string; role?: string; groups?: any[] } | null };",
|
|
49
60
|
].join("\n");
|
|
50
61
|
console.log(PREFIX, "addExtraLib globals.d.ts");
|
|
51
62
|
jsDef.addExtraLib(libContent, "file:///globals.d.ts");
|
|
@@ -134,7 +145,17 @@
|
|
|
134
145
|
});
|
|
135
146
|
}
|
|
136
147
|
}
|
|
137
|
-
console.log(
|
|
148
|
+
console.log(
|
|
149
|
+
PREFIX,
|
|
150
|
+
"TW completion: ctx=" +
|
|
151
|
+
(isClassName ? "className" : "string") +
|
|
152
|
+
" word='" +
|
|
153
|
+
word +
|
|
154
|
+
"', matched=" +
|
|
155
|
+
suggestions.length +
|
|
156
|
+
"/" +
|
|
157
|
+
classes.length,
|
|
158
|
+
);
|
|
138
159
|
return { suggestions: suggestions };
|
|
139
160
|
},
|
|
140
161
|
});
|
|
@@ -142,19 +163,82 @@
|
|
|
142
163
|
// JSX/HTML tag completion — type tag name, Tab → <tag>|</tag>
|
|
143
164
|
console.log(PREFIX, "registering JSX tag completion provider");
|
|
144
165
|
var HTML_TAGS = [
|
|
145
|
-
"div",
|
|
146
|
-
"
|
|
147
|
-
"
|
|
148
|
-
"
|
|
149
|
-
"
|
|
150
|
-
"
|
|
151
|
-
"
|
|
152
|
-
"
|
|
153
|
-
"
|
|
154
|
-
"
|
|
155
|
-
"
|
|
166
|
+
"div",
|
|
167
|
+
"span",
|
|
168
|
+
"p",
|
|
169
|
+
"a",
|
|
170
|
+
"button",
|
|
171
|
+
"input",
|
|
172
|
+
"img",
|
|
173
|
+
"form",
|
|
174
|
+
"label",
|
|
175
|
+
"h1",
|
|
176
|
+
"h2",
|
|
177
|
+
"h3",
|
|
178
|
+
"h4",
|
|
179
|
+
"h5",
|
|
180
|
+
"h6",
|
|
181
|
+
"ul",
|
|
182
|
+
"ol",
|
|
183
|
+
"li",
|
|
184
|
+
"dl",
|
|
185
|
+
"dt",
|
|
186
|
+
"dd",
|
|
187
|
+
"table",
|
|
188
|
+
"thead",
|
|
189
|
+
"tbody",
|
|
190
|
+
"tfoot",
|
|
191
|
+
"tr",
|
|
192
|
+
"th",
|
|
193
|
+
"td",
|
|
194
|
+
"section",
|
|
195
|
+
"article",
|
|
196
|
+
"aside",
|
|
197
|
+
"header",
|
|
198
|
+
"footer",
|
|
199
|
+
"nav",
|
|
200
|
+
"main",
|
|
201
|
+
"strong",
|
|
202
|
+
"em",
|
|
203
|
+
"b",
|
|
204
|
+
"i",
|
|
205
|
+
"u",
|
|
206
|
+
"s",
|
|
207
|
+
"small",
|
|
208
|
+
"mark",
|
|
209
|
+
"code",
|
|
210
|
+
"pre",
|
|
211
|
+
"blockquote",
|
|
212
|
+
"br",
|
|
213
|
+
"hr",
|
|
214
|
+
"wbr",
|
|
215
|
+
"select",
|
|
216
|
+
"option",
|
|
217
|
+
"optgroup",
|
|
218
|
+
"textarea",
|
|
219
|
+
"fieldset",
|
|
220
|
+
"legend",
|
|
221
|
+
"details",
|
|
222
|
+
"summary",
|
|
223
|
+
"dialog",
|
|
224
|
+
"canvas",
|
|
225
|
+
"video",
|
|
226
|
+
"audio",
|
|
227
|
+
"source",
|
|
228
|
+
"picture",
|
|
229
|
+
"figure",
|
|
230
|
+
"figcaption",
|
|
231
|
+
"svg",
|
|
232
|
+
"path",
|
|
233
|
+
"circle",
|
|
234
|
+
"rect",
|
|
235
|
+
"line",
|
|
236
|
+
"g",
|
|
237
|
+
"defs",
|
|
238
|
+
"use",
|
|
239
|
+
"text",
|
|
156
240
|
];
|
|
157
|
-
var VOID_TAGS = new Set(["br","hr","img","input","wbr","source"]);
|
|
241
|
+
var VOID_TAGS = new Set(["br", "hr", "img", "input", "wbr", "source"]);
|
|
158
242
|
|
|
159
243
|
monaco.languages.registerCompletionItemProvider("javascript", {
|
|
160
244
|
triggerCharacters: ["<"],
|
|
@@ -165,12 +249,15 @@
|
|
|
165
249
|
// After "<" — suggest tags
|
|
166
250
|
var afterBracket = before.match(/<([a-zA-Z]*)$/);
|
|
167
251
|
// Bare word at line start or after whitespace/{ — Emmet-like (lower or upper)
|
|
168
|
-
var bareWord =
|
|
252
|
+
var bareWord =
|
|
253
|
+
!afterBracket &&
|
|
254
|
+
before.match(/(?:^|[\s{(,])([a-zA-Z][a-zA-Z0-9]*)$/);
|
|
169
255
|
|
|
170
256
|
if (!afterBracket && !bareWord) return { suggestions: [] };
|
|
171
257
|
|
|
172
258
|
var typed = (afterBracket ? afterBracket[1] : bareWord[1]) || "";
|
|
173
|
-
var replaceStart =
|
|
259
|
+
var replaceStart =
|
|
260
|
+
position.column - typed.length - (afterBracket ? 1 : 0);
|
|
174
261
|
|
|
175
262
|
var range = {
|
|
176
263
|
startLineNumber: position.lineNumber,
|
|
@@ -192,7 +279,8 @@
|
|
|
192
279
|
label: tag,
|
|
193
280
|
kind: monaco.languages.CompletionItemKind.Property,
|
|
194
281
|
insertText: "<" + tag + " $0/>",
|
|
195
|
-
insertTextRules:
|
|
282
|
+
insertTextRules:
|
|
283
|
+
monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
|
196
284
|
range: range,
|
|
197
285
|
detail: "<" + tag + " />",
|
|
198
286
|
sortText: "0" + tag,
|
|
@@ -202,7 +290,8 @@
|
|
|
202
290
|
label: tag,
|
|
203
291
|
kind: monaco.languages.CompletionItemKind.Property,
|
|
204
292
|
insertText: "<" + tag + ">$0</" + tag + ">",
|
|
205
|
-
insertTextRules:
|
|
293
|
+
insertTextRules:
|
|
294
|
+
monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
|
206
295
|
range: range,
|
|
207
296
|
detail: "<" + tag + ">…</" + tag + ">",
|
|
208
297
|
sortText: "0" + tag + "a",
|
|
@@ -211,7 +300,8 @@
|
|
|
211
300
|
label: tag,
|
|
212
301
|
kind: monaco.languages.CompletionItemKind.Property,
|
|
213
302
|
insertText: "<" + tag + " $0/>",
|
|
214
|
-
insertTextRules:
|
|
303
|
+
insertTextRules:
|
|
304
|
+
monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
|
215
305
|
range: range,
|
|
216
306
|
detail: "<" + tag + " />",
|
|
217
307
|
sortText: "0" + tag + "b",
|
|
@@ -220,9 +310,15 @@
|
|
|
220
310
|
}
|
|
221
311
|
|
|
222
312
|
// Custom components from registry (PascalCase)
|
|
223
|
-
var upperMatch =
|
|
224
|
-
|
|
225
|
-
var
|
|
313
|
+
var upperMatch =
|
|
314
|
+
afterBracket && before.match(/<([A-Z][a-zA-Z0-9]*)$/);
|
|
315
|
+
var bareUpper =
|
|
316
|
+
!afterBracket && before.match(/(?:^|[\s{(,])([A-Z][a-zA-Z0-9]*)$/);
|
|
317
|
+
var compTyped = upperMatch
|
|
318
|
+
? upperMatch[1]
|
|
319
|
+
: bareUpper
|
|
320
|
+
? bareUpper[1]
|
|
321
|
+
: "";
|
|
226
322
|
|
|
227
323
|
if (compTyped || (!typed && afterBracket)) {
|
|
228
324
|
// Fetch component names from registry (cached on window)
|
|
@@ -231,13 +327,19 @@
|
|
|
231
327
|
var name = reg[c];
|
|
232
328
|
if (seen.has(name)) continue;
|
|
233
329
|
if (compTyped && name.indexOf(compTyped) !== 0) continue;
|
|
234
|
-
if (
|
|
330
|
+
if (
|
|
331
|
+
!compTyped &&
|
|
332
|
+
typed &&
|
|
333
|
+
name.toLowerCase().indexOf(typed) !== 0
|
|
334
|
+
)
|
|
335
|
+
continue;
|
|
235
336
|
seen.add(name);
|
|
236
337
|
suggestions.push({
|
|
237
338
|
label: name,
|
|
238
339
|
kind: monaco.languages.CompletionItemKind.Class,
|
|
239
340
|
insertText: "<" + name + ">$0</" + name + ">",
|
|
240
|
-
insertTextRules:
|
|
341
|
+
insertTextRules:
|
|
342
|
+
monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
|
241
343
|
range: range,
|
|
242
344
|
detail: "<" + name + ">…</" + name + ">",
|
|
243
345
|
sortText: "0" + name + "a",
|
|
@@ -246,7 +348,8 @@
|
|
|
246
348
|
label: name,
|
|
247
349
|
kind: monaco.languages.CompletionItemKind.Class,
|
|
248
350
|
insertText: "<" + name + " $0/>",
|
|
249
|
-
insertTextRules:
|
|
351
|
+
insertTextRules:
|
|
352
|
+
monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
|
250
353
|
range: range,
|
|
251
354
|
detail: "<" + name + " />",
|
|
252
355
|
sortText: "0" + name + "b",
|
|
@@ -258,7 +361,8 @@
|
|
|
258
361
|
label: compTyped,
|
|
259
362
|
kind: monaco.languages.CompletionItemKind.Class,
|
|
260
363
|
insertText: "<" + compTyped + ">$0</" + compTyped + ">",
|
|
261
|
-
insertTextRules:
|
|
364
|
+
insertTextRules:
|
|
365
|
+
monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
|
262
366
|
range: range,
|
|
263
367
|
detail: "<" + compTyped + ">…</" + compTyped + ">",
|
|
264
368
|
sortText: "0" + compTyped + "a",
|
|
@@ -267,7 +371,8 @@
|
|
|
267
371
|
label: compTyped,
|
|
268
372
|
kind: monaco.languages.CompletionItemKind.Class,
|
|
269
373
|
insertText: "<" + compTyped + " $0/>",
|
|
270
|
-
insertTextRules:
|
|
374
|
+
insertTextRules:
|
|
375
|
+
monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
|
271
376
|
range: range,
|
|
272
377
|
detail: "<" + compTyped + " />",
|
|
273
378
|
sortText: "0" + compTyped + "b",
|
|
@@ -275,7 +380,13 @@
|
|
|
275
380
|
}
|
|
276
381
|
}
|
|
277
382
|
|
|
278
|
-
console.log(
|
|
383
|
+
console.log(
|
|
384
|
+
PREFIX,
|
|
385
|
+
"JSX tag completion: typed='" +
|
|
386
|
+
typed +
|
|
387
|
+
"', suggestions=" +
|
|
388
|
+
suggestions.length,
|
|
389
|
+
);
|
|
279
390
|
return { suggestions: suggestions };
|
|
280
391
|
},
|
|
281
392
|
});
|
|
@@ -296,12 +407,14 @@
|
|
|
296
407
|
var m = line.match(/^(.*)<([a-zA-Z][a-zA-Z0-9]*)>\/<\/\2>(.*)$/);
|
|
297
408
|
var matchStr, tag, prefix;
|
|
298
409
|
if (m) {
|
|
299
|
-
prefix = m[1];
|
|
410
|
+
prefix = m[1];
|
|
411
|
+
tag = m[2];
|
|
300
412
|
matchStr = "<" + tag + ">/</" + tag + ">";
|
|
301
413
|
} else {
|
|
302
414
|
m = line.match(/^(.*)<([a-zA-Z][a-zA-Z0-9]*)\/><\/\2>(.*)$/);
|
|
303
415
|
if (!m) return;
|
|
304
|
-
prefix = m[1];
|
|
416
|
+
prefix = m[1];
|
|
417
|
+
tag = m[2];
|
|
305
418
|
matchStr = "<" + tag + "/></" + tag + ">";
|
|
306
419
|
}
|
|
307
420
|
var startCol = prefix.length + 1;
|
|
@@ -309,15 +422,17 @@
|
|
|
309
422
|
var replacement = "<" + tag + " />";
|
|
310
423
|
var cursorCol = startCol + replacement.length - 2;
|
|
311
424
|
setTimeout(function () {
|
|
312
|
-
editor.executeEdits("self-close-collapse", [
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
425
|
+
editor.executeEdits("self-close-collapse", [
|
|
426
|
+
{
|
|
427
|
+
range: {
|
|
428
|
+
startLineNumber: lineNum,
|
|
429
|
+
endLineNumber: lineNum,
|
|
430
|
+
startColumn: startCol,
|
|
431
|
+
endColumn: endCol,
|
|
432
|
+
},
|
|
433
|
+
text: replacement,
|
|
318
434
|
},
|
|
319
|
-
|
|
320
|
-
}]);
|
|
435
|
+
]);
|
|
321
436
|
editor.setPosition({ lineNumber: lineNum, column: cursorCol });
|
|
322
437
|
}, 0);
|
|
323
438
|
});
|
|
@@ -328,7 +443,11 @@
|
|
|
328
443
|
function refreshComponentNames() {
|
|
329
444
|
$.getJSON("portal-react/registry", function (reg) {
|
|
330
445
|
window.__fcComponentNames = Object.keys(reg);
|
|
331
|
-
console.log(
|
|
446
|
+
console.log(
|
|
447
|
+
PREFIX,
|
|
448
|
+
"component names loaded:",
|
|
449
|
+
window.__fcComponentNames,
|
|
450
|
+
);
|
|
332
451
|
}).fail(function () {
|
|
333
452
|
window.__fcComponentNames = [];
|
|
334
453
|
});
|
|
@@ -343,11 +462,19 @@
|
|
|
343
462
|
console.group(PREFIX + " MARKERS on " + uri.toString());
|
|
344
463
|
markers.forEach(function (m) {
|
|
345
464
|
console.log(
|
|
346
|
-
" code=" +
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
465
|
+
" code=" +
|
|
466
|
+
(m.code || "?") +
|
|
467
|
+
" severity=" +
|
|
468
|
+
m.severity +
|
|
469
|
+
" source=" +
|
|
470
|
+
(m.source || "?") +
|
|
471
|
+
" msg=" +
|
|
472
|
+
m.message +
|
|
473
|
+
" [L" +
|
|
474
|
+
m.startLineNumber +
|
|
475
|
+
":" +
|
|
476
|
+
m.startColumn +
|
|
477
|
+
"]",
|
|
351
478
|
);
|
|
352
479
|
});
|
|
353
480
|
console.groupEnd();
|
|
@@ -379,8 +506,16 @@
|
|
|
379
506
|
console.log(PREFIX, "setDiagnosticsOptions:", JSON.stringify(diagOpts));
|
|
380
507
|
jsDef.setDiagnosticsOptions(diagOpts);
|
|
381
508
|
|
|
382
|
-
console.log(
|
|
383
|
-
|
|
509
|
+
console.log(
|
|
510
|
+
PREFIX,
|
|
511
|
+
"readback compilerOptions:",
|
|
512
|
+
JSON.stringify(jsDef.getCompilerOptions()),
|
|
513
|
+
);
|
|
514
|
+
console.log(
|
|
515
|
+
PREFIX,
|
|
516
|
+
"readback diagnosticsOptions:",
|
|
517
|
+
JSON.stringify(jsDef.getDiagnosticsOptions()),
|
|
518
|
+
);
|
|
384
519
|
}
|
|
385
520
|
|
|
386
521
|
// Exported for oneditprepare to call before model creation
|
|
@@ -391,7 +526,10 @@
|
|
|
391
526
|
window.__fcLoadMonaco = function (cb) {
|
|
392
527
|
console.log(PREFIX, "loadMonaco called, monaco exists:", !!window.monaco);
|
|
393
528
|
if (window.monaco) {
|
|
394
|
-
console.log(
|
|
529
|
+
console.log(
|
|
530
|
+
PREFIX,
|
|
531
|
+
"Monaco already loaded (by Node-RED?), running setup inline",
|
|
532
|
+
);
|
|
395
533
|
ensureJsxSetup();
|
|
396
534
|
cb();
|
|
397
535
|
return;
|
|
@@ -497,7 +635,7 @@
|
|
|
497
635
|
inputs: 0,
|
|
498
636
|
outputs: 0,
|
|
499
637
|
icon: "font-awesome/fa-cube",
|
|
500
|
-
paletteLabel: "component",
|
|
638
|
+
paletteLabel: "fromcubes component",
|
|
501
639
|
label: function () {
|
|
502
640
|
return this.name || this.compName || "component";
|
|
503
641
|
},
|
|
@@ -510,22 +648,32 @@
|
|
|
510
648
|
if (!window.__fcTwClasses) {
|
|
511
649
|
console.log("[FC-Monaco] loading tw-classes...");
|
|
512
650
|
$.getJSON("portal-react/tw-classes", function (classes) {
|
|
513
|
-
console.log(
|
|
651
|
+
console.log(
|
|
652
|
+
"[FC-Monaco] tw-classes loaded, count=" + classes.length,
|
|
653
|
+
);
|
|
514
654
|
window.__fcTwClasses = classes;
|
|
515
655
|
}).fail(function (xhr) {
|
|
516
|
-
console.error(
|
|
656
|
+
console.error(
|
|
657
|
+
"[FC-Monaco] tw-classes FAILED:",
|
|
658
|
+
xhr.status,
|
|
659
|
+
xhr.statusText,
|
|
660
|
+
);
|
|
517
661
|
});
|
|
518
662
|
}
|
|
519
663
|
|
|
520
664
|
window.__fcLoadMonaco(function (failed) {
|
|
521
665
|
if (failed) {
|
|
522
|
-
console.error(
|
|
666
|
+
console.error(
|
|
667
|
+
"[FC-Monaco] COMP: Monaco load failed, using fallback textarea",
|
|
668
|
+
);
|
|
523
669
|
$("#fcc-monaco").hide();
|
|
524
670
|
$("#fcc-fallback").show().val(code);
|
|
525
671
|
return;
|
|
526
672
|
}
|
|
527
673
|
|
|
528
|
-
console.log(
|
|
674
|
+
console.log(
|
|
675
|
+
"[FC-Monaco] COMP: applying JSX defaults before model creation",
|
|
676
|
+
);
|
|
529
677
|
window.__fcApplyJsxDefaults();
|
|
530
678
|
|
|
531
679
|
var compUri = monaco.Uri.parse("file:///fc-comp-" + node.id + ".jsx");
|
|
@@ -535,8 +683,15 @@
|
|
|
535
683
|
console.log("[FC-Monaco] COMP: disposing existing model");
|
|
536
684
|
existingModel.dispose();
|
|
537
685
|
}
|
|
538
|
-
var compModel = monaco.editor.createModel(
|
|
539
|
-
|
|
686
|
+
var compModel = monaco.editor.createModel(
|
|
687
|
+
code,
|
|
688
|
+
"javascript",
|
|
689
|
+
compUri,
|
|
690
|
+
);
|
|
691
|
+
console.log(
|
|
692
|
+
"[FC-Monaco] COMP: model created, language=" +
|
|
693
|
+
compModel.getLanguageId(),
|
|
694
|
+
);
|
|
540
695
|
|
|
541
696
|
compEditorInstance = monaco.editor.create(
|
|
542
697
|
document.getElementById("fcc-monaco"),
|
|
@@ -547,13 +702,27 @@
|
|
|
547
702
|
// Log markers after a short delay (diagnostics are async)
|
|
548
703
|
setTimeout(function () {
|
|
549
704
|
var markers = monaco.editor.getModelMarkers({ resource: compUri });
|
|
550
|
-
console.log(
|
|
705
|
+
console.log(
|
|
706
|
+
"[FC-Monaco] COMP: markers after 500ms, count=" + markers.length,
|
|
707
|
+
);
|
|
551
708
|
markers.forEach(function (m) {
|
|
552
|
-
console.log(
|
|
709
|
+
console.log(
|
|
710
|
+
"[FC-Monaco] COMP marker: code=" + m.code + " msg=" + m.message,
|
|
711
|
+
);
|
|
553
712
|
});
|
|
554
713
|
// Also log current compiler options state
|
|
555
|
-
console.log(
|
|
556
|
-
|
|
714
|
+
console.log(
|
|
715
|
+
"[FC-Monaco] COMP: current compilerOptions:",
|
|
716
|
+
JSON.stringify(
|
|
717
|
+
monaco.typescript.javascriptDefaults.getCompilerOptions(),
|
|
718
|
+
),
|
|
719
|
+
);
|
|
720
|
+
console.log(
|
|
721
|
+
"[FC-Monaco] COMP: current diagnosticsOptions:",
|
|
722
|
+
JSON.stringify(
|
|
723
|
+
monaco.typescript.javascriptDefaults.getDiagnosticsOptions(),
|
|
724
|
+
),
|
|
725
|
+
);
|
|
557
726
|
}, 500);
|
|
558
727
|
});
|
|
559
728
|
},
|
|
@@ -631,6 +800,34 @@
|
|
|
631
800
|
<ul id="fc-tabs" style="min-width:600px;margin-bottom:0;"></ul>
|
|
632
801
|
</div>
|
|
633
802
|
<div id="fc-tabs-content">
|
|
803
|
+
<!-- ── Tab: JSX ── -->
|
|
804
|
+
<div id="fc-tab-jsx" class="fc-tab-pane">
|
|
805
|
+
<div class="form-row node-text-editor-row">
|
|
806
|
+
<div
|
|
807
|
+
id="fc-editor-wrap"
|
|
808
|
+
style="width:100%;height:420px;border:1px solid var(--red-ui-form-input-border-color,#555);border-radius:4px;overflow:hidden;position:relative;"
|
|
809
|
+
>
|
|
810
|
+
<div id="fc-monaco" style="width:100%;height:100%;"></div>
|
|
811
|
+
<textarea
|
|
812
|
+
id="fc-fallback"
|
|
813
|
+
style="display:none;width:100%;height:100%;font-family:'Cascadia Code',Consolas,monospace;font-size:13px;background:#1e1e1e;color:#d4d4d4;border:none;padding:12px;resize:none;"
|
|
814
|
+
></textarea>
|
|
815
|
+
</div>
|
|
816
|
+
</div>
|
|
817
|
+
<div class="form-row" style="display:flex;gap:8px;margin-top:4px;flex-wrap:wrap;">
|
|
818
|
+
<button type="button" class="red-ui-button" id="fc-btn-starter">
|
|
819
|
+
<i class="fa fa-magic"></i> Default
|
|
820
|
+
</button>
|
|
821
|
+
<button type="button" class="red-ui-button" id="fc-btn-components">
|
|
822
|
+
<i class="fa fa-cube"></i> Components
|
|
823
|
+
</button>
|
|
824
|
+
<span style="flex:1"></span>
|
|
825
|
+
<button type="button" class="red-ui-button" id="fc-btn-preview">
|
|
826
|
+
<i class="fa fa-eye"></i> Preview
|
|
827
|
+
</button>
|
|
828
|
+
</div>
|
|
829
|
+
</div>
|
|
830
|
+
|
|
634
831
|
<!-- ── Tab: Properties ── -->
|
|
635
832
|
<div id="fc-tab-props" class="fc-tab-pane" style="display:none;">
|
|
636
833
|
<div class="form-row">
|
|
@@ -641,7 +838,7 @@
|
|
|
641
838
|
<label for="node-input-endpoint"
|
|
642
839
|
><i class="fa fa-globe"></i> Endpoint</label
|
|
643
840
|
>
|
|
644
|
-
<input type="text" id="node-input-endpoint" placeholder="/
|
|
841
|
+
<input type="text" id="node-input-endpoint" placeholder="/fromcubes" />
|
|
645
842
|
<div
|
|
646
843
|
style="font-size:11px;opacity:.5;margin-top:2px;margin-left:105px;"
|
|
647
844
|
id="fc-url-hint"
|
|
@@ -653,41 +850,41 @@
|
|
|
653
850
|
>
|
|
654
851
|
<input type="text" id="node-input-pageTitle" placeholder="Portal" />
|
|
655
852
|
</div>
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
<div id="fc-tab-jsx" class="fc-tab-pane">
|
|
660
|
-
<div class="form-row" style="margin-bottom:0;">
|
|
661
|
-
<div style="font-size:11px;opacity:.6;margin-bottom:4px;">
|
|
662
|
-
<code>useNodeRed()</code> →
|
|
663
|
-
<code>{ data, send }</code> | Components from
|
|
664
|
-
<code>fc-portal-component</code> nodes auto-imported |
|
|
665
|
-
Must export <code><App /></code> |
|
|
666
|
-
<strong>Transpiled server-side at deploy</strong>
|
|
667
|
-
</div>
|
|
668
|
-
</div>
|
|
669
|
-
<div class="form-row node-text-editor-row">
|
|
853
|
+
<div class="form-row node-input-libs-container-row">
|
|
854
|
+
<label style="width:auto;"><i class="fa fa-archive"></i> Modules</label>
|
|
855
|
+
<ol id="node-input-libs-container"></ol>
|
|
670
856
|
<div
|
|
671
|
-
|
|
672
|
-
style="width:100%;height:420px;border:1px solid var(--red-ui-form-input-border-color,#555);border-radius:4px;overflow:hidden;position:relative;"
|
|
857
|
+
style="font-size:11px;opacity:.5;margin-top:4px;margin-left:4px;"
|
|
673
858
|
>
|
|
674
|
-
|
|
675
|
-
<textarea
|
|
676
|
-
id="fc-fallback"
|
|
677
|
-
style="display:none;width:100%;height:100%;font-family:'Cascadia Code',Consolas,monospace;font-size:13px;background:#1e1e1e;color:#d4d4d4;border:none;padding:12px;resize:none;"
|
|
678
|
-
></textarea>
|
|
859
|
+
npm packages to bundle. Node-RED auto-installs them at deploy time.
|
|
679
860
|
</div>
|
|
680
861
|
</div>
|
|
681
|
-
<div class="form-row" style="
|
|
682
|
-
<
|
|
683
|
-
<
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
862
|
+
<div class="form-row" style="margin-top:12px;">
|
|
863
|
+
<label style="width:auto;">
|
|
864
|
+
<input
|
|
865
|
+
type="checkbox"
|
|
866
|
+
id="node-input-showWsStatus"
|
|
867
|
+
style="width:auto;margin:0 8px 0 0;vertical-align:middle;"
|
|
868
|
+
/>
|
|
869
|
+
Show WebSocket status indicator
|
|
870
|
+
</label>
|
|
871
|
+
<div style="font-size:11px;opacity:.5;margin-top:4px;margin-left:4px;">
|
|
872
|
+
Displays a small <em>fromcubes</em> badge in the bottom-right corner
|
|
873
|
+
showing connection state.
|
|
874
|
+
</div>
|
|
875
|
+
</div>
|
|
876
|
+
<div class="form-row" style="margin-top:12px;">
|
|
877
|
+
<label style="width:auto;">
|
|
878
|
+
<input
|
|
879
|
+
type="checkbox"
|
|
880
|
+
id="node-input-portalAuth"
|
|
881
|
+
style="width:auto;margin:0 8px 0 0;vertical-align:middle;"
|
|
882
|
+
/>
|
|
883
|
+
Enable Portal Auth headers
|
|
884
|
+
</label>
|
|
885
|
+
<div style="font-size:11px;opacity:.5;margin-top:4px;margin-left:4px;">
|
|
886
|
+
Read <code>X-Portal-*</code> headers from reverse proxy.
|
|
887
|
+
</div>
|
|
691
888
|
</div>
|
|
692
889
|
</div>
|
|
693
890
|
|
|
@@ -734,7 +931,7 @@
|
|
|
734
931
|
"",
|
|
735
932
|
" return (",
|
|
736
933
|
' <div className="min-h-screen bg-zinc-950 p-8">',
|
|
737
|
-
' <h1 className="text-2xl font-light text-cyan-400 mb-6">
|
|
934
|
+
' <h1 className="text-2xl font-light text-cyan-400 mb-6">fromcubes</h1>',
|
|
738
935
|
' <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">',
|
|
739
936
|
' <StatusCard label="Value" value={d.value ?? "—"} unit="" />',
|
|
740
937
|
" </div>",
|
|
@@ -754,15 +951,18 @@
|
|
|
754
951
|
color: "#61dafb",
|
|
755
952
|
defaults: {
|
|
756
953
|
name: { value: "" },
|
|
757
|
-
endpoint: { value: "/
|
|
758
|
-
pageTitle: { value: "
|
|
954
|
+
endpoint: { value: "/fromcubes", required: true },
|
|
955
|
+
pageTitle: { value: "fromcubes" },
|
|
759
956
|
componentCode: { value: STARTER },
|
|
760
957
|
customHead: { value: "" },
|
|
958
|
+
portalAuth: { value: false },
|
|
959
|
+
showWsStatus: { value: false },
|
|
960
|
+
libs: { value: [] },
|
|
761
961
|
},
|
|
762
962
|
inputs: 1,
|
|
763
963
|
outputs: 1,
|
|
764
964
|
icon: "font-awesome/fa-desktop",
|
|
765
|
-
paletteLabel: "portal
|
|
965
|
+
paletteLabel: "fromcubes portal",
|
|
766
966
|
label: function () {
|
|
767
967
|
return this.name || this.endpoint || "portal react";
|
|
768
968
|
},
|
|
@@ -776,10 +976,16 @@
|
|
|
776
976
|
if (!window.__fcTwClasses) {
|
|
777
977
|
console.log("[FC-Monaco] loading tw-classes...");
|
|
778
978
|
$.getJSON("portal-react/tw-classes", function (classes) {
|
|
779
|
-
console.log(
|
|
979
|
+
console.log(
|
|
980
|
+
"[FC-Monaco] tw-classes loaded, count=" + classes.length,
|
|
981
|
+
);
|
|
780
982
|
window.__fcTwClasses = classes;
|
|
781
983
|
}).fail(function (xhr) {
|
|
782
|
-
console.error(
|
|
984
|
+
console.error(
|
|
985
|
+
"[FC-Monaco] tw-classes FAILED:",
|
|
986
|
+
xhr.status,
|
|
987
|
+
xhr.statusText,
|
|
988
|
+
);
|
|
783
989
|
});
|
|
784
990
|
}
|
|
785
991
|
|
|
@@ -802,16 +1008,39 @@
|
|
|
802
1008
|
|
|
803
1009
|
// URL hint
|
|
804
1010
|
function updateHint() {
|
|
805
|
-
var ep = $("#node-input-endpoint").val() || "/
|
|
806
|
-
|
|
1011
|
+
var ep = $("#node-input-endpoint").val() || "/fromcubes";
|
|
1012
|
+
var root = (RED.settings.httpNodeRoot || "/").replace(/\/$/, "");
|
|
1013
|
+
$("#fc-url-hint").text("Page served at: http://<host>:1880" + root + ep);
|
|
807
1014
|
}
|
|
808
1015
|
$("#node-input-endpoint").on("input", updateHint);
|
|
809
1016
|
updateHint();
|
|
810
1017
|
|
|
1018
|
+
// Modules editableList (like function node's libs)
|
|
1019
|
+
var libsList = node.libs || [];
|
|
1020
|
+
$("#node-input-libs-container").css("min-height","68px").editableList({
|
|
1021
|
+
addItem: function(container, i, opt) {
|
|
1022
|
+
var lib = opt || {};
|
|
1023
|
+
var row = $('<div/>',{style:"display:flex;gap:8px;align-items:center;"}).appendTo(container);
|
|
1024
|
+
var modInput = $('<input/>',{type:"text",placeholder:"e.g. chart.js/auto@^4.4.0",style:"flex:1;"}).appendTo(row);
|
|
1025
|
+
var varInput = $('<input/>',{type:"text",placeholder:"Import as (e.g. Chart)",style:"width:140px;"}).appendTo(row);
|
|
1026
|
+
modInput.val(lib.module || "");
|
|
1027
|
+
varInput.val(lib.var || "");
|
|
1028
|
+
container.data("mod", modInput);
|
|
1029
|
+
container.data("var", varInput);
|
|
1030
|
+
},
|
|
1031
|
+
removable: true,
|
|
1032
|
+
sortable: true
|
|
1033
|
+
});
|
|
1034
|
+
libsList.forEach(function(lib) {
|
|
1035
|
+
$("#node-input-libs-container").editableList("addItem", lib);
|
|
1036
|
+
});
|
|
1037
|
+
|
|
811
1038
|
// Monaco
|
|
812
1039
|
window.__fcLoadMonaco(function (failed) {
|
|
813
1040
|
if (failed) {
|
|
814
|
-
console.error(
|
|
1041
|
+
console.error(
|
|
1042
|
+
"[FC-Monaco] PORTAL: Monaco load failed, using fallback textarea",
|
|
1043
|
+
);
|
|
815
1044
|
$("#fc-monaco").hide();
|
|
816
1045
|
$("#fc-fallback").show().val(code);
|
|
817
1046
|
$("#fc-head-monaco").hide();
|
|
@@ -819,11 +1048,15 @@
|
|
|
819
1048
|
return;
|
|
820
1049
|
}
|
|
821
1050
|
|
|
822
|
-
console.log(
|
|
1051
|
+
console.log(
|
|
1052
|
+
"[FC-Monaco] PORTAL: applying JSX defaults before model creation",
|
|
1053
|
+
);
|
|
823
1054
|
window.__fcApplyJsxDefaults();
|
|
824
1055
|
var opts = window.__fcEditorOpts;
|
|
825
1056
|
|
|
826
|
-
var jsxUri = monaco.Uri.parse(
|
|
1057
|
+
var jsxUri = monaco.Uri.parse(
|
|
1058
|
+
"file:///fc-portal-" + node.id + ".jsx",
|
|
1059
|
+
);
|
|
827
1060
|
console.log("[FC-Monaco] PORTAL: JSX model URI=" + jsxUri.toString());
|
|
828
1061
|
var existingJsx = monaco.editor.getModel(jsxUri);
|
|
829
1062
|
if (existingJsx) {
|
|
@@ -831,15 +1064,21 @@
|
|
|
831
1064
|
existingJsx.dispose();
|
|
832
1065
|
}
|
|
833
1066
|
var jsxModel = monaco.editor.createModel(code, "javascript", jsxUri);
|
|
834
|
-
console.log(
|
|
1067
|
+
console.log(
|
|
1068
|
+
"[FC-Monaco] PORTAL: JSX model created, language=" +
|
|
1069
|
+
jsxModel.getLanguageId(),
|
|
1070
|
+
);
|
|
835
1071
|
editorInstance = monaco.editor.create(
|
|
836
1072
|
document.getElementById("fc-monaco"),
|
|
837
1073
|
Object.assign({ model: jsxModel }, opts),
|
|
838
1074
|
);
|
|
839
1075
|
console.log("[FC-Monaco] PORTAL: JSX editor created");
|
|
840
|
-
if (window.__fcAttachSelfClose)
|
|
1076
|
+
if (window.__fcAttachSelfClose)
|
|
1077
|
+
window.__fcAttachSelfClose(editorInstance);
|
|
841
1078
|
|
|
842
|
-
var headUri = monaco.Uri.parse(
|
|
1079
|
+
var headUri = monaco.Uri.parse(
|
|
1080
|
+
"file:///fc-head-" + node.id + ".html",
|
|
1081
|
+
);
|
|
843
1082
|
var existingHead = monaco.editor.getModel(headUri);
|
|
844
1083
|
if (existingHead) existingHead.dispose();
|
|
845
1084
|
var headModel = monaco.editor.createModel(headCode, "html", headUri);
|
|
@@ -852,77 +1091,173 @@
|
|
|
852
1091
|
// Log markers after a short delay (diagnostics are async)
|
|
853
1092
|
setTimeout(function () {
|
|
854
1093
|
var markers = monaco.editor.getModelMarkers({ resource: jsxUri });
|
|
855
|
-
console.log(
|
|
1094
|
+
console.log(
|
|
1095
|
+
"[FC-Monaco] PORTAL: markers after 500ms, count=" +
|
|
1096
|
+
markers.length,
|
|
1097
|
+
);
|
|
856
1098
|
markers.forEach(function (m) {
|
|
857
|
-
console.log(
|
|
1099
|
+
console.log(
|
|
1100
|
+
"[FC-Monaco] PORTAL marker: code=" +
|
|
1101
|
+
m.code +
|
|
1102
|
+
" severity=" +
|
|
1103
|
+
m.severity +
|
|
1104
|
+
" msg=" +
|
|
1105
|
+
m.message,
|
|
1106
|
+
);
|
|
858
1107
|
});
|
|
859
|
-
console.log(
|
|
860
|
-
|
|
1108
|
+
console.log(
|
|
1109
|
+
"[FC-Monaco] PORTAL: current compilerOptions:",
|
|
1110
|
+
JSON.stringify(
|
|
1111
|
+
monaco.typescript.javascriptDefaults.getCompilerOptions(),
|
|
1112
|
+
),
|
|
1113
|
+
);
|
|
1114
|
+
console.log(
|
|
1115
|
+
"[FC-Monaco] PORTAL: current diagnosticsOptions:",
|
|
1116
|
+
JSON.stringify(
|
|
1117
|
+
monaco.typescript.javascriptDefaults.getDiagnosticsOptions(),
|
|
1118
|
+
),
|
|
1119
|
+
);
|
|
861
1120
|
}, 500);
|
|
862
1121
|
});
|
|
863
1122
|
|
|
864
1123
|
// Buttons
|
|
865
1124
|
$("#fc-btn-starter").on("click", function () {
|
|
866
|
-
|
|
867
|
-
|
|
1125
|
+
$("<div>Replace current JSX with default starter code?</div>").dialog({
|
|
1126
|
+
title: "Load Default",
|
|
1127
|
+
modal: true,
|
|
1128
|
+
width: 360,
|
|
1129
|
+
buttons: [
|
|
1130
|
+
{ text: "Cancel", click: function () { $(this).dialog("close"); } },
|
|
1131
|
+
{ text: "Replace", class: "primary", click: function () {
|
|
1132
|
+
if (editorInstance) editorInstance.setValue(STARTER);
|
|
1133
|
+
else $("#fc-fallback").val(STARTER);
|
|
1134
|
+
$(this).dialog("close");
|
|
1135
|
+
}},
|
|
1136
|
+
],
|
|
1137
|
+
close: function () { $(this).remove(); },
|
|
1138
|
+
});
|
|
868
1139
|
});
|
|
869
1140
|
|
|
870
1141
|
$("#fc-btn-preview").on("click", function () {
|
|
871
1142
|
var ep = $("#node-input-endpoint").val();
|
|
872
|
-
|
|
1143
|
+
var root = (RED.settings.httpNodeRoot || "/").replace(/\/$/, "");
|
|
1144
|
+
if (ep) window.open(root + ep, "_blank");
|
|
873
1145
|
});
|
|
874
1146
|
|
|
875
1147
|
$("#fc-btn-components").on("click", function () {
|
|
876
1148
|
$.getJSON("portal-react/registry", function (reg) {
|
|
877
|
-
var names = Object.keys(reg);
|
|
1149
|
+
var names = Object.keys(reg).sort();
|
|
878
1150
|
if (!names.length) {
|
|
879
1151
|
RED.notify("No component nodes on canvas.", "warning");
|
|
880
1152
|
return;
|
|
881
1153
|
}
|
|
882
|
-
|
|
1154
|
+
|
|
1155
|
+
function extractProps(code) {
|
|
1156
|
+
if (!code) return [];
|
|
1157
|
+
var m = code.match(/function\s+\w+\s*\(\s*\{([^}]*)\}/);
|
|
1158
|
+
if (!m) return [];
|
|
1159
|
+
return m[1].split(",").map(function (s) { return s.trim().split(/\s*=\s*/)[0]; }).filter(Boolean);
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
var html =
|
|
1163
|
+
'<div style="display:flex;flex-direction:column;height:100%;overflow:hidden;">' +
|
|
1164
|
+
'<input type="text" id="fc-comp-search" placeholder="Search..." ' +
|
|
1165
|
+
'style="width:100%;box-sizing:border-box;margin-bottom:6px;padding:5px 8px;border:1px solid rgba(128,128,128,.4);border-radius:3px;' +
|
|
1166
|
+
'background:var(--red-ui-form-input-background-color,#fff);color:var(--red-ui-form-text-color,#333);font-size:12px;flex-shrink:0;" />' +
|
|
1167
|
+
'<div id="fc-comp-list" style="flex:1;overflow-y:auto;">';
|
|
1168
|
+
|
|
883
1169
|
names.forEach(function (n) {
|
|
884
1170
|
var c = reg[n];
|
|
1171
|
+
var props = extractProps(c.code);
|
|
1172
|
+
var detailParts = [];
|
|
1173
|
+
if (props.length) {
|
|
1174
|
+
detailParts.push('<div style="margin:4px 0 0 0;font-size:11px;opacity:.6;">' +
|
|
1175
|
+
props.map(function (p) {
|
|
1176
|
+
return '<code style="background:rgba(128,128,128,.15);padding:0 4px;border-radius:2px;margin-right:3px;">' + p + '</code>';
|
|
1177
|
+
}).join("") + '</div>');
|
|
1178
|
+
}
|
|
1179
|
+
var io = [];
|
|
1180
|
+
if ((c.inputs || []).length) io.push("in: " + c.inputs.join(", "));
|
|
1181
|
+
if ((c.outputs || []).length) io.push("out: " + c.outputs.join(", "));
|
|
1182
|
+
if (io.length) {
|
|
1183
|
+
detailParts.push('<div style="font-size:10px;opacity:.4;margin-top:2px;">' + io.join(" • ") + '</div>');
|
|
1184
|
+
}
|
|
1185
|
+
var hasDetail = detailParts.length > 0;
|
|
1186
|
+
|
|
885
1187
|
html +=
|
|
886
|
-
'<div
|
|
887
|
-
'
|
|
888
|
-
|
|
889
|
-
'">' +
|
|
890
|
-
n +
|
|
891
|
-
|
|
892
|
-
'
|
|
893
|
-
|
|
894
|
-
"] out:[" +
|
|
895
|
-
(c.outputs || []).join(",") +
|
|
896
|
-
"]</span></div>";
|
|
1188
|
+
'<div class="fc-comp-item" data-name="' + n + '" data-search="' + n.toLowerCase() + '" ' +
|
|
1189
|
+
'style="border-bottom:1px solid rgba(128,128,128,.1);">' +
|
|
1190
|
+
'<div style="display:flex;align-items:center;padding:4px 6px;cursor:pointer;" class="fc-comp-row">' +
|
|
1191
|
+
(hasDetail ? '<i class="fa fa-caret-right fc-comp-arrow" style="width:14px;opacity:.4;font-size:12px;transition:transform .15s;"></i>' : '<span style="width:14px;"></span>') +
|
|
1192
|
+
'<span class="fc-comp-name" data-name="' + n + '" style="font-weight:600;font-size:12px;flex:1;">' + n + '</span>' +
|
|
1193
|
+
'</div>' +
|
|
1194
|
+
(hasDetail ? '<div class="fc-comp-detail" style="display:none;padding:0 6px 4px 20px;">' + detailParts.join("") + '</div>' : '') +
|
|
1195
|
+
'</div>';
|
|
897
1196
|
});
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
1197
|
+
html += '</div></div>';
|
|
1198
|
+
|
|
1199
|
+
var $dlg = $("<div></div>").html(html).dialog({
|
|
1200
|
+
title: "Components",
|
|
1201
|
+
modal: true,
|
|
1202
|
+
width: 380,
|
|
1203
|
+
buttons: [
|
|
1204
|
+
{ text: "Close", click: function () { $(this).dialog("close"); } },
|
|
1205
|
+
],
|
|
1206
|
+
close: function () { $(this).remove(); },
|
|
1207
|
+
});
|
|
1208
|
+
|
|
1209
|
+
// Search
|
|
1210
|
+
$dlg.find("#fc-comp-search").on("input", function () {
|
|
1211
|
+
var q = $(this).val().toLowerCase();
|
|
1212
|
+
$dlg.find(".fc-comp-item").each(function () {
|
|
1213
|
+
$(this).toggle($(this).data("search").indexOf(q) !== -1);
|
|
915
1214
|
});
|
|
916
|
-
|
|
917
|
-
|
|
1215
|
+
}).focus();
|
|
1216
|
+
|
|
1217
|
+
// Hover
|
|
1218
|
+
$dlg.on("mouseenter", ".fc-comp-row", function () {
|
|
1219
|
+
$(this).css("background", "rgba(128,128,128,.1)");
|
|
1220
|
+
}).on("mouseleave", ".fc-comp-row", function () {
|
|
1221
|
+
$(this).css("background", "");
|
|
1222
|
+
});
|
|
1223
|
+
|
|
1224
|
+
// Arrow toggle detail
|
|
1225
|
+
$dlg.on("click", ".fc-comp-arrow", function (e) {
|
|
1226
|
+
e.stopPropagation();
|
|
1227
|
+
var $item = $(this).closest(".fc-comp-item");
|
|
1228
|
+
var $detail = $item.find(".fc-comp-detail");
|
|
1229
|
+
var open = $detail.is(":visible");
|
|
1230
|
+
$detail.slideToggle(100);
|
|
1231
|
+
$(this).css("transform", open ? "" : "rotate(90deg)");
|
|
1232
|
+
});
|
|
1233
|
+
|
|
1234
|
+
// Click name: delete selection, insert <Tag></Tag> in one line, cursor between tags
|
|
1235
|
+
$dlg.on("click", ".fc-comp-name", function () {
|
|
1236
|
+
var name = $(this).data("name");
|
|
1237
|
+
var openTag = "<" + name + ">";
|
|
1238
|
+
var closeTag = "</" + name + ">";
|
|
1239
|
+
var text = openTag + closeTag;
|
|
1240
|
+
$dlg.dialog("close");
|
|
918
1241
|
if (editorInstance) {
|
|
919
|
-
editorInstance.
|
|
920
|
-
|
|
1242
|
+
var sel = editorInstance.getSelection();
|
|
1243
|
+
var startLine = sel.startLineNumber;
|
|
1244
|
+
var startCol = sel.startColumn;
|
|
1245
|
+
editorInstance.executeEdits("fc-components", [{
|
|
1246
|
+
range: sel,
|
|
1247
|
+
text: text,
|
|
1248
|
+
}]);
|
|
1249
|
+
setTimeout(function () {
|
|
1250
|
+
editorInstance.setPosition({ lineNumber: startLine, column: startCol + openTag.length });
|
|
1251
|
+
editorInstance.focus();
|
|
1252
|
+
}, 50);
|
|
921
1253
|
} else {
|
|
922
1254
|
var ta = $("#fc-fallback")[0];
|
|
923
|
-
var
|
|
924
|
-
|
|
925
|
-
|
|
1255
|
+
var s = ta.selectionStart, e = ta.selectionEnd, v = ta.value;
|
|
1256
|
+
ta.value = v.slice(0, s) + text + v.slice(e);
|
|
1257
|
+
setTimeout(function () {
|
|
1258
|
+
ta.selectionStart = ta.selectionEnd = s + openTag.length;
|
|
1259
|
+
ta.focus();
|
|
1260
|
+
}, 50);
|
|
926
1261
|
}
|
|
927
1262
|
});
|
|
928
1263
|
});
|
|
@@ -931,6 +1266,19 @@
|
|
|
931
1266
|
|
|
932
1267
|
oneditsave: function () {
|
|
933
1268
|
console.log("[FC-Monaco] PORTAL oneditsave");
|
|
1269
|
+
|
|
1270
|
+
// Collect libs from editableList
|
|
1271
|
+
var libs = [];
|
|
1272
|
+
var items = $("#node-input-libs-container").editableList("items");
|
|
1273
|
+
items.each(function() {
|
|
1274
|
+
var mod = $(this).data("mod").val().trim();
|
|
1275
|
+
var v = $(this).data("var").val().trim();
|
|
1276
|
+
if (mod) {
|
|
1277
|
+
libs.push({ module: mod, var: v });
|
|
1278
|
+
}
|
|
1279
|
+
});
|
|
1280
|
+
this.libs = libs;
|
|
1281
|
+
|
|
934
1282
|
var code = editorInstance
|
|
935
1283
|
? editorInstance.getValue()
|
|
936
1284
|
: $("#fc-fallback").val();
|
|
@@ -997,15 +1345,20 @@
|
|
|
997
1345
|
Help
|
|
998
1346
|
============================================================ -->
|
|
999
1347
|
<script type="text/html" data-help-name="portal-react">
|
|
1348
|
+
<h3>Quick Reference</h3>
|
|
1349
|
+
<ul>
|
|
1350
|
+
<li><code>useNodeRed()</code> → <code>{ data, send, user }</code></li>
|
|
1351
|
+
<li>Components from <code>fc-portal-component</code> nodes are auto-imported</li>
|
|
1352
|
+
<li>Must export <code><App /></code></li>
|
|
1353
|
+
<li>JSX is transpiled server-side at deploy</li>
|
|
1354
|
+
</ul>
|
|
1355
|
+
|
|
1000
1356
|
<p>
|
|
1001
|
-
Renders a React application on a configurable HTTP endpoint with live
|
|
1002
|
-
WebSocket data binding.
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
JSX is transpiled <strong>server-side at deploy time</strong> using Sucrase.
|
|
1007
|
-
The browser receives plain JS — no Babel, no runtime compilation. Page
|
|
1008
|
-
weight: ~45 KB (React production).
|
|
1357
|
+
Renders a React 19 application on a configurable HTTP endpoint with live
|
|
1358
|
+
WebSocket data binding. JSX is transpiled <strong>server-side at deploy
|
|
1359
|
+
time</strong> using esbuild — browsers receive pre-compiled JS with zero
|
|
1360
|
+
runtime compilation. Tailwind CSS 4 utility classes are generated
|
|
1361
|
+
server-side and served as static CSS.
|
|
1009
1362
|
</p>
|
|
1010
1363
|
|
|
1011
1364
|
<h3>Inputs</h3>
|
|
@@ -1013,21 +1366,36 @@
|
|
|
1013
1366
|
<code>msg.payload</code> is pushed to all connected clients via WebSocket.
|
|
1014
1367
|
</p>
|
|
1015
1368
|
<pre>
|
|
1016
|
-
const { data, send } = useNodeRed();
|
|
1017
|
-
// data = last msg.payload
|
|
1369
|
+
const { data, send, user } = useNodeRed();
|
|
1370
|
+
// data = last msg.payload (reactive)
|
|
1371
|
+
// send(payload, topic?) — emit msg on output wire
|
|
1372
|
+
// user = portal auth data or null</pre
|
|
1018
1373
|
>
|
|
1019
1374
|
|
|
1020
1375
|
<h3>Outputs</h3>
|
|
1021
1376
|
<p>
|
|
1022
1377
|
<code>send(payload, topic?)</code> emits a <code>msg</code> on the node's
|
|
1023
|
-
output wire.
|
|
1378
|
+
output wire via WebSocket.
|
|
1379
|
+
</p>
|
|
1380
|
+
|
|
1381
|
+
<h3>npm Packages</h3>
|
|
1382
|
+
<p>
|
|
1383
|
+
Add npm packages in the <strong>Modules</strong> list (e.g.
|
|
1384
|
+
<code>chart.js/auto@^4.4.0</code>, <code>d3</code>, <code>three</code>).
|
|
1385
|
+
Node-RED auto-installs them at deploy time. All packages are bundled with
|
|
1386
|
+
React into a single vendor IIFE via esbuild, cached by hash of installed
|
|
1387
|
+
versions. Use them in JSX via standard imports.
|
|
1024
1388
|
</p>
|
|
1025
1389
|
|
|
1026
1390
|
<h3>Deploy behavior</h3>
|
|
1027
1391
|
<ul>
|
|
1028
1392
|
<li>
|
|
1029
|
-
Each deploy re-transpiles JSX
|
|
1030
|
-
instant)
|
|
1393
|
+
Each deploy re-transpiles JSX via esbuild and rebuilds Tailwind CSS
|
|
1394
|
+
(both cached by content hash — unchanged code is instant)
|
|
1395
|
+
</li>
|
|
1396
|
+
<li>
|
|
1397
|
+
Vendor bundle (React + npm packages) rebuilt only when package versions
|
|
1398
|
+
change
|
|
1031
1399
|
</li>
|
|
1032
1400
|
<li>
|
|
1033
1401
|
Active WebSocket clients receive close code 1001 and auto-reconnect
|
|
@@ -1045,15 +1413,34 @@ const { data, send } = useNodeRed();
|
|
|
1045
1413
|
<p>
|
|
1046
1414
|
Components defined in <strong>fc-portal-component</strong> nodes on the
|
|
1047
1415
|
canvas are auto-injected into every portal-react page. Use them as JSX tags
|
|
1048
|
-
by their component name.
|
|
1416
|
+
by their component name — no imports needed.
|
|
1049
1417
|
</p>
|
|
1050
1418
|
|
|
1419
|
+
<h3>Portal Auth</h3>
|
|
1420
|
+
<p>
|
|
1421
|
+
When enabled in the Auth tab, reads <code>X-Portal-*</code> headers set by
|
|
1422
|
+
a reverse proxy (e.g. Nginx) and exposes user data:
|
|
1423
|
+
</p>
|
|
1424
|
+
<ul>
|
|
1425
|
+
<li><code>useNodeRed()</code> returns <code>{ data, send, user }</code></li>
|
|
1426
|
+
<li>
|
|
1427
|
+
Messages from WebSocket include <code>msg._client</code> with user info
|
|
1428
|
+
</li>
|
|
1429
|
+
</ul>
|
|
1430
|
+
|
|
1051
1431
|
<h3>Custom Head HTML</h3>
|
|
1052
1432
|
<p>
|
|
1053
1433
|
Inject CDN links, fonts, or extra stylesheets into
|
|
1054
1434
|
<code><head></code>. Example:
|
|
1055
1435
|
</p>
|
|
1056
1436
|
<pre>
|
|
1057
|
-
<link href="https://fonts.googleapis.com/css2?family=Inter&display=swap" rel="stylesheet"></pre
|
|
1437
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter&display=swap" rel="stylesheet"></pre
|
|
1058
1438
|
>
|
|
1439
|
+
|
|
1440
|
+
<h3>WebSocket Status</h3>
|
|
1441
|
+
<p>
|
|
1442
|
+
Optional <em>fromcubes</em> badge in the bottom-right corner showing
|
|
1443
|
+
connection state. Enable via the <strong>Show WebSocket status
|
|
1444
|
+
indicator</strong> checkbox (off by default).
|
|
1445
|
+
</p>
|
|
1059
1446
|
</script>
|