@aaqu/fromcubes-portal-react 0.1.0-alpha.6 → 0.1.0-alpha.8

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.
@@ -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(PREFIX, "ensureJsxSetup: already done, re-applying compiler+diag only");
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(PREFIX, "INITIAL compilerOptions:", JSON.stringify(jsDef.getCompilerOptions()));
40
- console.log(PREFIX, "INITIAL diagnosticsOptions:", JSON.stringify(jsDef.getDiagnosticsOptions()));
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
 
@@ -134,7 +145,17 @@
134
145
  });
135
146
  }
136
147
  }
137
- console.log(PREFIX, "TW completion: ctx=" + (isClassName ? "className" : "string") + " word='" + word + "', matched=" + suggestions.length + "/" + classes.length);
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","span","p","a","button","input","img","form","label",
146
- "h1","h2","h3","h4","h5","h6",
147
- "ul","ol","li","dl","dt","dd",
148
- "table","thead","tbody","tfoot","tr","th","td",
149
- "section","article","aside","header","footer","nav","main",
150
- "strong","em","b","i","u","s","small","mark","code","pre","blockquote",
151
- "br","hr","wbr",
152
- "select","option","optgroup","textarea",
153
- "fieldset","legend","details","summary","dialog",
154
- "canvas","video","audio","source","picture","figure","figcaption",
155
- "svg","path","circle","rect","line","g","defs","use","text",
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 = !afterBracket && before.match(/(?:^|[\s{(,])([a-zA-Z][a-zA-Z0-9]*)$/);
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 = position.column - typed.length - (afterBracket ? 1 : 0);
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: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
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: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
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: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
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 = afterBracket && before.match(/<([A-Z][a-zA-Z0-9]*)$/);
224
- var bareUpper = !afterBracket && before.match(/(?:^|[\s{(,])([A-Z][a-zA-Z0-9]*)$/);
225
- var compTyped = upperMatch ? upperMatch[1] : (bareUpper ? bareUpper[1] : "");
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 (!compTyped && typed && name.toLowerCase().indexOf(typed) !== 0) continue;
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: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
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: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
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: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
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: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
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(PREFIX, "JSX tag completion: typed='" + typed + "', suggestions=" + suggestions.length);
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]; tag = m[2];
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]; tag = m[2];
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
- range: {
314
- startLineNumber: lineNum,
315
- endLineNumber: lineNum,
316
- startColumn: startCol,
317
- endColumn: endCol,
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
- text: replacement,
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(PREFIX, "component names loaded:", window.__fcComponentNames);
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=" + (m.code || "?") +
347
- " severity=" + m.severity +
348
- " source=" + (m.source || "?") +
349
- " msg=" + m.message +
350
- " [L" + m.startLineNumber + ":" + m.startColumn + "]"
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(PREFIX, "readback compilerOptions:", JSON.stringify(jsDef.getCompilerOptions()));
383
- console.log(PREFIX, "readback diagnosticsOptions:", JSON.stringify(jsDef.getDiagnosticsOptions()));
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(PREFIX, "Monaco already loaded (by Node-RED?), running setup inline");
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;
@@ -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("[FC-Monaco] tw-classes loaded, count=" + classes.length);
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("[FC-Monaco] tw-classes FAILED:", xhr.status, xhr.statusText);
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("[FC-Monaco] COMP: Monaco load failed, using fallback textarea");
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("[FC-Monaco] COMP: applying JSX defaults before model creation");
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(code, "javascript", compUri);
539
- console.log("[FC-Monaco] COMP: model created, language=" + compModel.getLanguageId());
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("[FC-Monaco] COMP: markers after 500ms, count=" + markers.length);
705
+ console.log(
706
+ "[FC-Monaco] COMP: markers after 500ms, count=" + markers.length,
707
+ );
551
708
  markers.forEach(function (m) {
552
- console.log("[FC-Monaco] COMP marker: code=" + m.code + " msg=" + m.message);
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("[FC-Monaco] COMP: current compilerOptions:", JSON.stringify(monaco.typescript.javascriptDefaults.getCompilerOptions()));
556
- console.log("[FC-Monaco] COMP: current diagnosticsOptions:", JSON.stringify(monaco.typescript.javascriptDefaults.getDiagnosticsOptions()));
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
  },
@@ -641,7 +810,7 @@
641
810
  <label for="node-input-endpoint"
642
811
  ><i class="fa fa-globe"></i> Endpoint</label
643
812
  >
644
- <input type="text" id="node-input-endpoint" placeholder="/portal" />
813
+ <input type="text" id="node-input-endpoint" placeholder="/fromcubes" />
645
814
  <div
646
815
  style="font-size:11px;opacity:.5;margin-top:2px;margin-left:105px;"
647
816
  id="fc-url-hint"
@@ -653,6 +822,29 @@
653
822
  >
654
823
  <input type="text" id="node-input-pageTitle" placeholder="Portal" />
655
824
  </div>
825
+ <div class="form-row node-input-libs-container-row">
826
+ <label style="width:auto;"><i class="fa fa-archive"></i> Modules</label>
827
+ <ol id="node-input-libs-container"></ol>
828
+ <div
829
+ style="font-size:11px;opacity:.5;margin-top:4px;margin-left:4px;"
830
+ >
831
+ npm packages to bundle. Node-RED auto-installs them at deploy time.
832
+ </div>
833
+ </div>
834
+ <div class="form-row" style="margin-top:12px;">
835
+ <label style="width:auto;">
836
+ <input
837
+ type="checkbox"
838
+ id="node-input-showWsStatus"
839
+ style="width:auto;margin:0 8px 0 0;vertical-align:middle;"
840
+ />
841
+ Show WebSocket status indicator
842
+ </label>
843
+ <div style="font-size:11px;opacity:.5;margin-top:4px;margin-left:4px;">
844
+ Displays a small <em>fromcubes</em> badge in the bottom-right corner
845
+ showing connection state.
846
+ </div>
847
+ </div>
656
848
  </div>
657
849
 
658
850
  <!-- ── Tab: JSX ── -->
@@ -725,14 +917,17 @@
725
917
  </div>
726
918
  <div class="form-row" style="padding-left:4px;">
727
919
  <div style="font-size:11px;opacity:.6;line-height:1.5;">
728
- Read <code>X-Portal-*</code> headers set by Nginx proxy and expose user data
729
- via <code>useNodeRed()</code>.<br><br>
730
- When enabled:<br>
731
- &bull; <code>useNodeRed()</code> returns <code>{ data, send, user }</code> where
732
- <code>user</code> contains <code>userId</code>, <code>userName</code>,
733
- <code>username</code>, <code>email</code>, <code>role</code>, <code>groups</code><br>
734
- &bull; Messages from WebSocket include <code>msg._client</code> with user data<br><br>
735
- Requires <code>@aaqu/node-red-dashboard-2-portal-auth</code> Nginx setup.
920
+ Read <code>X-Portal-*</code> headers set by Nginx proxy and expose
921
+ user data via <code>useNodeRed()</code>.<br /><br />
922
+ When enabled:<br />
923
+ &bull; <code>useNodeRed()</code> returns
924
+ <code>{ data, send, user }</code> where <code>user</code> contains
925
+ <code>userId</code>, <code>userName</code>, <code>username</code>,
926
+ <code>email</code>, <code>role</code>, <code>groups</code><br />
927
+ &bull; Messages from WebSocket include <code>msg._client</code> with
928
+ user data<br /><br />
929
+ Requires Nginx (or similar reverse proxy) to set
930
+ <code>X-Portal-*</code> headers.
736
931
  </div>
737
932
  </div>
738
933
  </div>
@@ -759,7 +954,7 @@
759
954
  "",
760
955
  " return (",
761
956
  ' <div className="min-h-screen bg-zinc-950 p-8">',
762
- ' <h1 className="text-2xl font-light text-cyan-400 mb-6">Portal</h1>',
957
+ ' <h1 className="text-2xl font-light text-cyan-400 mb-6">fromcubes</h1>',
763
958
  ' <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">',
764
959
  ' <StatusCard label="Value" value={d.value ?? "—"} unit="" />',
765
960
  " </div>",
@@ -779,11 +974,13 @@
779
974
  color: "#61dafb",
780
975
  defaults: {
781
976
  name: { value: "" },
782
- endpoint: { value: "/portal", required: true },
783
- pageTitle: { value: "Portal" },
977
+ endpoint: { value: "/fromcubes", required: true },
978
+ pageTitle: { value: "fromcubes" },
784
979
  componentCode: { value: STARTER },
785
980
  customHead: { value: "" },
786
981
  portalAuth: { value: false },
982
+ showWsStatus: { value: false },
983
+ libs: { value: [] },
787
984
  },
788
985
  inputs: 1,
789
986
  outputs: 1,
@@ -802,10 +999,16 @@
802
999
  if (!window.__fcTwClasses) {
803
1000
  console.log("[FC-Monaco] loading tw-classes...");
804
1001
  $.getJSON("portal-react/tw-classes", function (classes) {
805
- console.log("[FC-Monaco] tw-classes loaded, count=" + classes.length);
1002
+ console.log(
1003
+ "[FC-Monaco] tw-classes loaded, count=" + classes.length,
1004
+ );
806
1005
  window.__fcTwClasses = classes;
807
1006
  }).fail(function (xhr) {
808
- console.error("[FC-Monaco] tw-classes FAILED:", xhr.status, xhr.statusText);
1007
+ console.error(
1008
+ "[FC-Monaco] tw-classes FAILED:",
1009
+ xhr.status,
1010
+ xhr.statusText,
1011
+ );
809
1012
  });
810
1013
  }
811
1014
 
@@ -829,16 +1032,38 @@
829
1032
 
830
1033
  // URL hint
831
1034
  function updateHint() {
832
- var ep = $("#node-input-endpoint").val() || "/portal";
1035
+ var ep = $("#node-input-endpoint").val() || "/fromcubes";
833
1036
  $("#fc-url-hint").text("Page served at: http://<host>:1880" + ep);
834
1037
  }
835
1038
  $("#node-input-endpoint").on("input", updateHint);
836
1039
  updateHint();
837
1040
 
1041
+ // Modules editableList (like function node's libs)
1042
+ var libsList = node.libs || [];
1043
+ $("#node-input-libs-container").css("min-height","68px").editableList({
1044
+ addItem: function(container, i, opt) {
1045
+ var lib = opt || {};
1046
+ var row = $('<div/>',{style:"display:flex;gap:8px;align-items:center;"}).appendTo(container);
1047
+ var modInput = $('<input/>',{type:"text",placeholder:"e.g. chart.js/auto@^4.4.0",style:"flex:1;"}).appendTo(row);
1048
+ var varInput = $('<input/>',{type:"text",placeholder:"Import as (e.g. Chart)",style:"width:140px;"}).appendTo(row);
1049
+ modInput.val(lib.module || "");
1050
+ varInput.val(lib.var || "");
1051
+ container.data("mod", modInput);
1052
+ container.data("var", varInput);
1053
+ },
1054
+ removable: true,
1055
+ sortable: true
1056
+ });
1057
+ libsList.forEach(function(lib) {
1058
+ $("#node-input-libs-container").editableList("addItem", lib);
1059
+ });
1060
+
838
1061
  // Monaco
839
1062
  window.__fcLoadMonaco(function (failed) {
840
1063
  if (failed) {
841
- console.error("[FC-Monaco] PORTAL: Monaco load failed, using fallback textarea");
1064
+ console.error(
1065
+ "[FC-Monaco] PORTAL: Monaco load failed, using fallback textarea",
1066
+ );
842
1067
  $("#fc-monaco").hide();
843
1068
  $("#fc-fallback").show().val(code);
844
1069
  $("#fc-head-monaco").hide();
@@ -846,11 +1071,15 @@
846
1071
  return;
847
1072
  }
848
1073
 
849
- console.log("[FC-Monaco] PORTAL: applying JSX defaults before model creation");
1074
+ console.log(
1075
+ "[FC-Monaco] PORTAL: applying JSX defaults before model creation",
1076
+ );
850
1077
  window.__fcApplyJsxDefaults();
851
1078
  var opts = window.__fcEditorOpts;
852
1079
 
853
- var jsxUri = monaco.Uri.parse("file:///fc-portal-" + node.id + ".jsx");
1080
+ var jsxUri = monaco.Uri.parse(
1081
+ "file:///fc-portal-" + node.id + ".jsx",
1082
+ );
854
1083
  console.log("[FC-Monaco] PORTAL: JSX model URI=" + jsxUri.toString());
855
1084
  var existingJsx = monaco.editor.getModel(jsxUri);
856
1085
  if (existingJsx) {
@@ -858,15 +1087,21 @@
858
1087
  existingJsx.dispose();
859
1088
  }
860
1089
  var jsxModel = monaco.editor.createModel(code, "javascript", jsxUri);
861
- console.log("[FC-Monaco] PORTAL: JSX model created, language=" + jsxModel.getLanguageId());
1090
+ console.log(
1091
+ "[FC-Monaco] PORTAL: JSX model created, language=" +
1092
+ jsxModel.getLanguageId(),
1093
+ );
862
1094
  editorInstance = monaco.editor.create(
863
1095
  document.getElementById("fc-monaco"),
864
1096
  Object.assign({ model: jsxModel }, opts),
865
1097
  );
866
1098
  console.log("[FC-Monaco] PORTAL: JSX editor created");
867
- if (window.__fcAttachSelfClose) window.__fcAttachSelfClose(editorInstance);
1099
+ if (window.__fcAttachSelfClose)
1100
+ window.__fcAttachSelfClose(editorInstance);
868
1101
 
869
- var headUri = monaco.Uri.parse("file:///fc-head-" + node.id + ".html");
1102
+ var headUri = monaco.Uri.parse(
1103
+ "file:///fc-head-" + node.id + ".html",
1104
+ );
870
1105
  var existingHead = monaco.editor.getModel(headUri);
871
1106
  if (existingHead) existingHead.dispose();
872
1107
  var headModel = monaco.editor.createModel(headCode, "html", headUri);
@@ -879,12 +1114,32 @@
879
1114
  // Log markers after a short delay (diagnostics are async)
880
1115
  setTimeout(function () {
881
1116
  var markers = monaco.editor.getModelMarkers({ resource: jsxUri });
882
- console.log("[FC-Monaco] PORTAL: markers after 500ms, count=" + markers.length);
1117
+ console.log(
1118
+ "[FC-Monaco] PORTAL: markers after 500ms, count=" +
1119
+ markers.length,
1120
+ );
883
1121
  markers.forEach(function (m) {
884
- console.log("[FC-Monaco] PORTAL marker: code=" + m.code + " severity=" + m.severity + " msg=" + m.message);
1122
+ console.log(
1123
+ "[FC-Monaco] PORTAL marker: code=" +
1124
+ m.code +
1125
+ " severity=" +
1126
+ m.severity +
1127
+ " msg=" +
1128
+ m.message,
1129
+ );
885
1130
  });
886
- console.log("[FC-Monaco] PORTAL: current compilerOptions:", JSON.stringify(monaco.typescript.javascriptDefaults.getCompilerOptions()));
887
- console.log("[FC-Monaco] PORTAL: current diagnosticsOptions:", JSON.stringify(monaco.typescript.javascriptDefaults.getDiagnosticsOptions()));
1131
+ console.log(
1132
+ "[FC-Monaco] PORTAL: current compilerOptions:",
1133
+ JSON.stringify(
1134
+ monaco.typescript.javascriptDefaults.getCompilerOptions(),
1135
+ ),
1136
+ );
1137
+ console.log(
1138
+ "[FC-Monaco] PORTAL: current diagnosticsOptions:",
1139
+ JSON.stringify(
1140
+ monaco.typescript.javascriptDefaults.getDiagnosticsOptions(),
1141
+ ),
1142
+ );
888
1143
  }, 500);
889
1144
  });
890
1145
 
@@ -958,6 +1213,19 @@
958
1213
 
959
1214
  oneditsave: function () {
960
1215
  console.log("[FC-Monaco] PORTAL oneditsave");
1216
+
1217
+ // Collect libs from editableList
1218
+ var libs = [];
1219
+ var items = $("#node-input-libs-container").editableList("items");
1220
+ items.each(function() {
1221
+ var mod = $(this).data("mod").val().trim();
1222
+ var v = $(this).data("var").val().trim();
1223
+ if (mod) {
1224
+ libs.push({ module: mod, var: v });
1225
+ }
1226
+ });
1227
+ this.libs = libs;
1228
+
961
1229
  var code = editorInstance
962
1230
  ? editorInstance.getValue()
963
1231
  : $("#fc-fallback").val();
@@ -1078,12 +1346,13 @@ const { data, send } = useNodeRed();
1078
1346
  <h3>Portal Auth</h3>
1079
1347
  <p>
1080
1348
  When enabled in the Auth tab, reads <code>X-Portal-*</code> headers set by
1081
- an Nginx proxy (via <code>@aaqu/node-red-dashboard-2-portal-auth</code>) and
1082
- exposes user data:
1349
+ a reverse proxy (e.g. Nginx) and exposes user data:
1083
1350
  </p>
1084
1351
  <ul>
1085
1352
  <li><code>useNodeRed()</code> returns <code>{ data, send, user }</code></li>
1086
- <li>Messages from WebSocket include <code>msg._client</code> with user info</li>
1353
+ <li>
1354
+ Messages from WebSocket include <code>msg._client</code> with user info
1355
+ </li>
1087
1356
  </ul>
1088
1357
 
1089
1358
  <h3>Custom Head HTML</h3>