@yuneta/gobj-ui 5.11.1 → 5.13.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "5.11.1",
3
+ "version": "5.13.0",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
@@ -30,7 +30,7 @@
30
30
  "@rollup/plugin-terser": "^1.0.0",
31
31
  "@vitest/coverage-v8": "^4.1.10",
32
32
  "@yuneta/gobj-js": "^7.8.7",
33
- "vite": "^8.1.5",
33
+ "vite": "^8.2.1",
34
34
  "vitest": "^4.1.10"
35
35
  },
36
36
  "peerDependencies": {
package/src/c_yui_json.js CHANGED
@@ -37,6 +37,7 @@ import {
37
37
  data_type_t,
38
38
  event_flag_t,
39
39
  gclass_create,
40
+ gclass_find_by_name,
40
41
  log_error,
41
42
  gobj_read_pointer_attr,
42
43
  gobj_parent,
@@ -936,6 +937,13 @@ function create_gclass(gclass_name)
936
937
  ***************************************************************/
937
938
  function register_c_yui_json()
938
939
  {
940
+ /* Idempotent: C_YUI_TREEDB_TOPIC_WITH_FORM auto-registers this
941
+ * gclass for its schema dialog, so an app that also registers it
942
+ * explicitly (order-independent) must not trip
943
+ * "GClass ALREADY created". */
944
+ if(gclass_find_by_name(GCLASS_NAME, false)) {
945
+ return 0;
946
+ }
939
947
  return create_gclass(GCLASS_NAME);
940
948
  }
941
949
 
@@ -82,6 +82,24 @@
82
82
  }
83
83
  }
84
84
 
85
+ /* Cell of a col holding a JSON document: a truncated preview that opens
86
+ * the whole value in a viewer. Bulma already paints an <a> as a link
87
+ * (colour + pointer cursor, href or not — the click is an FSM event,
88
+ * not a navigation), so the only thing to state here is that a preview
89
+ * of a JSON fragment must stay on ONE line: wrapped, it would make the
90
+ * row of every structured record three lines tall. */
91
+ .JSON_CELL {
92
+ white-space: nowrap;
93
+ }
94
+ .JSON_CELL .JSON_CELL_ICON {
95
+ margin-right: 0.25rem;
96
+ vertical-align: middle;
97
+ }
98
+ .JSON_CELL .JSON_CELL_PREVIEW {
99
+ text-decoration: underline;
100
+ vertical-align: middle;
101
+ }
102
+
85
103
  .without-border {
86
104
  border: none !important;
87
105
  box-shadow: none !important;
@@ -69,6 +69,7 @@ import {
69
69
  } from "./c_yui_shell.js";
70
70
 
71
71
  import {register_c_yui_form} from "./c_yui_form.js";
72
+ import {register_c_yui_json} from "./c_yui_json.js";
72
73
  import {attach_clear} from "./yui_inputs.js";
73
74
 
74
75
  import {yui_tabulator_lang, yui_tabulator_relocalize} from "./yui_tabulator_i18n.js";
@@ -103,6 +104,7 @@ SDATA(data_type_t.DTP_BOOLEAN, "with_copy_button", 0, true, "Button tool
103
104
  SDATA(data_type_t.DTP_BOOLEAN, "with_paste_button", 0, true, "Button toolbar PASTE"),
104
105
  SDATA(data_type_t.DTP_BOOLEAN, "with_refresh_button", 0, true, "Button toolbar REFRESH"),
105
106
  SDATA(data_type_t.DTP_BOOLEAN, "with_search_button", 0, true, "Button toolbar SEARCH"),
107
+ SDATA(data_type_t.DTP_BOOLEAN, "with_schema_button", 0, true, "Button toolbar SCHEMA (show the topic's desc)"),
106
108
  SDATA(data_type_t.DTP_BOOLEAN, "with_in_row_edit_icons", 0, true, "Add a last column with internal EDIT/DELETE icon"),
107
109
 
108
110
  SDATA(data_type_t.DTP_BOOLEAN, "editable", 0, false, "Edit state"),
@@ -143,6 +145,10 @@ let PRIVATE_DATA = {
143
145
  tabulator: null,
144
146
  form: null, // hosted C_YUI_FORM child (while dialog open)
145
147
  form_modal: null, // { close } handle of the adaptive dialog
148
+ schema_gobj: null, // hosted C_YUI_JSON child (while schema open)
149
+ schema_modal: null, // { close } handle of the schema dialog
150
+ cell_json_gobj: null, // hosted C_YUI_JSON child (while a cell is open)
151
+ cell_json_modal: null, // { close } handle of the cell dialog
146
152
  };
147
153
 
148
154
  let __gclass__ = null;
@@ -221,6 +227,8 @@ function mt_stop(gobj)
221
227
  gobj_unsubscribe_event(shell, "EV_LANGUAGE_CHANGED", {}, gobj);
222
228
  }
223
229
  close_form_dialog(gobj);
230
+ close_schema_dialog(gobj);
231
+ close_cell_json_dialog(gobj);
224
232
  table__destroy(gobj);
225
233
  }
226
234
 
@@ -234,6 +242,8 @@ function mt_destroy(gobj)
234
242
  * it unhooks the Escape handler (shell chain or document
235
243
  * listener) and removes the dialog DOM. */
236
244
  close_form_dialog(gobj);
245
+ close_schema_dialog(gobj);
246
+ close_cell_json_dialog(gobj);
237
247
  destroy_ui(gobj);
238
248
  }
239
249
 
@@ -477,6 +487,7 @@ function build_ui(gobj)
477
487
 
478
488
  let with_refresh_button = gobj_read_bool_attr(gobj, "with_refresh_button");
479
489
  let with_search_button = gobj_read_bool_attr(gobj, "with_search_button");
490
+ let with_schema_button = gobj_read_bool_attr(gobj, "with_schema_button");
480
491
 
481
492
  if(with_search_button) {
482
493
  let search_id = `${toolbar_id}_search`;
@@ -543,6 +554,27 @@ function build_ui(gobj)
543
554
  $view_toolbar.appendChild($refresh);
544
555
  }
545
556
 
557
+ if(with_schema_button) {
558
+ /* What the columns of this topic ARE: types, flags, fkeys. The
559
+ * table shows the data; this shows the contract the data answers
560
+ * to, which is what you need when a value is refused or a link
561
+ * does not appear. */
562
+ let $schema = createElement2(
563
+ ['button', {class: 'TREEDB_TABLE_SCHEMA button mr-1',
564
+ title: t('schema'), 'data-i18n-title': 'schema',
565
+ 'aria-label': t('schema'), 'data-i18n-aria-label': 'schema'}, [
566
+ ['i', {class: 'yi-hexagon-nodes'}],
567
+ ['span', {class: 'is-hidden-mobile', i18n: 'schema', style: 'padding-left:5px;'}, 'schema']
568
+ ], {
569
+ 'click': (event) => {
570
+ event.stopPropagation();
571
+ gobj_send_event(gobj, "EV_SHOW_SCHEMA", {}, gobj);
572
+ }
573
+ }]
574
+ );
575
+ $view_toolbar.appendChild($schema);
576
+ }
577
+
546
578
  /*----------------------------------------------*
547
579
  * Layout Schema
548
580
  *----------------------------------------------*/
@@ -703,6 +735,36 @@ function create_tabulator(gobj)
703
735
  return "???";
704
736
  }
705
737
 
738
+ /*
739
+ * Click on a JSON-document cell (dict/list/blob/template/…): the cell
740
+ * can only ever show a truncated preview, so open the whole value in
741
+ * the viewer. The kw carries the IDENTITY of the cell (row id + col
742
+ * id), never the value itself — the machine trace dumps the kw.
743
+ */
744
+ function json_cell_click(e, cell)
745
+ {
746
+ if(!cell.getElement().querySelector('.JSON_CELL')) {
747
+ return; // empty document: nothing to open
748
+ }
749
+ e.stopPropagation();
750
+
751
+ let pkey = desc.pkey || "id";
752
+ let row_id = cell.getData()[pkey];
753
+ if(row_id === undefined || row_id === null) {
754
+ log_error(
755
+ `${gobj_short_name(gobj)}: row without pkey '${pkey}',` +
756
+ ` cannot open the json of '${cell.getField()}'`
757
+ );
758
+ return;
759
+ }
760
+ gobj_send_event(
761
+ gobj,
762
+ "EV_SHOW_CELL_JSON",
763
+ {row_id: row_id, col_id: cell.getField()},
764
+ gobj
765
+ );
766
+ }
767
+
706
768
  for (let i = 0; i < desc.cols.length; i++) {
707
769
  let col = desc.cols[i];
708
770
  if(!col.id || col.id[0]==='_') {
@@ -766,6 +828,8 @@ function create_tabulator(gobj)
766
828
  case "list":
767
829
  case "coordinates":
768
830
  case "blob":
831
+ case "gbuffer":
832
+ cellClick = json_cell_click;
769
833
  break;
770
834
  case "boolean":
771
835
  hozAlign = "center";
@@ -956,6 +1020,41 @@ function table__destroy(gobj)
956
1020
  }
957
1021
  }
958
1022
 
1023
+ /************************************************************
1024
+ * Cell of a col holding a JSON document (dict/list/blob/
1025
+ * template/coordinates/…): a one-line truncated preview,
1026
+ * marked as a link because the click opens the whole value
1027
+ * in a viewer (json_cell_click -> EV_SHOW_CELL_JSON).
1028
+ *
1029
+ * An empty document gets no link — there is nothing to open,
1030
+ * and json_cell_click uses its absence to ignore the click.
1031
+ *
1032
+ * Built as a DOM node and not as an HTML string: the preview
1033
+ * is raw record data, so it must never be parsed as markup.
1034
+ ************************************************************/
1035
+ function build_json_cell_preview(value)
1036
+ {
1037
+ if(value === null || value === undefined || json_size(value) === 0) {
1038
+ return "";
1039
+ }
1040
+
1041
+ let text = JSON.stringify(value);
1042
+ if(text && text.length > 20) {
1043
+ text = text.substring(0, 20) + "…";
1044
+ }
1045
+
1046
+ return createElement2(
1047
+ ['a', {
1048
+ class: 'JSON_CELL',
1049
+ title: t('show json'),
1050
+ 'data-i18n-title': 'show json'
1051
+ }, [
1052
+ ['span', {class: 'JSON_CELL_ICON icon yi-eye'}],
1053
+ ['span', {class: 'JSON_CELL_PREVIEW'}, text]
1054
+ ]]
1055
+ );
1056
+ }
1057
+
959
1058
  /************************************************************
960
1059
  * Convert a record column value from backend to frontend
961
1060
  ************************************************************/
@@ -985,10 +1084,7 @@ function transform__treedb_value_2_table_value(gobj, col, value, row, field)
985
1084
  case "coordinates":
986
1085
  case "blob":
987
1086
  case "gbuffer":
988
- value = JSON.stringify(value);
989
- if(value && value.length > 20) {
990
- value = value.substring(0, 20) + "…";
991
- }
1087
+ value = build_json_cell_preview(value);
992
1088
  break;
993
1089
 
994
1090
  case "enum":
@@ -1328,6 +1424,230 @@ function close_form_dialog(gobj)
1328
1424
  teardown_form_child(gobj);
1329
1425
  }
1330
1426
 
1427
+ /************************************************************
1428
+ * Show the topic's SCHEMA: its `desc` — pkey, cols, types,
1429
+ * flags and fkey targets — in the standardized adaptive
1430
+ * dialog, rendered by a C_YUI_JSON child. A viewer and not a
1431
+ * <pre> dump because a forty-column topic is only readable
1432
+ * collapsed and searchable.
1433
+ *
1434
+ * Read-only and offline: the desc is already in this gobj,
1435
+ * so no command is issued and nothing can be edited here.
1436
+ ************************************************************/
1437
+ function open_schema_dialog(gobj)
1438
+ {
1439
+ close_schema_dialog(gobj); // only one at a time
1440
+
1441
+ let priv = gobj.priv;
1442
+
1443
+ let desc = gobj_read_attr(gobj, "desc");
1444
+ if(!desc) {
1445
+ log_error(`${gobj_short_name(gobj)}: no desc, no schema to show`);
1446
+ return;
1447
+ }
1448
+
1449
+ let shell = yui_shell_of(gobj);
1450
+ if(!shell) {
1451
+ log_error(`${gobj_short_name(gobj)}: no shell, cannot open the schema view`);
1452
+ return;
1453
+ }
1454
+
1455
+ let json_view = gobj_create_pure_child(
1456
+ "schema_" + clean_name(gobj_name(gobj)),
1457
+ "C_YUI_JSON",
1458
+ {
1459
+ /* No `title`: the dialog header already titles it. The
1460
+ * viewer's own would land inside the dialog, doubling it. */
1461
+ },
1462
+ gobj
1463
+ );
1464
+ if(!json_view) {
1465
+ log_error(`${gobj_short_name(gobj)}: cannot create the schema viewer`);
1466
+ return;
1467
+ }
1468
+ priv.schema_gobj = json_view;
1469
+ gobj_start(json_view);
1470
+
1471
+ let $box = gobj_read_attr(json_view, "$container");
1472
+ if(!$box) {
1473
+ log_error(`${gobj_short_name(gobj)}: the schema viewer built no $container`);
1474
+ teardown_schema_child(gobj);
1475
+ return;
1476
+ }
1477
+
1478
+ /* Title split in two halves: the topic name is DATA (never
1479
+ * translated) and "schema" is the kind (carries its i18n key), so
1480
+ * the header re-translates on a language switch. */
1481
+ priv.schema_modal = yui_shell_show_modal(shell, $box, {
1482
+ dialog: true,
1483
+ logical_class: "TREEDB_SCHEMA_SHEET",
1484
+ title_prefix: gobj_read_str_attr(gobj, "topic_name"),
1485
+ title: "schema",
1486
+ t: t,
1487
+ on_close: function() {
1488
+ teardown_schema_child(gobj);
1489
+ }
1490
+ });
1491
+
1492
+ /* EV_SET_JSON and not the `json_data` attr: the attr renders the
1493
+ * tree but leaves the viewer in ST_IDLE, where a click to expand a
1494
+ * node is an event nobody handles. */
1495
+ gobj_send_event(json_view, "EV_SET_JSON", {json: desc}, gobj);
1496
+ }
1497
+
1498
+ /************************************************************
1499
+ * Destroy the hosted C_YUI_JSON child. Called from the
1500
+ * schema modal's on_close — the shell has already removed
1501
+ * the dialog DOM and retired its Escape / history entries.
1502
+ ************************************************************/
1503
+ function teardown_schema_child(gobj)
1504
+ {
1505
+ let priv = gobj.priv;
1506
+ if(priv.schema_gobj) {
1507
+ if(gobj_is_running(priv.schema_gobj)) {
1508
+ gobj_stop(priv.schema_gobj);
1509
+ }
1510
+ gobj_destroy(priv.schema_gobj);
1511
+ priv.schema_gobj = null;
1512
+ }
1513
+ priv.schema_modal = null;
1514
+ }
1515
+
1516
+ /************************************************************
1517
+ * Close the schema dialog (teardown, or a second open).
1518
+ * Closing the modal runs its on_close, which tears the
1519
+ * viewer child down.
1520
+ ************************************************************/
1521
+ function close_schema_dialog(gobj)
1522
+ {
1523
+ let priv = gobj.priv;
1524
+ if(priv.schema_modal) {
1525
+ let modal = priv.schema_modal;
1526
+ priv.schema_modal = null;
1527
+ modal.close(); // -> on_close -> teardown_schema_child
1528
+ return;
1529
+ }
1530
+ teardown_schema_child(gobj);
1531
+ }
1532
+
1533
+ /************************************************************
1534
+ * Show the JSON document held by ONE CELL — the value of a
1535
+ * dict/list/object/array/blob/template/coordinates col — in
1536
+ * the standardized adaptive dialog, rendered by a C_YUI_JSON
1537
+ * child. The cell can only show a 20-char preview; this is
1538
+ * where the value is actually readable (collapsed, searchable).
1539
+ *
1540
+ * Read-only and offline: the record is already in the table,
1541
+ * so no command is issued and nothing can be edited here (the
1542
+ * edit form remains the way to change the value).
1543
+ ************************************************************/
1544
+ function open_cell_json_dialog(gobj, row_id, col_id)
1545
+ {
1546
+ close_cell_json_dialog(gobj); // only one at a time
1547
+
1548
+ let priv = gobj.priv;
1549
+
1550
+ let tabulator = gobj_read_attr(gobj, "tabulator");
1551
+ if(!tabulator) {
1552
+ log_error(`${gobj_short_name(gobj)}: no table, no cell to show`);
1553
+ return;
1554
+ }
1555
+ let row = tabulator.getRow(row_id);
1556
+ if(!row) {
1557
+ log_error(`${gobj_short_name(gobj)}: row '${row_id}' not found in the table`);
1558
+ return;
1559
+ }
1560
+ let value = row.getData()[col_id];
1561
+ if(value === undefined) {
1562
+ log_error(`${gobj_short_name(gobj)}: row '${row_id}' has no col '${col_id}'`);
1563
+ return;
1564
+ }
1565
+
1566
+ let shell = yui_shell_of(gobj);
1567
+ if(!shell) {
1568
+ log_error(`${gobj_short_name(gobj)}: no shell, cannot open the cell json`);
1569
+ return;
1570
+ }
1571
+
1572
+ let json_view = gobj_create_pure_child(
1573
+ "cell_" + clean_name(gobj_name(gobj)),
1574
+ "C_YUI_JSON",
1575
+ {
1576
+ /* No `title`: the dialog header already titles it. */
1577
+ },
1578
+ gobj
1579
+ );
1580
+ if(!json_view) {
1581
+ log_error(`${gobj_short_name(gobj)}: cannot create the cell json viewer`);
1582
+ return;
1583
+ }
1584
+ priv.cell_json_gobj = json_view;
1585
+ gobj_start(json_view);
1586
+
1587
+ let $box = gobj_read_attr(json_view, "$container");
1588
+ if(!$box) {
1589
+ log_error(`${gobj_short_name(gobj)}: the cell json viewer built no $container`);
1590
+ teardown_cell_json_child(gobj);
1591
+ return;
1592
+ }
1593
+
1594
+ /* Title split in two halves, same contract as the schema dialog: the
1595
+ * record id is DATA (never translated) and the column carries its own
1596
+ * i18n key — the shared `col.id` one, so the header re-translates on a
1597
+ * language switch and reads as the raw id where the app defines no key
1598
+ * (exactly what an untranslated column header already shows). */
1599
+ priv.cell_json_modal = yui_shell_show_modal(shell, $box, {
1600
+ dialog: true,
1601
+ logical_class: "TREEDB_CELL_JSON_SHEET",
1602
+ title_prefix: String(row_id),
1603
+ title: col_id,
1604
+ t: t,
1605
+ on_close: function() {
1606
+ teardown_cell_json_child(gobj);
1607
+ }
1608
+ });
1609
+
1610
+ /* EV_SET_JSON and not the `json_data` attr: the attr renders the
1611
+ * tree but leaves the viewer in ST_IDLE, where a click to expand a
1612
+ * node is an event nobody handles. */
1613
+ gobj_send_event(json_view, "EV_SET_JSON", {json: value}, gobj);
1614
+ }
1615
+
1616
+ /************************************************************
1617
+ * Destroy the hosted C_YUI_JSON child. Called from the cell
1618
+ * modal's on_close — the shell has already removed the dialog
1619
+ * DOM and retired its Escape / history entries.
1620
+ ************************************************************/
1621
+ function teardown_cell_json_child(gobj)
1622
+ {
1623
+ let priv = gobj.priv;
1624
+ if(priv.cell_json_gobj) {
1625
+ if(gobj_is_running(priv.cell_json_gobj)) {
1626
+ gobj_stop(priv.cell_json_gobj);
1627
+ }
1628
+ gobj_destroy(priv.cell_json_gobj);
1629
+ priv.cell_json_gobj = null;
1630
+ }
1631
+ priv.cell_json_modal = null;
1632
+ }
1633
+
1634
+ /************************************************************
1635
+ * Close the cell json dialog (teardown, or a second open).
1636
+ * Closing the modal runs its on_close, which tears the
1637
+ * viewer child down.
1638
+ ************************************************************/
1639
+ function close_cell_json_dialog(gobj)
1640
+ {
1641
+ let priv = gobj.priv;
1642
+ if(priv.cell_json_modal) {
1643
+ let modal = priv.cell_json_modal;
1644
+ priv.cell_json_modal = null;
1645
+ modal.close(); // -> on_close -> teardown_cell_json_child
1646
+ return;
1647
+ }
1648
+ teardown_cell_json_child(gobj);
1649
+ }
1650
+
1331
1651
  /************************************************************
1332
1652
  * True if the topic's pkey col carries the "rowid" flag.
1333
1653
  ************************************************************/
@@ -2284,6 +2604,30 @@ function ac_refresh(gobj, event, kw, src)
2284
2604
  return 0;
2285
2605
  }
2286
2606
 
2607
+ /************************************************************
2608
+ * Show the JSON document of one cell
2609
+ * {
2610
+ * row_id:
2611
+ * col_id:
2612
+ * }
2613
+ ************************************************************/
2614
+ function ac_show_cell_json(gobj, event, kw, src)
2615
+ {
2616
+ open_cell_json_dialog(gobj, kw.row_id, kw.col_id);
2617
+
2618
+ return 0;
2619
+ }
2620
+
2621
+ /************************************************************
2622
+ * Show the schema (desc) of this topic
2623
+ ************************************************************/
2624
+ function ac_show_schema(gobj, event, kw, src)
2625
+ {
2626
+ open_schema_dialog(gobj);
2627
+
2628
+ return 0;
2629
+ }
2630
+
2287
2631
  /************************************************************
2288
2632
  * {
2289
2633
  * href: href
@@ -2356,8 +2700,10 @@ function create_gclass(gclass_name)
2356
2700
  ["EV_COPY_ROWS", ac_copy_rows, null],
2357
2701
  ["EV_PASTE_ROWS", ac_paste_rows, null],
2358
2702
  ["EV_SHOW_HOOK_DATA", ac_show_hook_data, null],
2703
+ ["EV_SHOW_CELL_JSON", ac_show_cell_json, null],
2359
2704
  ["EV_CHANGE_LOCALE", ac_change_locale, null],
2360
2705
  ["EV_REFRESH", ac_refresh, null],
2706
+ ["EV_SHOW_SCHEMA", ac_show_schema, null],
2361
2707
  ["EV_SHOW", ac_show, null],
2362
2708
  ["EV_HIDE", ac_hide, null]
2363
2709
  ]]
@@ -2382,8 +2728,10 @@ function create_gclass(gclass_name)
2382
2728
  ["EV_SELECT_ROWS", event_flag_t.EVF_OUTPUT_EVENT],
2383
2729
  ["EV_UNSELECT_ROWS", event_flag_t.EVF_OUTPUT_EVENT],
2384
2730
  ["EV_SHOW_HOOK_DATA", event_flag_t.EVF_OUTPUT_EVENT],
2731
+ ["EV_SHOW_CELL_JSON", 0],
2385
2732
  ["EV_CHANGE_LOCALE", 0],
2386
2733
  ["EV_REFRESH", 0],
2734
+ ["EV_SHOW_SCHEMA", 0],
2387
2735
 
2388
2736
  ["EV_CREATE_RECORD", event_flag_t.EVF_OUTPUT_EVENT],
2389
2737
  ["EV_UPDATE_RECORD", event_flag_t.EVF_OUTPUT_EVENT],
@@ -2425,6 +2773,10 @@ function register_c_yui_treedb_topic_with_form()
2425
2773
  if(!gclass_find_by_name("C_YUI_FORM", false)) {
2426
2774
  register_c_yui_form();
2427
2775
  }
2776
+ /* same for the schema dialog's C_YUI_JSON viewer */
2777
+ if(!gclass_find_by_name("C_YUI_JSON", false)) {
2778
+ register_c_yui_json();
2779
+ }
2428
2780
  return create_gclass(GCLASS_NAME);
2429
2781
  }
2430
2782