@yuneta/gobj-ui 7.23.167 → 7.23.169

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": "7.23.167",
3
+ "version": "7.23.169",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
@@ -0,0 +1,115 @@
1
+ /***********************************************************************
2
+ * broadcast_attrs.test.js
3
+ *
4
+ * A `broadcast_<x>_event` attr gates the publish of EV_<X>,
5
+ * and NOTHING ELSE gates it.
6
+ *
7
+ * WHY THIS IS A TEST: the two attrs come in pairs that differ by
8
+ * one word, and the guard is a string. `ac_unselect_rows()` read
9
+ * "broadcast_select_rows_event" -- the SELECT one -- so a host
10
+ * that asked only for the unselect got nothing, and a host that
11
+ * asked only for the select got both. The attr was declared, the
12
+ * event was declared, the action was wired, and the whole path
13
+ * typechecks: the only thing wrong was one word inside a string,
14
+ * in a branch no test walks and no host turns on today.
15
+ *
16
+ * The rule, stated once: every attr named `broadcast_<x>_event`
17
+ * is read by name, and the publish it guards is of EV_<X>.
18
+ *
19
+ * Copyright (c) 2026, ArtGins.
20
+ * All Rights Reserved.
21
+ ***********************************************************************/
22
+ import {describe, it, expect} from "vitest";
23
+ import {readdirSync, readFileSync} from "node:fs";
24
+ import {dirname, join} from "node:path";
25
+ import {fileURLToPath} from "node:url";
26
+
27
+ const SRC = dirname(fileURLToPath(import.meta.url));
28
+
29
+ const SOURCES = readdirSync(SRC)
30
+ .filter((f) => f.endsWith(".js") && !f.endsWith(".test.js"))
31
+ .map((f) => [f, readFileSync(join(SRC, f), "utf8")]);
32
+
33
+
34
+ /***************************************************************
35
+ * The body of the function holding `index`, by brace depth.
36
+ ***************************************************************/
37
+ function enclosing_function(src, index)
38
+ {
39
+ let start = src.lastIndexOf("\nfunction ", index);
40
+ if(start < 0) {
41
+ return "";
42
+ }
43
+ let open = src.indexOf("{", start);
44
+ if(open < 0 || open > index) {
45
+ return "";
46
+ }
47
+ let depth = 0;
48
+ for(let k = open; k < src.length; k++) {
49
+ if(src[k] === "{") {
50
+ depth++;
51
+ } else if(src[k] === "}") {
52
+ depth--;
53
+ if(depth === 0) {
54
+ return src.slice(open, k + 1);
55
+ }
56
+ }
57
+ }
58
+ return "";
59
+ }
60
+
61
+
62
+ describe("broadcast_<x>_event attrs", function() {
63
+ it("every one of them is read by its own name", function() {
64
+ let unread = [];
65
+ for(const [file, src] of SOURCES) {
66
+ const attrs = [...src.matchAll(
67
+ /^SDATA\(\s*data_type_t\.DTP_BOOLEAN\s*,\s*"(broadcast_[a-z0-9_]+_event)"/gmi
68
+ )].map((m) => m[1]);
69
+ for(const attr of new Set(attrs)) {
70
+ if(!src.includes(`gobj_read_bool_attr(gobj, "${attr}")`)) {
71
+ unread.push(`${file}: ${attr}`);
72
+ }
73
+ }
74
+ }
75
+ expect(unread).toEqual([]);
76
+ });
77
+
78
+ it("the publish each one guards is of the event it names", function() {
79
+ let wrong = [];
80
+ for(const [file, src] of SOURCES) {
81
+ const attrs = [...src.matchAll(
82
+ /^SDATA\(\s*data_type_t\.DTP_BOOLEAN\s*,\s*"(broadcast_[a-z0-9_]+_event)"/gmi
83
+ )].map((m) => m[1]);
84
+ for(const attr of new Set(attrs)) {
85
+ /* broadcast_unselect_rows_event -> EV_UNSELECT_ROWS */
86
+ const event = "EV_" + attr
87
+ .replace(/^broadcast_/, "")
88
+ .replace(/_event$/, "")
89
+ .toUpperCase();
90
+ const guard = `gobj_read_bool_attr(gobj, "${attr}")`;
91
+ let at = src.indexOf(guard);
92
+ if(at < 0) {
93
+ continue; /* the first test already reports it */
94
+ }
95
+ /* The action publishes `event`, the variable the FSM
96
+ * handed it -- so what proves the pairing is the
97
+ * event the FSM routes to this action. */
98
+ const body = enclosing_function(src, at);
99
+ const action = src.slice(0, at).match(
100
+ /\nfunction (ac_[a-z0-9_]+)\s*\(/g
101
+ );
102
+ const name = action? action[action.length - 1].trim()
103
+ .replace(/^function /, "").replace(/\s*\($/, "") : "";
104
+ const routed = new RegExp(
105
+ `\\["${event}"\\s*,\\s*${name}\\s*,`
106
+ ).test(src);
107
+ const publishes = /gobj_publish_event\(gobj, event, kw\)/.test(body);
108
+ if(!(name && publishes && routed)) {
109
+ wrong.push(`${file}: ${attr} does not gate ${event}`);
110
+ }
111
+ }
112
+ }
113
+ expect(wrong).toEqual([]);
114
+ });
115
+ });
@@ -1014,6 +1014,15 @@ function build_graph(gobj)
1014
1014
 
1015
1015
  priv.graph_rendered = false;
1016
1016
  graph_render(gobj).then(() => {
1017
+ /*
1018
+ * render() settles on its own time, and the view can be gone
1019
+ * by then: mt_destroy() destroys the graph and nulls
1020
+ * priv.graph, so configure_events() would wire handlers onto
1021
+ * null. A tab closed right after it was opened is enough.
1022
+ */
1023
+ if(gobj_is_destroying(gobj) || !priv.graph) {
1024
+ return;
1025
+ }
1017
1026
  configure_events(gobj);
1018
1027
  configure_behaviour(gobj);
1019
1028
  configure_plugins(gobj);
@@ -1023,9 +1032,41 @@ function build_graph(gobj)
1023
1032
  * so deferred mode/layout/theme changes can reconfigure.
1024
1033
  */
1025
1034
  priv.graph_rendered = true;
1035
+ }).catch((e) => {
1036
+ /*
1037
+ * Without this the graph stays blank and the only trace is
1038
+ * an unhandled rejection in the browser console, which says
1039
+ * nothing about WHO failed to render.
1040
+ */
1041
+ log_error(`${gobj_short_name(gobj)}: graph render failed: ${e}`);
1026
1042
  });
1027
1043
  }
1028
1044
 
1045
+ /************************************************************
1046
+ * A G6 event, as PLAIN JSON.
1047
+ *
1048
+ * A kw is dumped by the `machine` trace (`trace_json(kw)`), and a
1049
+ * G6 / @antv/g event is circular: serializing one THROWS, so the
1050
+ * first thing a kw carrying `evt` breaks is the trace the FSM
1051
+ * exists to feed. It also drags a live graph element into a
1052
+ * message that may be read after that element is gone.
1053
+ *
1054
+ * What the actions read of it is an id and three scalars. The
1055
+ * event itself never leaves the callback that received it.
1056
+ ************************************************************/
1057
+ function g6_event_kw(evt)
1058
+ {
1059
+ let target = (evt && evt.target)? evt.target : null;
1060
+ let client = (evt && evt.client)? evt.client : null;
1061
+
1062
+ return {
1063
+ id: (target && target.id)? String(target.id) : "",
1064
+ shift: !!(evt && evt.shiftKey),
1065
+ client_x: (client && typeof client.x === "number")? client.x : 0,
1066
+ client_y: (client && typeof client.y === "number")? client.y : 0
1067
+ };
1068
+ }
1069
+
1029
1070
  /************************************************************
1030
1071
  * Configure G6 event handlers
1031
1072
  ************************************************************/
@@ -1042,7 +1083,7 @@ function configure_events(gobj)
1042
1083
  if(consume_long_press_click(graph)) {
1043
1084
  return;
1044
1085
  }
1045
- gobj_send_event(gobj, "EV_CANVAS_CLICK", {evt: evt}, gobj);
1086
+ gobj_send_event(gobj, "EV_CANVAS_CLICK", g6_event_kw(evt), gobj);
1046
1087
  });
1047
1088
 
1048
1089
  graph.on(NodeEvent.DRAG, (evt) => {
@@ -1055,29 +1096,29 @@ function configure_events(gobj)
1055
1096
  });
1056
1097
 
1057
1098
  graph.on(NodeEvent.DRAG_END, (evt) => {
1058
- gobj_send_event(gobj, "EV_NODE_DRAG_END", {evt: evt}, gobj);
1099
+ gobj_send_event(gobj, "EV_NODE_DRAG_END", g6_event_kw(evt), gobj);
1059
1100
  });
1060
1101
 
1061
1102
  graph.on(NodeEvent.CLICK, (evt) => {
1062
1103
  if(consume_long_press_click(graph)) {
1063
1104
  return;
1064
1105
  }
1065
- gobj_send_event(gobj, "EV_NODE_CLICK", {evt: evt}, gobj);
1106
+ gobj_send_event(gobj, "EV_NODE_CLICK", g6_event_kw(evt), gobj);
1066
1107
  });
1067
1108
 
1068
1109
  graph.on(NodeEvent.CONTEXT_MENU, (evt) => {
1069
- gobj_send_event(gobj, "EV_NODE_CONTEXT_MENU", {evt: evt}, gobj);
1110
+ gobj_send_event(gobj, "EV_NODE_CONTEXT_MENU", g6_event_kw(evt), gobj);
1070
1111
  });
1071
1112
 
1072
1113
  graph.on(NodeEvent.DBLCLICK, (evt) => {
1073
- gobj_send_event(gobj, "EV_NODE_DBLCLICK", {evt: evt}, gobj);
1114
+ gobj_send_event(gobj, "EV_NODE_DBLCLICK", g6_event_kw(evt), gobj);
1074
1115
  });
1075
1116
 
1076
1117
  graph.on(EdgeEvent.CLICK, (evt) => {
1077
1118
  if(consume_long_press_click(graph)) {
1078
1119
  return;
1079
1120
  }
1080
- gobj_send_event(gobj, "EV_EDGE_CLICK", {evt: evt}, gobj);
1121
+ gobj_send_event(gobj, "EV_EDGE_CLICK", g6_event_kw(evt), gobj);
1081
1122
  });
1082
1123
 
1083
1124
  graph.on('aftertransform', () => {
@@ -1281,8 +1322,13 @@ function configure_plugins(gobj)
1281
1322
  'contextmenu',
1282
1323
  {
1283
1324
  trigger: 'contextmenu',
1325
+ /* The plugin's callback only turns the pick into an
1326
+ * EVENT: what the items DO -- unlink an edge, open a
1327
+ * form, delete -- belongs in an action, where the
1328
+ * `machine` trace can see it. */
1284
1329
  onClick: (value) => {
1285
- handle_context_menu_click(gobj, value);
1330
+ gobj_send_event(gobj, "EV_CONTEXT_MENU_ITEM",
1331
+ {value: String(value)}, gobj);
1286
1332
  },
1287
1333
  getItems: (e) => {
1288
1334
  return build_context_menu_items(gobj, e);
@@ -6751,7 +6797,8 @@ function show_edge_icon(gobj)
6751
6797
 
6752
6798
  let icons = show_dual_icons(gobj, mid.x + 4, mid.y + floating_icon_dy(),
6753
6799
  t('edge properties'), () => toggle_edge_popover(gobj),
6754
- t('unlink'), () => request_unlink_edge(gobj)
6800
+ t('unlink'),
6801
+ () => gobj_send_event(gobj, "EV_REQUEST_UNLINK_EDGE", {}, gobj)
6755
6802
  );
6756
6803
  priv._edge_icon_el = icons.icon_el;
6757
6804
  priv._edge_delete_el = icons.delete_el;
@@ -6976,7 +7023,8 @@ function show_node_icon(gobj)
6976
7023
 
6977
7024
  let icons = show_dual_icons(gobj, rect.right + 4, rect.top + floating_icon_dy(),
6978
7025
  t('node properties'), () => toggle_node_popover(gobj),
6979
- t('delete node'), () => request_delete_node(gobj)
7026
+ t('delete node'),
7027
+ () => gobj_send_event(gobj, "EV_REQUEST_DELETE_NODE", {}, gobj)
6980
7028
  );
6981
7029
  priv._node_icon_el = icons.icon_el;
6982
7030
  priv._node_delete_el = icons.delete_el;
@@ -8108,7 +8156,10 @@ function request_delete_selection(gobj)
8108
8156
  show_confirm_popover(gobj, null,
8109
8157
  delete_question(nodes),
8110
8158
  'delete', '#ff4d4f',
8111
- () => execute_delete_selection(gobj, nodes),
8159
+ () => gobj_send_event(gobj, "EV_CONFIRMED", {
8160
+ what: "delete_selection",
8161
+ ids: nodes.map((nd) => String(nd.id))
8162
+ }, gobj),
8112
8163
  '_delete_confirm_el'
8113
8164
  );
8114
8165
  }
@@ -8145,7 +8196,10 @@ function show_delete_confirm(gobj, nodeData)
8145
8196
  show_confirm_popover(gobj, priv._node_delete_el,
8146
8197
  delete_question([nodeData]),
8147
8198
  'delete', '#ff4d4f',
8148
- () => execute_delete_node(gobj, nodeData),
8199
+ () => gobj_send_event(gobj, "EV_CONFIRMED", {
8200
+ what: "delete_node",
8201
+ id: String(nodeData.id)
8202
+ }, gobj),
8149
8203
  '_delete_confirm_el'
8150
8204
  );
8151
8205
  }
@@ -8297,7 +8351,10 @@ function show_create_popover(gobj)
8297
8351
  idInput.focus();
8298
8352
  return;
8299
8353
  }
8300
- execute_create_node(gobj, topicSelect.value, node_id);
8354
+ gobj_send_event(gobj, "EV_SUBMIT_NEW_NODE", {
8355
+ topic_name: String(topicSelect.value),
8356
+ id: node_id
8357
+ }, gobj);
8301
8358
  },
8302
8359
  },
8303
8360
  ]);
@@ -8403,7 +8460,10 @@ function show_unlink_confirm(gobj, edgeData)
8403
8460
  t('unlink') + ' ' + d.child_id + ' → ' + d.parent_id + '?' +
8404
8461
  '\n' + t('neither record is deleted'),
8405
8462
  'unlink', '#ff4d4f',
8406
- () => execute_unlink_edge(gobj, edgeData),
8463
+ () => gobj_send_event(gobj, "EV_CONFIRMED", {
8464
+ what: "unlink_edge",
8465
+ id: String(edgeData.id)
8466
+ }, gobj),
8407
8467
  '_unlink_confirm_el'
8408
8468
  );
8409
8469
  }
@@ -11155,7 +11215,7 @@ function ac_toggle_node_mode(gobj, event, kw, src)
11155
11215
  function ac_node_dblclick(gobj, event, kw, src)
11156
11216
  {
11157
11217
  let priv = gobj.priv;
11158
- let node_id = kw && kw.evt && kw.evt.target? kw.evt.target.id : "";
11218
+ let node_id = (kw && kw.id)? kw.id : "";
11159
11219
  let nd = null;
11160
11220
  try {
11161
11221
  nd = priv.graph.getNodeData(node_id);
@@ -11418,7 +11478,7 @@ function ac_node_click(gobj, event, kw, src)
11418
11478
  {
11419
11479
  let priv = gobj.priv;
11420
11480
  let graph = priv.graph;
11421
- let node_id = kw.evt.target.id;
11481
+ let node_id = (kw && kw.id)? kw.id : "";
11422
11482
 
11423
11483
  /*
11424
11484
  * A `+N` chip is one thing only: the next page of its group. It
@@ -11464,7 +11524,7 @@ function ac_node_click(gobj, event, kw, src)
11464
11524
  record: nodedata.data.record
11465
11525
  });
11466
11526
 
11467
- if(priv.edit_mode && (kw.evt.shiftKey || priv.selection_mode)) {
11527
+ if(priv.edit_mode && (kw.shift || priv.selection_mode)) {
11468
11528
  /* Shift+click extends the selection, the way it does
11469
11529
  * everywhere -- and so does a plain tap while the
11470
11530
  * toolbar's selection mode is on, which is that key
@@ -11477,8 +11537,8 @@ function ac_node_click(gobj, event, kw, src)
11477
11537
  // Convert client coords to viewport (container-relative) then to canvas
11478
11538
  let containerRect = priv.$container.getBoundingClientRect();
11479
11539
  let canvasPoint = graph.getCanvasByViewport([
11480
- kw.evt.client.x - containerRect.left,
11481
- kw.evt.client.y - containerRect.top
11540
+ kw.client_x - containerRect.left,
11541
+ kw.client_y - containerRect.top
11482
11542
  ]);
11483
11543
  let port_key = detect_port_click(
11484
11544
  gobj, node_id, canvasPoint[0], canvasPoint[1]
@@ -11520,7 +11580,7 @@ function ac_edge_click(gobj, event, kw, src)
11520
11580
  {
11521
11581
  let priv = gobj.priv;
11522
11582
  let graph = priv.graph;
11523
- let edge_id = kw.evt.target.id;
11583
+ let edge_id = (kw && kw.id)? kw.id : "";
11524
11584
 
11525
11585
  try {
11526
11586
  let edgedata = graph.getEdgeData(edge_id);
@@ -11556,6 +11616,125 @@ function ac_node_context_menu(gobj, event, kw, src)
11556
11616
  return 0;
11557
11617
  }
11558
11618
 
11619
+ /************************************************************
11620
+ * {value} -- an item of the context menu was picked.
11621
+ ************************************************************/
11622
+ function ac_context_menu_item(gobj, event, kw, src)
11623
+ {
11624
+ handle_context_menu_click(gobj, (kw && kw.value)? kw.value : "");
11625
+ return 0;
11626
+ }
11627
+
11628
+ /************************************************************
11629
+ * The delete icon floating beside the selected node.
11630
+ ************************************************************/
11631
+ function ac_request_delete_node(gobj, event, kw, src)
11632
+ {
11633
+ request_delete_node(gobj);
11634
+ return 0;
11635
+ }
11636
+
11637
+ /************************************************************
11638
+ * The unlink icon floating beside the selected edge.
11639
+ ************************************************************/
11640
+ function ac_request_unlink_edge(gobj, event, kw, src)
11641
+ {
11642
+ request_unlink_edge(gobj);
11643
+ return 0;
11644
+ }
11645
+
11646
+ /************************************************************
11647
+ * {topic_name, id} -- the create popover was filled in.
11648
+ ************************************************************/
11649
+ function ac_submit_new_node(gobj, event, kw, src)
11650
+ {
11651
+ let topic_name = (kw && kw.topic_name)? kw.topic_name : "";
11652
+ let node_id = (kw && kw.id)? kw.id : "";
11653
+
11654
+ if(!topic_name || !node_id) {
11655
+ log_error(`${gobj_short_name(gobj)}: ${event} without topic or id`);
11656
+ return -1;
11657
+ }
11658
+ execute_create_node(gobj, topic_name, node_id);
11659
+ return 0;
11660
+ }
11661
+
11662
+ /************************************************************
11663
+ * {what, id|ids} -- the person said yes in a confirm popover.
11664
+ *
11665
+ * The kw carries IDS and not the graph elements they name: a kw
11666
+ * is plain json, and a node/edge datum of a live graph is not
11667
+ * something to park in a message. They are resolved here, where
11668
+ * the graph is -- and an id that no longer names anything is a
11669
+ * loud refusal instead of a silent nothing.
11670
+ ************************************************************/
11671
+ function ac_confirmed(gobj, event, kw, src)
11672
+ {
11673
+ let priv = gobj.priv;
11674
+ let graph = priv.graph;
11675
+ let what = (kw && kw.what)? kw.what : "";
11676
+
11677
+ if(!graph) {
11678
+ log_error(`${gobj_short_name(gobj)}: ${event} with no graph`);
11679
+ return -1;
11680
+ }
11681
+
11682
+ if(what === "delete_selection") {
11683
+ let ids = (kw && Array.isArray(kw.ids))? kw.ids : [];
11684
+ let nodes = [];
11685
+ for(let id of ids) {
11686
+ let nd = null;
11687
+ try {
11688
+ nd = graph.getNodeData(id);
11689
+ } catch(e) {
11690
+ nd = null;
11691
+ }
11692
+ if(nd && nd.data && nd.data.desc) {
11693
+ nodes.push(nd);
11694
+ }
11695
+ }
11696
+ if(!nodes.length) {
11697
+ log_error(`${gobj_short_name(gobj)}: ${event} names no node any more`);
11698
+ return -1;
11699
+ }
11700
+ execute_delete_selection(gobj, nodes);
11701
+ return 0;
11702
+ }
11703
+
11704
+ if(what === "delete_node") {
11705
+ let nd = null;
11706
+ try {
11707
+ nd = graph.getNodeData(kw.id);
11708
+ } catch(e) {
11709
+ nd = null;
11710
+ }
11711
+ if(!nd || !nd.data || !nd.data.desc) {
11712
+ log_error(`${gobj_short_name(gobj)}: ${event} names no node: ${kw.id}`);
11713
+ return -1;
11714
+ }
11715
+ execute_delete_node(gobj, nd);
11716
+ return 0;
11717
+ }
11718
+
11719
+ if(what === "unlink_edge") {
11720
+ let ed = null;
11721
+ try {
11722
+ ed = graph.getEdgeData(kw.id);
11723
+ } catch(e) {
11724
+ ed = null;
11725
+ }
11726
+ if(!ed || !ed.data) {
11727
+ log_error(`${gobj_short_name(gobj)}: ${event} names no edge: ${kw.id}`);
11728
+ return -1;
11729
+ }
11730
+ execute_unlink_edge(gobj, ed);
11731
+ return 0;
11732
+ }
11733
+
11734
+ log_error(`${gobj_short_name(gobj)}: ${event} of nothing this view asked about: ${what}`);
11735
+ return -1;
11736
+ }
11737
+
11559
11738
  /************************************************************
11560
11739
  * Canvas click
11561
11740
  ************************************************************/
@@ -11757,6 +11936,11 @@ function create_gclass(gclass_name)
11757
11936
  ["EV_NODE_CLICK", ac_node_click, null],
11758
11937
  ["EV_EDGE_CLICK", ac_edge_click, null],
11759
11938
  ["EV_NODE_CONTEXT_MENU", ac_node_context_menu, null],
11939
+ ["EV_CONTEXT_MENU_ITEM", ac_context_menu_item, null],
11940
+ ["EV_REQUEST_DELETE_NODE", ac_request_delete_node, null],
11941
+ ["EV_REQUEST_UNLINK_EDGE", ac_request_unlink_edge, null],
11942
+ ["EV_SUBMIT_NEW_NODE", ac_submit_new_node, null],
11943
+ ["EV_CONFIRMED", ac_confirmed, null],
11760
11944
  ["EV_CANVAS_CLICK", ac_canvas_click, null],
11761
11945
  ["EV_NODE_DRAG_END", ac_node_drag_end, null],
11762
11946
  ["EV_BRUSH_SELECT", ac_brush_select, null],
@@ -11821,6 +12005,11 @@ function create_gclass(gclass_name)
11821
12005
  ["EV_NODE_CLICK", 0],
11822
12006
  ["EV_EDGE_CLICK", 0],
11823
12007
  ["EV_NODE_CONTEXT_MENU", 0],
12008
+ ["EV_CONTEXT_MENU_ITEM", 0],
12009
+ ["EV_REQUEST_DELETE_NODE", 0],
12010
+ ["EV_REQUEST_UNLINK_EDGE", 0],
12011
+ ["EV_SUBMIT_NEW_NODE", 0],
12012
+ ["EV_CONFIRMED", 0],
11824
12013
  ["EV_CANVAS_CLICK", 0],
11825
12014
  ["EV_NODE_DRAG_END", 0],
11826
12015
  ["EV_BRUSH_SELECT", 0],
@@ -1335,7 +1335,10 @@ function request_print_tranger(gobj, path)
1335
1335
  expanded: 1,
1336
1336
  lists_limit: 100,
1337
1337
  dicts_limit: 100,
1338
- path: path || ""
1338
+ path: path || "",
1339
+ /* What comes BACK: the answer carries only this frame, and
1340
+ * without the path a drill replaced the whole document. */
1341
+ __md_command__: {path: path || ""}
1339
1342
  }, gobj);
1340
1343
  if(ret) {
1341
1344
  log_error(ret);