@yuneta/gobj-ui 7.23.67 → 7.23.69

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.
@@ -115,6 +115,9 @@ SDATA(data_type_t.DTP_INTEGER, "fold_page_size", 0, 24, "Children of one
115
115
  /*---------------- User last selections ----------------*/
116
116
  SDATA(data_type_t.DTP_STRING, "operation_mode", sdata_flag_t.SDF_PERSIST, "reading", "Current operation mode (internal behaviour or role). Changed by the user trough the gui."),
117
117
  SDATA(data_type_t.DTP_STRING, "layout", sdata_flag_t.SDF_PERSIST, "", "Current graph layout. User preference. Changed by the user through the gui."),
118
+ SDATA(data_type_t.DTP_LIST, "hidden_topics", sdata_flag_t.SDF_PERSIST, "[]", "Topics hidden from the graph (legend). User preference, per treedb"),
119
+ SDATA(data_type_t.DTP_STRING, "main_topic", sdata_flag_t.SDF_PERSIST, "", "The topic the tree hangs from; empty = deduced by the graph. User preference, per treedb"),
120
+ SDATA(data_type_t.DTP_LIST, "loose_topics", sdata_flag_t.SDF_PERSIST, "[]", "Topics whose LOOSE records (no parent) are shown. User preference, per treedb"),
118
121
 
119
122
  /*---------------- Remote Connection ----------------*/
120
123
  SDATA(data_type_t.DTP_POINTER, "gobj_remote_yuno", 0, null, "Remote Yuno to request data"),
@@ -165,6 +168,7 @@ let PRIVATE_DATA = {
165
168
  operation_mode: null,
166
169
  layout: null,
167
170
  _topics_subscribed: {},
171
+ _legend_state: null, /* the engine's last EV_LEGEND_STATE */
168
172
  _links_subscribed: false, /* EV_TREEDB_NODE_LINKED/UNLINKED (treedb-wide) */
169
173
 
170
174
  is_pinhold_window: false, // inherited of v6, todo review
@@ -246,6 +250,9 @@ function mt_create(gobj)
246
250
  fkey_port_position: "top",
247
251
  expand_depth: gobj_read_integer_attr(gobj, "expand_depth"),
248
252
  fold_page_size: gobj_read_integer_attr(gobj, "fold_page_size"),
253
+ hidden_topics: gobj_read_attr(gobj, "hidden_topics") || [],
254
+ main_topic: gobj_read_str_attr(gobj, "main_topic") || "",
255
+ loose_topics: gobj_read_attr(gobj, "loose_topics") || [],
249
256
  },
250
257
  gobj
251
258
  );
@@ -363,13 +370,18 @@ function build_ui(gobj)
363
370
  // Don't use is-flex, don't work well with is-hidden
364
371
  ['div', {class: 'C_YUI_TREEDB_GRAPH', style: `height:100%; display:flex; flex-direction:column;`}, [
365
372
  ['div', {class: 'GRAPH_TOOLBAR_ROW is-flex-grow-0 is-flex is-align-items-center'}, row_items],
366
- /* Topic colour legend: a strip, not an overlay. It is opened to
367
- * be READ against the graph, and an overlay would cover the
368
- * thing it explains. Built on first open (the colours are the
369
- * child's, assigned when the schema arrives). */
370
- ['div', {class: 'GRAPH_LEGEND is-hidden is-flex-grow-0',
371
- style: 'display:flex; flex-wrap:wrap; align-items:center; ' +
372
- 'gap:.4rem; padding:.35rem .5rem;'}, []],
373
+ /* The legend: a strip under the toolbar, ALWAYS there, one
374
+ * chip per topic. It is the graph's layer control, not a
375
+ * colour key: a chip shows or hides its topic, marks the main
376
+ * one, shows the loose records, highlights. Painted from the
377
+ * engine's EV_LEGEND_STATE, which is the truth about what is
378
+ * drawn. A strip and not an overlay because it is read
379
+ * AGAINST the graph, and an overlay would cover the thing it
380
+ * explains. Scrolls sideways on a narrow screen. */
381
+ ['div', {class: 'GRAPH_LEGEND is-flex-grow-0',
382
+ style: 'display:flex; flex-wrap:nowrap; align-items:center; ' +
383
+ 'gap:.35rem; padding:.3rem .5rem; overflow-x:auto; ' +
384
+ 'min-height:2.2rem;'}, []],
373
385
  ['div', {class: `GRAPH_BODY is-flex-grow-1 ${padding}`, style: 'height:100%; min-height:0; overflow:hidden;'}, [
374
386
  ['div', {id: priv.canvas_id, class: `GRAPH_CANVAS graph-container`, style: 'height:100%; min-height:0;border: 1px solid var(--bulma-border-weak);border-radius:0.2rem;'}, [
375
387
  ]]
@@ -575,23 +587,6 @@ function make_toolbar(gobj)
575
587
  * way back to the default depth. */
576
588
  ...yui_graph_fold_items(gobj, gobj_read_str_attr(gobj, "wide")),
577
589
 
578
- /* Which colour is which topic. The port colour of a node encodes
579
- * the topic it links to, which is the whole point of the graph and
580
- * which nothing on screen explained. Clicking an entry focuses that
581
- * topic, so the legend is also the way to say "show me these" —
582
- * and clicking the focused one again clears it. */
583
- ['button', {class: 'GRAPH_LEGEND_BTN button ml-2',
584
- title: t('legend'), 'aria-label': t('legend'),
585
- 'data-i18n-title': 'legend', 'data-i18n-aria-label': 'legend'}, [
586
- ['i', {class: 'yi-square'}],
587
- ['span', {class: 'is-hidden-mobile', style: 'padding-left:5px;', i18n: 'legend'}, 'legend']
588
- ], {
589
- click: (evt) => {
590
- evt.stopPropagation();
591
- gobj_send_event(gobj, "EV_TOGGLE_LEGEND", {}, gobj);
592
- }
593
- }],
594
-
595
590
  /* Inspect the treedb's raw tranger json in the lazy tree viewer
596
591
  * (print-tranger on the C_NODE service). A treedb can be huge, so
597
592
  * the viewer drills in on demand — see open_json_viewer. */
@@ -2333,6 +2328,11 @@ function ac_set_operation_mode(gobj, event, kw, src)
2333
2328
  * card's graph icon travels — as EV_TOPIC_SELECTED, which the host turns
2334
2329
  * into the URL, so what you are looking at stays linkable.
2335
2330
  ************************************************************/
2331
+ /* The glyphs of a chip's small buttons: `is-small` sets a 0.75rem
2332
+ * font and a star or a crosshair at that size is a speck. The BUTTON
2333
+ * stays small so the strip keeps its height; the glyph inside grows. */
2334
+ const LEGEND_GLYPH_STYLE = 'font-size:1.5em; line-height:1; color:inherit;';
2335
+
2336
2336
  function refresh_legend(gobj)
2337
2337
  {
2338
2338
  let priv = gobj.priv;
@@ -2345,72 +2345,194 @@ function refresh_legend(gobj)
2345
2345
  return;
2346
2346
  }
2347
2347
 
2348
- let descs = priv.gobj_nodes_tree?
2349
- gobj_read_attr(priv.gobj_nodes_tree, "descs") : null;
2348
+ let state = priv._legend_state;
2350
2349
  $legend.textContent = "";
2351
- if(!descs) {
2352
- return;
2350
+ if(!state || !is_array(state.topics)) {
2351
+ return; /* nothing drawn yet: the engine has not spoken */
2353
2352
  }
2354
2353
 
2355
- for(const [topic_name, desc] of Object.entries(descs)) {
2356
- if(topic_name.substring(0, 2) === "__") {
2357
- continue; /* the internal topics are not drawn */
2354
+ /* The main topic leads the strip: it is the trunk everything else
2355
+ * hangs from, and the eye reads the strip left to right. */
2356
+ let topics = state.topics.slice().sort((a, b) => {
2357
+ let am = (a.topic === state.main_topic)? 0 : 1;
2358
+ let bm = (b.topic === state.main_topic)? 0 : 1;
2359
+ return am - bm;
2360
+ });
2361
+
2362
+ for(let entry of topics) {
2363
+ let topic_name = entry.topic;
2364
+ let is_main = (topic_name === state.main_topic);
2365
+ let focused = (priv.focus_topic === topic_name);
2366
+ let hidden = !!entry.hidden;
2367
+
2368
+ /* A control with a small, named target for each of the things
2369
+ * the strip can do, so a finger lands on ONE of them:
2370
+ * the body show / hide (the main topic cannot be hidden)
2371
+ * ★ make it the main topic
2372
+ * +N show / hide its loose records
2373
+ * ⌖ highlight (the focus)
2374
+ * The names are DATA: a topic name carries no i18n key and is
2375
+ * never translated; the counts are numbers. */
2376
+ let controls = [];
2377
+
2378
+ controls.push(
2379
+ ['span', {class: 'GRAPH_LEGEND_SWATCH',
2380
+ style: `display:inline-block; width:.8rem; height:.8rem; ` +
2381
+ `border-radius:3px; margin-right:.35rem; flex:0 0 auto; ` +
2382
+ `background:${entry.color || "#94a3b8"}; ` +
2383
+ `border:1px solid rgba(0,0,0,.25);` +
2384
+ (hidden? 'opacity:.35;' : '')}],
2385
+ ['span', {class: 'GRAPH_LEGEND_NAME',
2386
+ style: hidden? 'text-decoration:line-through; opacity:.6;' : ''},
2387
+ topic_name],
2388
+ ['span', {class: 'GRAPH_LEGEND_COUNT has-text-grey',
2389
+ style: 'margin-left:.35rem; font-size:.8em;'},
2390
+ hidden? `${entry.total}` : `${entry.visible}/${entry.total}`]
2391
+ );
2392
+
2393
+ /* Built as an object first: an attribute that is `undefined`
2394
+ * is still SET by createElement2 (to the string "undefined"),
2395
+ * and a `disabled="undefined"` button is a disabled button --
2396
+ * every chip was dead except its star. */
2397
+ let body_attrs = {
2398
+ class: 'GRAPH_LEGEND_ITEM button is-small' +
2399
+ (focused? ' is-primary is-light' : '') +
2400
+ (is_main? ' GRAPH_LEGEND_MAIN' : ''),
2401
+ type: 'button',
2402
+ style: 'gap:0; flex:0 0 auto;',
2403
+ title: is_main? t('main topic') : (hidden? t('show topic') : t('hide topic')),
2404
+ 'aria-label': topic_name,
2405
+ 'aria-pressed': hidden? 'false' : 'true',
2406
+ };
2407
+ if(is_main) {
2408
+ body_attrs.disabled = 'disabled';
2358
2409
  }
2359
- if(!desc || !desc.color) {
2360
- continue;
2410
+ let $body = createElement2(
2411
+ ['button', body_attrs, controls, {
2412
+ click: (evt) => {
2413
+ evt.stopPropagation();
2414
+ gobj_send_event(gobj, "EV_LEGEND_TOPIC",
2415
+ {topic: topic_name, action: "toggle"}, gobj);
2416
+ }
2417
+ }]
2418
+ );
2419
+
2420
+ let extras = [];
2421
+
2422
+ /* The star: filled on the main one, hollow on the others,
2423
+ * where it moves the star. On a main topic the READER chose it
2424
+ * is a button too: pressing it hands the choice back to the
2425
+ * graph (deduced again). On a deduced one it is a mark. */
2426
+ if(is_main && state.main_chosen) {
2427
+ extras.push(['button', {class: 'GRAPH_LEGEND_STAR button is-small is-warning is-light',
2428
+ type: 'button', style: 'padding:0 .4rem;',
2429
+ title: t('main topic'), 'data-i18n-title': 'main topic',
2430
+ 'aria-label': t('main topic'), 'aria-pressed': 'true'},
2431
+ [['span', {style: LEGEND_GLYPH_STYLE}, '★']], {
2432
+ click: (evt) => {
2433
+ evt.stopPropagation();
2434
+ gobj_send_event(gobj, "EV_LEGEND_TOPIC",
2435
+ {topic: "", action: "main"}, gobj);
2436
+ }
2437
+ }]);
2438
+ } else if(is_main) {
2439
+ extras.push(['span', {class: 'GRAPH_LEGEND_STAR button is-small is-static',
2440
+ style: 'padding:0 .4rem;',
2441
+ title: t('main topic'), 'data-i18n-title': 'main topic'},
2442
+ [['span', {style: LEGEND_GLYPH_STYLE}, '★']]]);
2443
+ } else if(!hidden) {
2444
+ extras.push(['button', {class: 'GRAPH_LEGEND_STAR button is-small',
2445
+ type: 'button', style: 'padding:0 .4rem;',
2446
+ title: t('main topic'), 'data-i18n-title': 'main topic',
2447
+ 'aria-label': t('main topic')},
2448
+ [['span', {style: LEGEND_GLYPH_STYLE}, '☆']], {
2449
+ click: (evt) => {
2450
+ evt.stopPropagation();
2451
+ gobj_send_event(gobj, "EV_LEGEND_TOPIC",
2452
+ {topic: topic_name, action: "main"}, gobj);
2453
+ }
2454
+ }]);
2361
2455
  }
2362
- let focused = (priv.focus_topic === topic_name);
2363
- let $item = createElement2(
2364
- ['button', {
2365
- /* `is-light` on the focused one, not solid `is-primary`:
2366
- * the solid fill sat right behind the swatch and swallowed
2367
- * the one colour the entry exists to show. */
2368
- class: 'GRAPH_LEGEND_ITEM button is-small' +
2456
+
2457
+ /* Loose records: the ones that should hang from the main tree
2458
+ * and do not. Only where there are any, and only for a topic
2459
+ * the schema hangs from the main one. */
2460
+ if(!hidden && entry.linked && entry.loose > 0) {
2461
+ extras.push(['button', {
2462
+ class: 'GRAPH_LEGEND_LOOSE button is-small' +
2463
+ (entry.loose_shown? ' is-warning is-light' : ''),
2464
+ type: 'button', style: 'padding:0 .45rem;',
2465
+ title: t('loose records'), 'data-i18n-title': 'loose records',
2466
+ 'aria-label': t('loose records'),
2467
+ 'aria-pressed': entry.loose_shown? 'true' : 'false'
2468
+ }, `+${entry.loose}`, {
2469
+ click: (evt) => {
2470
+ evt.stopPropagation();
2471
+ gobj_send_event(gobj, "EV_LEGEND_TOPIC",
2472
+ {topic: topic_name, action: "loose"}, gobj);
2473
+ }
2474
+ }]);
2475
+ }
2476
+
2477
+ if(!hidden) {
2478
+ extras.push(['button', {
2479
+ class: 'GRAPH_LEGEND_FOCUS button is-small' +
2369
2480
  (focused? ' is-primary is-light' : ''),
2370
- title: topic_name,
2371
- 'aria-label': topic_name,
2481
+ type: 'button', style: 'padding:0 .4rem;',
2482
+ title: t('highlight topic'), 'data-i18n-title': 'highlight topic',
2483
+ 'aria-label': t('highlight topic'),
2372
2484
  'aria-pressed': focused? 'true' : 'false'
2373
- }, [
2374
- ['span', {class: 'GRAPH_LEGEND_SWATCH',
2375
- style: `display:inline-block; width:.8rem; height:.8rem; ` +
2376
- `border-radius:3px; margin-right:.4rem; ` +
2377
- `background:${desc.color}; ` +
2378
- `border:1px solid rgba(0,0,0,.25);`}],
2379
- /* The topic name is DATA: it carries no i18n key and is
2380
- * never translated. */
2381
- ['span', {}, topic_name]
2382
- ], {
2485
+ }, [['i', {class: 'yi-location-crosshairs', style: LEGEND_GLYPH_STYLE}]], {
2383
2486
  click: (evt) => {
2384
2487
  evt.stopPropagation();
2385
- gobj_send_event(gobj, "EV_LEGEND_TOPIC", {topic: topic_name}, gobj);
2488
+ gobj_send_event(gobj, "EV_LEGEND_TOPIC",
2489
+ {topic: topic_name, action: "focus"}, gobj);
2386
2490
  }
2387
- }]
2491
+ }]);
2492
+ }
2493
+
2494
+ let $chip = createElement2(
2495
+ ['div', {class: 'GRAPH_LEGEND_CHIP buttons has-addons',
2496
+ style: 'margin:0; flex:0 0 auto; flex-wrap:nowrap;'},
2497
+ [$body].concat(extras.map((spec) => createElement2(spec)))]
2388
2498
  );
2389
- $legend.appendChild($item);
2499
+ $legend.appendChild($chip);
2390
2500
  }
2391
2501
  }
2392
2502
 
2503
+ /************************************************************
2504
+ * The engine said what is drawn: repaint the strip.
2505
+ ************************************************************/
2506
+ function ac_legend_state(gobj, event, kw, src)
2507
+ {
2508
+ let priv = gobj.priv;
2509
+ priv._legend_state = kw || null;
2510
+ refresh_legend(gobj);
2511
+ return 0;
2512
+ }
2513
+
2393
2514
  /************************************************************
2394
2515
  *
2395
2516
  ************************************************************/
2396
- function ac_toggle_legend(gobj, event, kw, src)
2517
+ /************************************************************
2518
+ * One of the legend's settings changed: write it, SAVE it (they
2519
+ * are the reader's, per treedb) and tell the engine.
2520
+ ************************************************************/
2521
+ function set_legend_list_attr(gobj, name, event_name, topics)
2397
2522
  {
2398
- let $container = gobj_read_attr(gobj, "$container");
2399
- if(!$container) {
2400
- return 0;
2401
- }
2402
- let $legend = $container.querySelector(".GRAPH_LEGEND");
2403
- if(!$legend) {
2404
- log_error(`${gobj_short_name(gobj)}: no legend strip to toggle`);
2405
- return -1;
2406
- }
2407
- if($legend.classList.contains("is-hidden")) {
2408
- refresh_legend(gobj);
2409
- $legend.classList.remove("is-hidden");
2410
- } else {
2411
- $legend.classList.add("is-hidden");
2523
+ let priv = gobj.priv;
2524
+
2525
+ gobj_write_attr(gobj, name, topics);
2526
+ gobj_save_persistent_attrs(gobj, name);
2527
+ if(priv.gobj_nodes_tree) {
2528
+ gobj_send_event(priv.gobj_nodes_tree, event_name, {topics: topics}, gobj);
2412
2529
  }
2413
- return 0;
2530
+ }
2531
+
2532
+ function legend_list_attr(gobj, name)
2533
+ {
2534
+ let list = gobj_read_attr(gobj, name);
2535
+ return is_array(list)? list.slice() : [];
2414
2536
  }
2415
2537
 
2416
2538
  /************************************************************
@@ -2422,11 +2544,60 @@ function ac_legend_topic(gobj, event, kw, src)
2422
2544
  {
2423
2545
  let priv = gobj.priv;
2424
2546
  let topic = (kw && kw.topic) || "";
2425
- if(!topic) {
2547
+ let action = (kw && kw.action) || "focus";
2548
+ if(!topic && action !== "main") {
2426
2549
  log_error(`${gobj_short_name(gobj)}: legend click with no topic`);
2427
2550
  return -1;
2428
2551
  }
2429
2552
 
2553
+ let state = priv._legend_state || {};
2554
+
2555
+ switch(action) {
2556
+ case "toggle": {
2557
+ /* Show or hide the topic. Never the main one: hiding the
2558
+ * trunk turns everything into roots, which is the pile. */
2559
+ if(topic === state.main_topic) {
2560
+ log_error(`${gobj_short_name(gobj)}: the main topic '${topic}' cannot be hidden`);
2561
+ return -1;
2562
+ }
2563
+ let hidden = legend_list_attr(gobj, "hidden_topics");
2564
+ if(str_in_list(hidden, topic)) {
2565
+ delete_from_list(hidden, topic);
2566
+ } else {
2567
+ hidden.push(topic);
2568
+ }
2569
+ set_legend_list_attr(gobj, "hidden_topics", "EV_SET_HIDDEN_TOPICS", hidden);
2570
+ return 0;
2571
+ }
2572
+
2573
+ case "loose": {
2574
+ let loose = legend_list_attr(gobj, "loose_topics");
2575
+ if(str_in_list(loose, topic)) {
2576
+ delete_from_list(loose, topic);
2577
+ } else {
2578
+ loose.push(topic);
2579
+ }
2580
+ set_legend_list_attr(gobj, "loose_topics", "EV_SET_LOOSE_TOPICS", loose);
2581
+ return 0;
2582
+ }
2583
+
2584
+ case "main": {
2585
+ gobj_write_str_attr(gobj, "main_topic", topic);
2586
+ gobj_save_persistent_attrs(gobj, "main_topic");
2587
+ if(priv.gobj_nodes_tree) {
2588
+ gobj_send_event(priv.gobj_nodes_tree, "EV_SET_MAIN_TOPIC", {topic: topic}, gobj);
2589
+ }
2590
+ return 0;
2591
+ }
2592
+
2593
+ case "focus":
2594
+ break;
2595
+
2596
+ default:
2597
+ log_error(`${gobj_short_name(gobj)}: legend action unknown: ${action}`);
2598
+ return -1;
2599
+ }
2600
+
2430
2601
  let next = (priv.focus_topic === topic)? "" : topic;
2431
2602
 
2432
2603
  /* APPLY, then announce — in that order, and both halves are needed.
@@ -2528,7 +2699,14 @@ function ac_find_result(gobj, event, kw, src)
2528
2699
  return 0;
2529
2700
  }
2530
2701
 
2531
- $count.textContent = t("matches", {count: (kw && kw.matches) || 0});
2702
+ let text = t("matches", {count: (kw && kw.matches) || 0});
2703
+ /* Matches in HIDDEN topics, counted apart: "0" alone reads as
2704
+ * "does not exist" when it means "is hidden". */
2705
+ let hidden = (kw && kw.hidden_matches) || 0;
2706
+ if(hidden > 0) {
2707
+ text += ` (+${hidden} ${t("hidden topics")})`;
2708
+ }
2709
+ $count.textContent = text;
2532
2710
  $result.classList.remove("is-hidden");
2533
2711
  return 0;
2534
2712
  }
@@ -2537,6 +2715,18 @@ function ac_set_focus_topic(gobj, event, kw, src)
2537
2715
  {
2538
2716
  let priv = gobj.priv;
2539
2717
  priv.focus_topic = (kw && kw.topic) || "";
2718
+
2719
+ /* The URL wins over the strip: a route that lands on a hidden
2720
+ * topic shows it first, or it would highlight nothing and say
2721
+ * nothing about why. */
2722
+ if(priv.focus_topic) {
2723
+ let hidden = legend_list_attr(gobj, "hidden_topics");
2724
+ if(str_in_list(hidden, priv.focus_topic)) {
2725
+ delete_from_list(hidden, priv.focus_topic);
2726
+ set_legend_list_attr(gobj, "hidden_topics", "EV_SET_HIDDEN_TOPICS", hidden);
2727
+ }
2728
+ }
2729
+
2540
2730
  if(priv.gobj_nodes_tree) {
2541
2731
  gobj_send_event(
2542
2732
  priv.gobj_nodes_tree,
@@ -2547,11 +2737,7 @@ function ac_set_focus_topic(gobj, event, kw, src)
2547
2737
  }
2548
2738
  /* Keep the legend's mark on the topic that is actually focused —
2549
2739
  * including when the focus arrives from the URL and not from a click. */
2550
- let $container = gobj_read_attr(gobj, "$container");
2551
- let $legend = $container? $container.querySelector(".GRAPH_LEGEND") : null;
2552
- if($legend && !$legend.classList.contains("is-hidden")) {
2553
- refresh_legend(gobj);
2554
- }
2740
+ refresh_legend(gobj);
2555
2741
  return 0;
2556
2742
  }
2557
2743
 
@@ -2644,8 +2830,8 @@ function create_gclass(gclass_name)
2644
2830
  ["EV_EXPAND_ALL", ac_expand_all, null],
2645
2831
  ["EV_COLLAPSE_ALL", ac_collapse_all, null],
2646
2832
  ["EV_LAYOUT_AUTOSET", ac_layout_autoset, null],
2647
- ["EV_TOGGLE_LEGEND", ac_toggle_legend, null],
2648
2833
  ["EV_LEGEND_TOPIC", ac_legend_topic, null],
2834
+ ["EV_LEGEND_STATE", ac_legend_state, null],
2649
2835
  ["EV_FIND_RESULT", ac_find_result, null],
2650
2836
  ["EV_SHOW", ac_show, null],
2651
2837
  ["EV_HIDE", ac_hide, null],
@@ -2686,8 +2872,8 @@ function create_gclass(gclass_name)
2686
2872
  ["EV_COLLAPSE_ALL", 0],
2687
2873
  ["EV_FIND_RESULT", 0],
2688
2874
  ["EV_LAYOUT_AUTOSET", 0],
2689
- ["EV_TOGGLE_LEGEND", 0],
2690
2875
  ["EV_LEGEND_TOPIC", 0],
2876
+ ["EV_LEGEND_STATE", 0],
2691
2877
  ["EV_TOPIC_SELECTED",
2692
2878
  event_flag_t.EVF_OUTPUT_EVENT | event_flag_t.EVF_NO_WARN_SUBS],
2693
2879
  ["EV_OPERATION_MODE_CHANGED",