@yuneta/gobj-ui 6.1.0 → 6.1.2

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/README.md CHANGED
@@ -539,12 +539,15 @@ schema, so the drawing can be held against the ASCII one in its `.c`.
539
539
  - Events: `EV_SHOW`, `EV_REBUILD`, `EV_THEME` (restyle — it repaints the G6
540
540
  graph in place, preserving the user's zoom/pan), plus `EV_NODE_CLICK`, which
541
541
  a node click sends into the FSM. **With a `node_route` the click IS a
542
- navigation** and this view makes it; **without one the click is published**
543
- (`{topic}`) for whoever mounted the view a host that draws the same picture
544
- inside its own screens opens the topic in place, with no hash involved. A
545
- host that subscribes must declare `EV_NODE_CLICK` in its own FSM, as with
546
- every event a child publishes. Since 6.1.0; before that a click with no route
547
- was dropped.
542
+ navigation** and this view makes it. **Without one the click is dropped,
543
+ unless the host asked for it with `with_node_click`** then it is published
544
+ as `{topic}`, for a host that draws the same picture inside its own screens
545
+ and opens the topic in place, with no hash involved. That host must declare
546
+ `EV_NODE_CLICK` in its own FSM, as with every event a child publishes, which
547
+ is exactly why it is opt-in: the CHILD subscription model subscribes a host
548
+ to ALL of this view's events, so publishing one unasked turns a click into
549
+ "Event NOT DEFINED in state" underneath a host that never wanted it. Since
550
+ 6.1.2 (6.1.0 and 6.1.1 published it unconditionally).
548
551
 
549
552
  Barrel-exported and public from 4.0.0. Renders with `@antv/g6`; the cards are
550
553
  HTML nodes carrying their own inline colours, so a theme switch repaints them in
@@ -631,11 +634,21 @@ else.
631
634
  `ST_LOADING`, `ST_EMPTY`, `ST_TREEDBS`, `ST_TOPICS`, `ST_DIAGRAM`,
632
635
  `ST_COLUMNS`, `ST_SAVING`.
633
636
 
637
+ **Three words decide whether a write does what it says** (`schema_write_options.js`,
638
+ and they are tested because getting one wrong costs a store repaired by hand):
639
+ a create goes through `update-node` and not `create-node`, because only that
640
+ path carries **`autolink`** — and without a link a new column belongs to no
641
+ topic. `autolink` rewrites a node's links from the fkey fields the record
642
+ carries, so it goes with a create, where the record has one, **and with nothing
643
+ else**: on a partial update it finds none, reads that as "no parents", and
644
+ detaches the node. Raising a topic's version with it on unlinked that topic
645
+ from its treedb, and the write answered success.
646
+
634
647
  The logic is pure and tested apart from the view: `schema_model.js` (the three
635
648
  lists regrouped — grouping follows the **fkey**, not a split of the qualified
636
649
  id on `.`, which works right up to the first name that carries one),
637
650
  `schema_validate.js`, `schema_descs.js`, `schema_to_c.js`, `schema_import.js`,
638
- `schema_flags.js`. Since 6.1.0.
651
+ `schema_flags.js`, `schema_write_options.js`. Since 6.1.0.
639
652
 
640
653
  ### Frontend view — `setup_frontend_view`
641
654
 
@@ -19831,6 +19831,7 @@ var attrs_table$3 = [
19831
19831
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_POINTER, "subscriber", 0, null, "Subscriber of output events"),
19832
19832
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_JSON, "descs", 0, null, "Treedb schema: {topic_name: desc}"),
19833
19833
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "node_route", 0, "", "Hash-route template with a {topic} placeholder: a node click opens that topic (e.g. '#/topics/db/<sel>/{topic}')"),
19834
+ (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_node_click", 0, false, "With no `node_route`, PUBLISH a node click as EV_NODE_CLICK instead of dropping it. Opt-in, because a subscriber has to DECLARE the event: the hosts that route by hash, and the ones that want nothing, must not have it appear underneath them"),
19834
19835
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "system", 0, false, "Include system topics (__*__) too"),
19835
19836
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_POINTER, "$container", 0, null, "Root HTML element"),
19836
19837
  (0, _yuneta_gobj_js.SDATA_END)()
@@ -20235,19 +20236,26 @@ function destroy_graph(gobj) {
20235
20236
  * With a `node_route` the click IS a navigation and this view
20236
20237
  * makes it: that is what the route was handed down for.
20237
20238
  *
20238
- * Without one it is still an action, and it belongs to whoever
20239
- * mounted this view — the schema editor draws the same picture
20240
- * inside its own screens, where a topic opens in place and no
20241
- * hash is involved. So the click is published rather than
20242
- * dropped. A host that subscribes must DECLARE EV_NODE_CLICK in
20243
- * its own FSM, as with every event a child publishes.
20239
+ * Without one it is still an action, and it can belong to
20240
+ * whoever mounted this view — the schema editor draws the same
20241
+ * picture inside its own screens, where a topic opens in place
20242
+ * and no hash is involved. That host asks for it with
20243
+ * `with_node_click` and DECLARES EV_NODE_CLICK in its own FSM,
20244
+ * as with every event a child publishes.
20245
+ *
20246
+ * It is opt-in and not the default for exactly that reason: the
20247
+ * CHILD subscription model subscribes the host to ALL of this
20248
+ * view's events, so publishing one unasked turns a click into
20249
+ * "Event NOT DEFINED in state" in every host that never wanted
20250
+ * it — the topics view that mounts this as its schema landing,
20251
+ * and the offline demo that mounts it against a json file.
20244
20252
  ************************************************************/
20245
20253
  function ac_node_click$2(gobj, event, kw, src) {
20246
20254
  let topic = kw && kw.node_id;
20247
20255
  let route = (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "node_route");
20248
20256
  if (!topic) return 0;
20249
20257
  if (!route) {
20250
- (0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_NODE_CLICK", { topic });
20258
+ if ((0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "with_node_click")) (0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_NODE_CLICK", { topic });
20251
20259
  return 0;
20252
20260
  }
20253
20261
  let href = route.replace("{topic}", topic);
@@ -20587,6 +20595,22 @@ function build_schema_model(records) {
20587
20595
  };
20588
20596
  }
20589
20597
  /***************************************************************
20598
+ * Is there a value here at all?
20599
+ *
20600
+ * The store answers a `blob` column that was never set with an
20601
+ * EMPTY COLLECTION, not with nothing: `enum: {}`, `hook: {}`,
20602
+ * `default: {}`. Read as "a value", those turn every export into
20603
+ * a schema full of empty objects — and `'hook': {}` in a literal
20604
+ * is a hook with no mapping, which is a link the treedb builds
20605
+ * and nothing writes.
20606
+ ***************************************************************/
20607
+ function is_empty_value(value) {
20608
+ if (value === null || value === void 0 || value === "") return true;
20609
+ if (Array.isArray(value)) return value.length === 0;
20610
+ if (typeof value === "object") return Object.keys(value).length === 0;
20611
+ return false;
20612
+ }
20613
+ /***************************************************************
20590
20614
  * The fields of a column that are DECLARED as one thing and
20591
20615
  * STORED as another.
20592
20616
  *
@@ -20924,6 +20948,7 @@ var COL_STORAGE_FIELDS = [
20924
20948
  "value",
20925
20949
  "topics",
20926
20950
  "order",
20951
+ "_geometry",
20927
20952
  "__md_treedb__"
20928
20953
  ];
20929
20954
  var TOPIC_STORAGE_FIELDS = [
@@ -20932,6 +20957,7 @@ var TOPIC_STORAGE_FIELDS = [
20932
20957
  "treedbs",
20933
20958
  "cols",
20934
20959
  "order",
20960
+ "_geometry",
20935
20961
  "__md_treedb__"
20936
20962
  ];
20937
20963
  var COL_JSON_FIELDS = [
@@ -20955,13 +20981,16 @@ function col_desc(col) {
20955
20981
  let desc = {};
20956
20982
  for (let [key, value] of Object.entries(record)) {
20957
20983
  if (COL_STORAGE_FIELDS.indexOf(key) >= 0) continue;
20958
- if (value === null || value === void 0 || value === "") continue;
20959
- desc[key] = COL_JSON_FIELDS.indexOf(key) >= 0 ? as_json(value) : value;
20984
+ if (is_empty_value(value)) continue;
20985
+ let read = COL_JSON_FIELDS.indexOf(key) >= 0 ? as_json(value) : value;
20986
+ if (is_empty_value(read)) continue;
20987
+ desc[key] = read;
20960
20988
  }
20961
20989
  desc.id = col.name;
20962
20990
  desc.flag = col_flags(record);
20963
20991
  let hook = col_hook(record);
20964
- if (hook) desc.hook = hook;
20992
+ if (hook && !is_empty_value(hook)) desc.hook = hook;
20993
+ else delete desc.hook;
20965
20994
  let e = col_enum(record);
20966
20995
  if (e.length > 0) desc.enum = e;
20967
20996
  return desc;
@@ -20979,7 +21008,7 @@ function topic_descs(treedb) {
20979
21008
  let desc = {};
20980
21009
  for (let [key, value] of Object.entries(record)) {
20981
21010
  if (TOPIC_STORAGE_FIELDS.indexOf(key) >= 0) continue;
20982
- if (value === null || value === void 0 || value === "") continue;
21011
+ if (is_empty_value(value)) continue;
20983
21012
  desc[key] = value;
20984
21013
  }
20985
21014
  desc.topic_name = topic.name;
@@ -21036,6 +21065,7 @@ var COL_SKIP = [
21036
21065
  "value",
21037
21066
  "topics",
21038
21067
  "order",
21068
+ "_geometry",
21039
21069
  "__md_treedb__"
21040
21070
  ];
21041
21071
  var TOPIC_SKIP = [
@@ -21044,6 +21074,7 @@ var TOPIC_SKIP = [
21044
21074
  "treedbs",
21045
21075
  "cols",
21046
21076
  "order",
21077
+ "_geometry",
21047
21078
  "__md_treedb__"
21048
21079
  ];
21049
21080
  var JSON_FIELDS$1 = [
@@ -21097,8 +21128,10 @@ function schema_to_json(treedb) {
21097
21128
  let jn_topic = { id: topic.name };
21098
21129
  for (let [key, value] of Object.entries(record)) {
21099
21130
  if (TOPIC_SKIP.indexOf(key) >= 0) continue;
21100
- if (value === null || value === void 0 || value === "") continue;
21101
- jn_topic[key] = JSON_FIELDS$1.indexOf(key) >= 0 ? as_json(value) : value;
21131
+ if (is_empty_value(value)) continue;
21132
+ let read = JSON_FIELDS$1.indexOf(key) >= 0 ? as_json(value) : value;
21133
+ if (is_empty_value(read)) continue;
21134
+ jn_topic[key] = read;
21102
21135
  }
21103
21136
  if (jn_topic.topic_version !== void 0) jn_topic.topic_version = version(jn_topic.topic_version);
21104
21137
  let pkey2s = topic_pkey2s(record);
@@ -21111,14 +21144,17 @@ function schema_to_json(treedb) {
21111
21144
  let jn_col = {};
21112
21145
  for (let [key, value] of Object.entries(col_record)) {
21113
21146
  if (COL_SKIP.indexOf(key) >= 0) continue;
21114
- if (value === null || value === void 0 || value === "") continue;
21115
- jn_col[key] = JSON_FIELDS$1.indexOf(key) >= 0 ? as_json(value) : value;
21147
+ if (is_empty_value(value)) continue;
21148
+ let read = JSON_FIELDS$1.indexOf(key) >= 0 ? as_json(value) : value;
21149
+ if (is_empty_value(read)) continue;
21150
+ jn_col[key] = read;
21116
21151
  }
21117
21152
  let flags = col_flags(col_record);
21118
21153
  if (flags.length > 0) jn_col.flag = flags;
21119
21154
  else delete jn_col.flag;
21120
21155
  let hook = col_hook(col_record);
21121
- if (hook) jn_col.hook = hook;
21156
+ if (hook && !is_empty_value(hook)) jn_col.hook = hook;
21157
+ else delete jn_col.hook;
21122
21158
  let e = col_enum(col_record);
21123
21159
  if (e.length > 0) jn_col.enum = e;
21124
21160
  let ordered_col = {};
@@ -21303,7 +21339,7 @@ function same_value(field, a, b) {
21303
21339
  list = list.filter((f) => typeof f === "string" && f.length > 0).sort();
21304
21340
  return JSON.stringify(list);
21305
21341
  }
21306
- if (v === null || v === void 0 || v === "") return "";
21342
+ if (is_empty_value(v)) return "";
21307
21343
  if (typeof v === "object") return JSON.stringify(v);
21308
21344
  return String(v);
21309
21345
  };
@@ -21316,8 +21352,10 @@ function incoming_col_fields(col) {
21316
21352
  let out = {};
21317
21353
  for (let field of COL_FIELDS) {
21318
21354
  let value = col ? col[field] : void 0;
21319
- if (value === void 0 || value === null || value === "") continue;
21320
- out[field] = JSON_FIELDS.indexOf(field) >= 0 ? as_json(value) : value;
21355
+ if (is_empty_value(value)) continue;
21356
+ let read = JSON_FIELDS.indexOf(field) >= 0 ? as_json(value) : value;
21357
+ if (is_empty_value(read)) continue;
21358
+ out[field] = read;
21321
21359
  }
21322
21360
  if (Array.isArray(out.flag)) out.flag = out.flag.filter((f) => typeof f === "string" && f.length > 0);
21323
21361
  else if (typeof out.flag === "string" && out.flag) out.flag = [out.flag];
@@ -21330,14 +21368,17 @@ function stored_col_fields(record) {
21330
21368
  let out = {};
21331
21369
  for (let field of COL_FIELDS) {
21332
21370
  let value = record ? record[field] : void 0;
21333
- if (value === void 0 || value === null || value === "") continue;
21334
- out[field] = JSON_FIELDS.indexOf(field) >= 0 ? as_json(value) : value;
21371
+ if (is_empty_value(value)) continue;
21372
+ let read = JSON_FIELDS.indexOf(field) >= 0 ? as_json(value) : value;
21373
+ if (is_empty_value(read)) continue;
21374
+ out[field] = read;
21335
21375
  }
21336
21376
  let flags = col_flags(record);
21337
21377
  if (flags.length > 0) out.flag = flags;
21338
21378
  else delete out.flag;
21339
21379
  let hook = col_hook(record);
21340
- if (hook) out.hook = hook;
21380
+ if (hook && !is_empty_value(hook)) out.hook = hook;
21381
+ else delete out.hook;
21341
21382
  let e = col_enum(record);
21342
21383
  if (e.length > 0) out.enum = e;
21343
21384
  return out;
@@ -21962,6 +22003,61 @@ function toggle_flag(current, name, on) {
21962
22003
  return out;
21963
22004
  }
21964
22005
  //#endregion
22006
+ //#region src/schema_write_options.js
22007
+ /***********************************************************************
22008
+ * schema_write_options.js
22009
+ *
22010
+ * The OPTIONS a schema write travels with, and why each one.
22011
+ *
22012
+ * Three words decide whether a write does what it says, and all
22013
+ * three are easy to get wrong in a way that succeeds:
22014
+ *
22015
+ * `create` a node that is not there yet is written by
22016
+ * `update-node` with this, not by `create-node`. The
22017
+ * two are not the same call: only the update path
22018
+ * carries `autolink`, and without a link a new column
22019
+ * belongs to no topic.
22020
+ *
22021
+ * `autolink` turns the fkey carried IN THE RECORD into a link.
22022
+ * It rewrites the node's links from the fkey fields
22023
+ * the record has — so on a PARTIAL update, which
22024
+ * carries none, it reads that as "no parents" and
22025
+ * UNLINKS the node. Raising a topic's version with it
22026
+ * on detached that topic from its treedb, and the
22027
+ * write returned success. It goes with a create,
22028
+ * where the record carries its fkey, and with nothing
22029
+ * else.
22030
+ *
22031
+ * `force` a delete of a node something points at. A column is
22032
+ * pointed at by the topic that owns it, which is not
22033
+ * a reason to refuse.
22034
+ *
22035
+ * Pure and tested, because the cost of getting it wrong is a
22036
+ * store that has to be repaired by hand, and nothing in the
22037
+ * answer says it happened.
22038
+ *
22039
+ * Copyright (c) 2026, ArtGins.
22040
+ * All Rights Reserved.
22041
+ ***********************************************************************/
22042
+ /***************************************************************
22043
+ * write_command(op) -> the treedb command that performs it
22044
+ ***************************************************************/
22045
+ function write_command(op) {
22046
+ return op === "delete" ? "delete-node" : "update-node";
22047
+ }
22048
+ /***************************************************************
22049
+ * write_options(op) -> the options it travels with
22050
+ ***************************************************************/
22051
+ function write_options(op) {
22052
+ if (op === "delete") return { force: true };
22053
+ if (op === "create") return {
22054
+ list_dict: true,
22055
+ create: true,
22056
+ autolink: true
22057
+ };
22058
+ return { list_dict: true };
22059
+ }
22060
+ //#endregion
21965
22061
  //#region src/c_yui_schema_editor.js
21966
22062
  /***********************************************************************
21967
22063
  * c_yui_schema_editor.js
@@ -22281,10 +22377,12 @@ function remote_command(gobj, command, kw) {
22281
22377
  service: treedb_name,
22282
22378
  treedb_name
22283
22379
  }, kw || {});
22284
- full_kw.__md_command__ = Object.assign({
22380
+ full_kw.__md_command__ = {
22285
22381
  purpose: PURPOSE,
22286
- command
22287
- }, kw && kw.topic_name ? { topic_name: kw.topic_name } : {});
22382
+ topic_name: kw && kw.topic_name || "",
22383
+ record_id: kw && kw.record_id || ""
22384
+ };
22385
+ delete full_kw.record_id;
22288
22386
  let ret = (0, _yuneta_gobj_js.gobj_command)(remote, command, full_kw, gobj);
22289
22387
  if (ret) {
22290
22388
  (0, _yuneta_gobj_js.log_error)(ret);
@@ -22533,9 +22631,9 @@ function render_toolbar(gobj) {
22533
22631
  let $right = [];
22534
22632
  if (priv.treedb_id && has_model) {
22535
22633
  $right.push(toolbar_button("SCHEMA_DIAGRAM_BTN", "yi-hexagon-nodes", priv.diagram ? "topics" : "diagram", "EV_TOGGLE_DIAGRAM", false));
22536
- $right.push(toolbar_button("SCHEMA_VALIDATE_BTN", "yi-circle-check", "check", "EV_VALIDATE", false));
22537
- $right.push(toolbar_button("SCHEMA_EXPORT_BTN", "yi-file-export", "export", "EV_EXPORT", false));
22538
- if (!readonly) $right.push(toolbar_button("SCHEMA_IMPORT_BTN", "yi-file-import", "import", "EV_IMPORT", false));
22634
+ $right.push(toolbar_button("SCHEMA_VALIDATE_BTN", "yi-square-check", "check", "EV_VALIDATE", false));
22635
+ $right.push(toolbar_button("SCHEMA_EXPORT_BTN", "yi-download", "export", "EV_EXPORT", false));
22636
+ if (!readonly) $right.push(toolbar_button("SCHEMA_IMPORT_BTN", "yi-upload", "import", "EV_IMPORT", false));
22539
22637
  }
22540
22638
  if (priv.topic_name && !readonly && !priv.diagram) $right.push(toolbar_button("SCHEMA_ADD_COL_BTN", "yi-plus", "new column", "EV_ADD_COLUMN", false));
22541
22639
  if (priv.treedb_id && !priv.topic_name && !readonly && !priv.diagram) $right.push(toolbar_button("SCHEMA_ADD_TOPIC_BTN", "yi-plus", "new topic", "EV_ADD_TOPIC", false));
@@ -22738,13 +22836,16 @@ function render_topics(gobj, $body) {
22738
22836
  return;
22739
22837
  }
22740
22838
  let readonly = is_readonly(gobj);
22839
+ let twice = repeated_names(treedb.topics);
22741
22840
  let $rows = [];
22742
22841
  for (let topic of treedb.topics) {
22743
22842
  let pending = priv.written[topic.id] && String(priv.baseline[topic.id]) === String(topic.topic_version);
22843
+ let ambiguous = !!twice[topic.name];
22844
+ let address = ambiguous ? topic.id : topic.name;
22744
22845
  let $actions = [];
22745
22846
  if (!readonly) {
22746
- $actions.push(row_icon(gobj, "SCHEMA_TOPIC_EDIT", "yi-pen", "edit topic", "EV_EDIT_TOPIC", { topic: topic.name }));
22747
- $actions.push(row_icon(gobj, "SCHEMA_TOPIC_DELETE", "yi-trash", "delete topic", "EV_DELETE_TOPIC", { topic: topic.name }));
22847
+ $actions.push(row_icon(gobj, "SCHEMA_TOPIC_EDIT", "yi-pen", "edit topic", "EV_EDIT_TOPIC", { topic: address }));
22848
+ $actions.push(row_icon(gobj, "SCHEMA_TOPIC_DELETE", "yi-trash", "delete topic", "EV_DELETE_TOPIC", { topic: address }));
22748
22849
  }
22749
22850
  $rows.push([
22750
22851
  "tr",
@@ -22757,22 +22858,38 @@ function render_topics(gobj, $body) {
22757
22858
  [
22758
22859
  "td",
22759
22860
  { class: "SCHEMA_TOPIC_NAME" },
22760
- [[
22761
- "span",
22762
- { class: "has-text-weight-semibold" },
22763
- `${topic.name}`
22764
- ], topic.system_topic ? [
22765
- "span",
22766
- {
22767
- class: "SCHEMA_TOPIC_SYSTEM tag is-light ml-2",
22768
- i18n: "system"
22769
- },
22770
- (0, i18next.t)("system")
22771
- ] : [
22772
- "span",
22773
- {},
22774
- ""
22775
- ]]
22861
+ [
22862
+ [
22863
+ "span",
22864
+ { class: "has-text-weight-semibold" },
22865
+ `${topic.name}`
22866
+ ],
22867
+ topic.system_topic ? [
22868
+ "span",
22869
+ {
22870
+ class: "SCHEMA_TOPIC_SYSTEM tag is-light ml-2",
22871
+ i18n: "system"
22872
+ },
22873
+ (0, i18next.t)("system")
22874
+ ] : [
22875
+ "span",
22876
+ {},
22877
+ ""
22878
+ ],
22879
+ ambiguous ? [
22880
+ "div",
22881
+ { class: "SCHEMA_TOPIC_ID is-size-7 has-text-grey" },
22882
+ [[
22883
+ "code",
22884
+ {},
22885
+ `${topic.id}`
22886
+ ]]
22887
+ ] : [
22888
+ "span",
22889
+ {},
22890
+ ""
22891
+ ]
22892
+ ]
22776
22893
  ],
22777
22894
  [
22778
22895
  "td",
@@ -22806,12 +22923,12 @@ function render_topics(gobj, $body) {
22806
22923
  {
22807
22924
  click: (evt) => {
22808
22925
  if (evt.target.closest(".SCHEMA_TOPIC_ACTIONS")) return;
22809
- (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_SELECT_TOPIC", { topic: topic.name }, gobj);
22926
+ (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_SELECT_TOPIC", { topic: address }, gobj);
22810
22927
  },
22811
22928
  keydown: (evt) => {
22812
22929
  if (evt.key !== "Enter" && evt.key !== " ") return;
22813
22930
  evt.preventDefault();
22814
- (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_SELECT_TOPIC", { topic: topic.name }, gobj);
22931
+ (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_SELECT_TOPIC", { topic: address }, gobj);
22815
22932
  }
22816
22933
  }
22817
22934
  ]);
@@ -22900,6 +23017,26 @@ function row_icon(gobj, logical, icon, key, event, kw) {
22900
23017
  ];
22901
23018
  }
22902
23019
  /***************************************************************
23020
+ * Which names appear more than once.
23021
+ *
23022
+ * A store can hold TWO GENERATIONS of the same schema: the
23023
+ * projector never deletes, so a treedb re-keyed by a newer SDK
23024
+ * keeps its old rowid-keyed rows next to the qualified ones, and
23025
+ * both are children of the same treedb with the same name. Drawn
23026
+ * as two identical rows that behave differently, that reads as a
23027
+ * bug in the editor; drawn with what tells them apart, it reads
23028
+ * as what it is.
23029
+ ***************************************************************/
23030
+ function repeated_names(list) {
23031
+ let seen = {};
23032
+ let twice = {};
23033
+ for (let entry of list) {
23034
+ if (seen[entry.name]) twice[entry.name] = true;
23035
+ seen[entry.name] = true;
23036
+ }
23037
+ return twice;
23038
+ }
23039
+ /***************************************************************
22903
23040
  * ST_COLUMNS — the columns of one topic, in schema order.
22904
23041
  *
22905
23042
  * The order is a FIELD (`order`), so the rows are draggable and
@@ -22925,8 +23062,9 @@ function render_columns(gobj, $body) {
22925
23062
  ]);
22926
23063
  let $banner = version_banner(gobj, topic);
22927
23064
  if ($banner) $wrap.appendChild($banner);
23065
+ let twice = repeated_names(topic.cols);
22928
23066
  let $rows = [];
22929
- for (let i = 0; i < topic.cols.length; i++) $rows.push(column_row(gobj, topic, topic.cols[i], i, readonly));
23067
+ for (let i = 0; i < topic.cols.length; i++) $rows.push(column_row(gobj, topic, topic.cols[i], i, readonly, twice));
22930
23068
  let $table = (0, _yuneta_gobj_js.createElement2)([
22931
23069
  "div",
22932
23070
  {
@@ -23047,7 +23185,7 @@ function version_banner(gobj, topic) {
23047
23185
  [[
23048
23186
  "span",
23049
23187
  { class: "icon" },
23050
- [["i", { class: "yi-arrow-up" }]]
23188
+ [["i", { class: "yi-chevron-up" }]]
23051
23189
  ], [
23052
23190
  "span",
23053
23191
  { i18n: "raise" },
@@ -23068,8 +23206,10 @@ function version_banner(gobj, topic) {
23068
23206
  * Yuneta event is plain JSON, so a DOM node never travels in
23069
23207
  * one.
23070
23208
  ***************************************************************/
23071
- function column_row(gobj, topic, col, index, readonly) {
23209
+ function column_row(gobj, topic, col, index, readonly, twice) {
23072
23210
  let record = col.record || {};
23211
+ let ambiguous = !!(twice && twice[col.name]);
23212
+ let address = ambiguous ? col.id : col.name;
23073
23213
  let flags = col_flags(record);
23074
23214
  let hook = col_hook(record);
23075
23215
  let is_pkey = col.name === (topic.pkey || "id");
@@ -23081,13 +23221,13 @@ function column_row(gobj, topic, col, index, readonly) {
23081
23221
  ];
23082
23222
  });
23083
23223
  let link = "";
23084
- if (hook) link = "→ " + Object.keys(hook).join(", ");
23224
+ if (hook && !is_empty_value(hook)) link = "→ " + Object.keys(hook).join(", ");
23085
23225
  else if (flags.indexOf("fkey") >= 0) link = "↖";
23086
23226
  let $actions = [];
23087
23227
  if (!readonly) {
23088
- $actions.push(row_icon(gobj, "SCHEMA_COL_EDIT", "yi-pen", "edit column", "EV_EDIT_COLUMN", { col: col.name }));
23089
- $actions.push(row_icon(gobj, "SCHEMA_COL_DUPLICATE", "yi-copy", "duplicate column", "EV_DUPLICATE_COLUMN", { col: col.name }));
23090
- $actions.push(row_icon(gobj, "SCHEMA_COL_DELETE", "yi-trash", "delete column", "EV_DELETE_COLUMN", { col: col.name }));
23228
+ $actions.push(row_icon(gobj, "SCHEMA_COL_EDIT", "yi-pen", "edit column", "EV_EDIT_COLUMN", { col: address }));
23229
+ $actions.push(row_icon(gobj, "SCHEMA_COL_DUPLICATE", "yi-copy", "duplicate column", "EV_DUPLICATE_COLUMN", { col: address }));
23230
+ $actions.push(row_icon(gobj, "SCHEMA_COL_DELETE", "yi-trash", "delete column", "EV_DELETE_COLUMN", { col: address }));
23091
23231
  }
23092
23232
  let $row = (0, _yuneta_gobj_js.createElement2)([
23093
23233
  "tr",
@@ -23109,22 +23249,38 @@ function column_row(gobj, topic, col, index, readonly) {
23109
23249
  [
23110
23250
  "td",
23111
23251
  { class: "SCHEMA_COL_NAME" },
23112
- [[
23113
- "code",
23114
- {},
23115
- `${col.name}`
23116
- ], is_pkey ? [
23117
- "span",
23118
- {
23119
- class: "SCHEMA_COL_PKEY tag is-info is-light ml-2",
23120
- i18n: "pkey"
23121
- },
23122
- (0, i18next.t)("pkey")
23123
- ] : [
23124
- "span",
23125
- {},
23126
- ""
23127
- ]]
23252
+ [
23253
+ [
23254
+ "code",
23255
+ {},
23256
+ `${col.name}`
23257
+ ],
23258
+ ambiguous ? [
23259
+ "span",
23260
+ { class: "SCHEMA_COL_ID is-size-7 has-text-grey ml-2" },
23261
+ [[
23262
+ "code",
23263
+ {},
23264
+ `${col.id}`
23265
+ ]]
23266
+ ] : [
23267
+ "span",
23268
+ {},
23269
+ ""
23270
+ ],
23271
+ is_pkey ? [
23272
+ "span",
23273
+ {
23274
+ class: "SCHEMA_COL_PKEY tag is-info is-light ml-2",
23275
+ i18n: "pkey"
23276
+ },
23277
+ (0, i18next.t)("pkey")
23278
+ ] : [
23279
+ "span",
23280
+ {},
23281
+ ""
23282
+ ]
23283
+ ]
23128
23284
  ],
23129
23285
  [
23130
23286
  "td",
@@ -23167,7 +23323,7 @@ function column_row(gobj, topic, col, index, readonly) {
23167
23323
  if (!readonly) wire_row_drag(gobj, $row);
23168
23324
  $row.addEventListener("dblclick", (evt) => {
23169
23325
  if (readonly || evt.target.closest(".SCHEMA_COL_ACTIONS")) return;
23170
- (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_EDIT_COLUMN", { col: col.name }, gobj);
23326
+ (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_EDIT_COLUMN", { col: address }, gobj);
23171
23327
  });
23172
23328
  return $row;
23173
23329
  }
@@ -23267,6 +23423,7 @@ function render_diagram(gobj) {
23267
23423
  subscriber: gobj,
23268
23424
  descs,
23269
23425
  node_route: "",
23426
+ with_node_click: true,
23270
23427
  system: true
23271
23428
  }, gobj);
23272
23429
  if (!diagram) {
@@ -23504,7 +23661,8 @@ function open_column_form(gobj, topic, col, prefill) {
23504
23661
  let record = prefill || col && col.record || {};
23505
23662
  let creating = !col;
23506
23663
  let type = record.type || "string";
23507
- let hook = col_hook(record) || {};
23664
+ let hook = col_hook(record);
23665
+ if (is_empty_value(hook)) hook = {};
23508
23666
  let hook_topic = Object.keys(hook)[0] || "";
23509
23667
  let hook_col = hook_topic ? hook[hook_topic] : "";
23510
23668
  let treedb = current_treedb(gobj);
@@ -23654,7 +23812,7 @@ function open_topic_form(gobj, treedb, topic) {
23654
23812
  [
23655
23813
  field("SCHEMA_TOPIC_FORM_NAME", "value", "topic", text_input("value", creating ? "" : topic.name, (0, i18next.t)("topic name"), !creating), creating ? "" : "a topic is keyed by its whole path: renaming it is creating another one"),
23656
23814
  field("SCHEMA_TOPIC_FORM_PKEY", "pkey", "pkey", creating ? text_input("pkey", "id", "id") : select_input("pkey", record.pkey || "id", col_names.length > 0 ? col_names : [record.pkey || "id"])),
23657
- field("SCHEMA_TOPIC_FORM_PKEY2S", "pkey2s", "secondary keys", text_input("pkey2s", stringify_field(record.pkey2s), ""), "the column a record is KNOWN by when its key is composed"),
23815
+ field("SCHEMA_TOPIC_FORM_PKEY2S", "pkey2s", "secondary keys", text_input("pkey2s", stringify_field(record.pkey2s), ""), "the column a record is known by when its key is composed"),
23658
23816
  field("SCHEMA_TOPIC_FORM_SYSTEM_FLAG", "system_flag", "system flag", text_input("system_flag", record.system_flag || "sf_string_key", "")),
23659
23817
  field("SCHEMA_TOPIC_FORM_TKEY", "tkey", "time key", text_input("tkey", record.tkey, "")),
23660
23818
  field("SCHEMA_TOPIC_FORM_VERSION", "topic_version", "version", number_input("topic_version", record.topic_version === void 0 ? 1 : record.topic_version), "raising it is what publishes a change of the columns"),
@@ -24069,7 +24227,7 @@ function open_import(gobj, treedb) {
24069
24227
  [[
24070
24228
  "span",
24071
24229
  { class: "icon" },
24072
- [["i", { class: "yi-file-import" }]]
24230
+ [["i", { class: "yi-upload" }]]
24073
24231
  ], [
24074
24232
  "span",
24075
24233
  { i18n: "import" },
@@ -24334,10 +24492,12 @@ function run_next_write(gobj) {
24334
24492
  let priv = gobj.priv;
24335
24493
  if (priv.save_queue.length === 0) return end_writes(gobj, "");
24336
24494
  let write = priv.save_queue[0];
24337
- if (remote_command(gobj, write.op === "create" ? "create-node" : write.op === "delete" ? "delete-node" : "update-node", {
24495
+ let command = write_command(write.op);
24496
+ let options = write_options(write.op);
24497
+ if (remote_command(gobj, command, {
24338
24498
  topic_name: write.topic_name,
24339
24499
  record: write.record,
24340
- options: {},
24500
+ options,
24341
24501
  record_id: write.record.id || ""
24342
24502
  }) < 0) return end_writes(gobj, (0, i18next.t)("cannot reach the treedb"));
24343
24503
  return 0;
@@ -24392,7 +24552,8 @@ function column_record(gobj, topic, col, values) {
24392
24552
  record.header = values.header || "";
24393
24553
  record.type = values.type || "string";
24394
24554
  record.flag = flags;
24395
- record.fillspace = values.fillspace === "" ? "" : Number(values.fillspace);
24555
+ if (String(values.fillspace).trim() !== "") record.fillspace = Number(values.fillspace);
24556
+ else if (!creating) record.fillspace = "";
24396
24557
  record.placeholder = values.placeholder || "";
24397
24558
  record.description = values.description || "";
24398
24559
  record.default = values.default || "";
@@ -24490,7 +24651,7 @@ function ac_mt_command_answer(gobj, event, kw, src) {
24490
24651
  let __command__ = (0, _yuneta_gobj_js.msg_iev_get_stack)(gobj, kw, "command_stack", true);
24491
24652
  let command = (0, _yuneta_gobj_js.kw_get_str)(gobj, __command__, "command", "", 0);
24492
24653
  let kw_command = (0, _yuneta_gobj_js.kw_get_dict)(gobj, __command__, "kw", {}, 0);
24493
- let md = (0, _yuneta_gobj_js.kw_get_dict)(gobj, kw_command, "__md_command__", {}, 0);
24654
+ let md = (0, _yuneta_gobj_js.is_object)(kw_command.__md_command__) ? kw_command.__md_command__ : kw_command;
24494
24655
  if ((0, _yuneta_gobj_js.kw_get_str)(gobj, md, "purpose", "", 0) !== PURPOSE) return 0;
24495
24656
  if (command === "nodes") {
24496
24657
  let topic_name = (0, _yuneta_gobj_js.kw_get_str)(gobj, md, "topic_name", "", 0);
@@ -24507,11 +24668,11 @@ function ac_mt_command_answer(gobj, event, kw, src) {
24507
24668
  build_model(gobj);
24508
24669
  return go(gobj, priv.treedb_id, priv.topic_name, priv.diagram, false);
24509
24670
  }
24510
- if (command === "create-node" || command === "update-node" || command === "delete-node") {
24511
- let topic_name = (0, _yuneta_gobj_js.kw_get_str)(gobj, kw_command, "topic_name", "", 0);
24671
+ if (command === "update-node" || command === "delete-node") {
24672
+ let topic_name = (0, _yuneta_gobj_js.kw_get_str)(gobj, md, "topic_name", "", 0);
24512
24673
  if (result < 0) return end_writes(gobj, comment || (0, i18next.t)("the treedb refused the write"));
24513
24674
  priv.save_wrote = true;
24514
- if (command === "delete-node") forget_record(gobj, topic_name, (0, _yuneta_gobj_js.kw_get_str)(gobj, kw_command, "record_id", "", 0));
24675
+ if (command === "delete-node") forget_record(gobj, topic_name, (0, _yuneta_gobj_js.kw_get_str)(gobj, md, "record_id", "", 0));
24515
24676
  else {
24516
24677
  let record = (0, _yuneta_gobj_js.is_object)(data) ? data : Array.isArray(data) && (0, _yuneta_gobj_js.is_object)(data[0]) ? data[0] : null;
24517
24678
  if (record) patch_record(gobj, topic_name, record);
@@ -24522,7 +24683,10 @@ function ac_mt_command_answer(gobj, event, kw, src) {
24522
24683
  return 0;
24523
24684
  }
24524
24685
  }
24525
- if (topic_name === T_TOPICS || topic_name === T_COLS) mark_written(gobj, topic_name, (0, _yuneta_gobj_js.kw_get_dict)(gobj, kw_command, "record", {}, 0));
24686
+ if (topic_name === T_TOPICS || topic_name === T_COLS) {
24687
+ let write = priv.save_queue[0];
24688
+ mark_written(gobj, topic_name, write ? write.record : null);
24689
+ }
24526
24690
  priv.save_queue.shift();
24527
24691
  return run_next_write(gobj);
24528
24692
  }
@@ -24915,7 +25079,7 @@ function ac_preview_import(gobj, event, kw, src) {
24915
25079
  writes: [],
24916
25080
  summary: {},
24917
25081
  conflicts: [{
24918
- code: "that is not JSON",
25082
+ code: "that is not json",
24919
25083
  topic: "",
24920
25084
  col: ""
24921
25085
  }]