@yuneta/gobj-ui 7.26.34 → 7.26.35
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 +223 -53
- package/dist/gobj-ui.cjs.js +1035 -141
- package/dist/gobj-ui.es.js +1035 -141
- package/package.json +2 -2
- package/scripts/validate-locales.mjs +672 -96
- package/src/c_yui_form.js +186 -9
- package/src/c_yui_gclass.js +2 -0
- package/src/c_yui_gobj_tree_js.js +2 -0
- package/src/c_yui_json.js +2 -0
- package/src/c_yui_json_graph.js +2 -0
- package/src/c_yui_nav.js +23 -5
- package/src/c_yui_shell.css +16 -0
- package/src/c_yui_shell.js +282 -26
- package/src/c_yui_treedb_graph.js +26 -8
- package/src/c_yui_treedb_topic_with_form.js +40 -18
- package/src/c_yui_treedb_topics.js +34 -11
- package/src/c_yui_window.js +132 -32
- package/src/c_yui_window_manager.js +156 -44
- package/src/form_json_editor_names.js +448 -0
- package/src/form_json_editor_names.wiring.test.js +595 -0
- package/src/form_label_i18n.wiring.test.js +169 -0
- package/src/i18n_key_functions.js +14 -6
- package/src/i18n_key_functions.test.js +124 -2
- package/src/lib_maplibre.js +26 -2
- package/src/lib_maplibre_relocalize.test.js +78 -0
- package/src/named_controls.wiring.test.js +468 -0
- package/src/nav_cards_helpers.js +9 -1
- package/src/nav_item_badge.wiring.test.js +182 -1
- package/src/shell_route_map.js +25 -8
- package/src/table_editor_name.wiring.test.js +201 -0
- package/src/treedb_column_title.wiring.test.js +151 -0
- package/src/treedb_json_drill.wiring.test.js +41 -1
- package/src/window_layer.wiring.test.js +308 -43
- package/src/window_show.wiring.test.js +6 -2
- package/src/window_stacking.wiring.test.js +334 -0
- package/src/yui_badge.js +58 -13
- package/src/yui_badge.test.js +40 -3
- package/src/yui_dev.js +2 -1
- package/src/yui_file_field.js +2 -0
- package/src/yui_frontend_view.js +2 -1
- package/src/yui_gclass_view.js +9 -6
- package/src/yui_tabulator_i18n.js +42 -0
- package/src/yui_tabulator_i18n.test.js +48 -1
package/src/c_yui_shell.js
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Parses a JSON config to build:
|
|
7
7
|
* - Layers (z-stacked): base, overlay, popup, modal, notification, loading
|
|
8
|
+
* (the overlay holds the WINDOW layer, where the windows live,
|
|
9
|
+
* under the drawer: yui_shell_window_layer())
|
|
8
10
|
* - Zones (inside base layer): top, top-sub, left, center, right,
|
|
9
11
|
* bottom-sub, bottom
|
|
10
12
|
* - Menus (primary + submenus) rendered via C_YUI_NAV — one nav
|
|
@@ -65,7 +67,9 @@ import {
|
|
|
65
67
|
|
|
66
68
|
import { resolve_route, normalize_route } from "./route_resolver.js";
|
|
67
69
|
import { text_node } from "./yui_text.js";
|
|
68
|
-
import {
|
|
70
|
+
import {
|
|
71
|
+
badge_node, badge_tone, badge_text, paint_badge, name_control_with_badge,
|
|
72
|
+
} from "./yui_badge.js";
|
|
69
73
|
import { build_nav_map } from "./route_map_model.js";
|
|
70
74
|
import {
|
|
71
75
|
section_index_target,
|
|
@@ -83,6 +87,19 @@ const GCLASS_NAME = "C_YUI_SHELL";
|
|
|
83
87
|
* one shell; tests that create several get the most recent. */
|
|
84
88
|
let __last_shell__ = null;
|
|
85
89
|
|
|
90
|
+
/* The gobjs that keep DOM in a shell's layers without being its
|
|
91
|
+
* children: a C_YUI_WINDOW parented to the app, the dock of
|
|
92
|
+
* C_YUI_WINDOW_MANAGER. Each is sent EV_SHELL_CHANGED when a shell is
|
|
93
|
+
* built or destroyed (yui_shell_watch_changes()), because the place
|
|
94
|
+
* it lives in belongs to a shell that it outlives. A list of the
|
|
95
|
+
* module, not of a shell: the gobj is there BEFORE the next shell
|
|
96
|
+
* exists, and it has to be told when that one is built. */
|
|
97
|
+
let __shell_watchers__ = [];
|
|
98
|
+
|
|
99
|
+
/* The z-order of the windows (yui_shell_raise_window()): ONE counter
|
|
100
|
+
* for every window of the page, docked or not. */
|
|
101
|
+
let __window_z__ = 10;
|
|
102
|
+
|
|
86
103
|
/* Zones rendered inside the base layer. */
|
|
87
104
|
const ZONE_IDS = ["top", "top-sub", "left", "center", "right", "bottom-sub", "bottom"];
|
|
88
105
|
|
|
@@ -116,6 +133,9 @@ SDATA_END()
|
|
|
116
133
|
let PRIVATE_DATA = {
|
|
117
134
|
zones: {},
|
|
118
135
|
layers: {},
|
|
136
|
+
/* The windows' stacking context, inside the overlay layer: see
|
|
137
|
+
* build_ui() and yui_shell_window_layer(). */
|
|
138
|
+
window_layer: null,
|
|
119
139
|
stages: {},
|
|
120
140
|
navs: [],
|
|
121
141
|
item_index: {},
|
|
@@ -124,8 +144,10 @@ let PRIVATE_DATA = {
|
|
|
124
144
|
* every nav the shell creates is handed it (`badges`), so a nav built
|
|
125
145
|
* or rebuilt after the call draws it too. */
|
|
126
146
|
nav_badges: {},
|
|
147
|
+
/* The live region a changed badge is said in (announce_badge()). */
|
|
148
|
+
badge_status: null,
|
|
127
149
|
/* Parent item id -> the items yui_shell_set_submenu() put in place:
|
|
128
|
-
* menu items as much as the declared ones (
|
|
150
|
+
* menu items as much as the declared ones (nav_item_of). */
|
|
129
151
|
dynamic_items: {},
|
|
130
152
|
/* Section route -> last position visited inside it. Mirrors the
|
|
131
153
|
* url for the duration of the page; see remember_position(). */
|
|
@@ -255,6 +277,11 @@ function mt_create(gobj)
|
|
|
255
277
|
__last_shell__ = gobj;
|
|
256
278
|
|
|
257
279
|
build_ui(gobj);
|
|
280
|
+
|
|
281
|
+
/* A window left in the body by a shell that went (a logout), or
|
|
282
|
+
* created while there was none, lives in THIS shell from now on:
|
|
283
|
+
* in its window layer, under its dialogs and toasts again. */
|
|
284
|
+
tell_shell_watchers(gobj);
|
|
258
285
|
}
|
|
259
286
|
|
|
260
287
|
/***************************************************************
|
|
@@ -607,15 +634,27 @@ function mt_destroy(gobj)
|
|
|
607
634
|
* when stop is skipped. */
|
|
608
635
|
close_toolbar_dropdown(gobj);
|
|
609
636
|
|
|
637
|
+
if(__last_shell__ === gobj) {
|
|
638
|
+
__last_shell__ = null;
|
|
639
|
+
}
|
|
640
|
+
|
|
610
641
|
let $container = gobj_read_attr(gobj, "$container");
|
|
611
642
|
if($container && $container.parentNode) {
|
|
612
643
|
$container.parentNode.removeChild($container);
|
|
613
644
|
}
|
|
614
|
-
gobj_write_attr(gobj, "$container", null);
|
|
615
645
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
646
|
+
/* Its layers go with it, and what lives there without being its
|
|
647
|
+
* child does not: the developer window, a window of the app, the
|
|
648
|
+
* dock. Since 7.26.31 a window with no `$parent` lives in this
|
|
649
|
+
* shell, so a logout that destroyed the shell left such a window
|
|
650
|
+
* alive with its DOM detached, and the next shell never placed it
|
|
651
|
+
* again -- invisible, and the first click on its menu entry closed
|
|
652
|
+
* the invisible one. Told now -- with `__last_shell__` cleared, so
|
|
653
|
+
* the place they find is the body, and with the $container still
|
|
654
|
+
* readable, so each can tell whether it was inside. */
|
|
655
|
+
tell_shell_watchers(gobj);
|
|
656
|
+
|
|
657
|
+
gobj_write_attr(gobj, "$container", null);
|
|
619
658
|
}
|
|
620
659
|
|
|
621
660
|
|
|
@@ -816,6 +855,30 @@ function build_ui(gobj)
|
|
|
816
855
|
priv.layers[id] = $layer;
|
|
817
856
|
}
|
|
818
857
|
|
|
858
|
+
/* The windows' own stacking context, inside the overlay layer
|
|
859
|
+
* (yui_shell_window_layer()). A window takes its z-index from the
|
|
860
|
+
* dock -- a counter that grows on every focus -- or 3, and in the
|
|
861
|
+
* overlay layer itself that number competed with the drawer (18):
|
|
862
|
+
* after a few focus changes a drawer opened UNDER the windows.
|
|
863
|
+
* Here no number a window takes reaches the drawer, nor the
|
|
864
|
+
* layers above. */
|
|
865
|
+
let $windows = createElement2(
|
|
866
|
+
["div", {class: "SHELL_WINDOW_LAYER yui-window-layer"}]
|
|
867
|
+
);
|
|
868
|
+
priv.layers.overlay.appendChild($windows);
|
|
869
|
+
priv.window_layer = $windows;
|
|
870
|
+
|
|
871
|
+
/* Where the shell SAYS that a badge changed (announce_badge()): one
|
|
872
|
+
* live region, always in the tree -- never hidden, never inside a
|
|
873
|
+
* control -- so the first count is said as well as the next, and
|
|
874
|
+
* with the name of what it counts. */
|
|
875
|
+
let $status = createElement2(
|
|
876
|
+
["div", {class: "SHELL_BADGE_STATUS is-sr-only", role: "status",
|
|
877
|
+
"aria-live": "polite", "aria-atomic": "true"}]
|
|
878
|
+
);
|
|
879
|
+
$container.appendChild($status);
|
|
880
|
+
priv.badge_status = $status;
|
|
881
|
+
|
|
819
882
|
/* Base layer holds the grid of zones */
|
|
820
883
|
let $base = priv.layers.base;
|
|
821
884
|
$base.classList.add("yui-base-grid");
|
|
@@ -1899,17 +1962,19 @@ function toolbar_link_attrs(it)
|
|
|
1899
1962
|
function build_toolbar_action_item(gobj, it)
|
|
1900
1963
|
{
|
|
1901
1964
|
let children = [];
|
|
1965
|
+
let badge = null;
|
|
1902
1966
|
if(!empty_string(it.icon)) {
|
|
1903
1967
|
/* The badge is anchored to the ICON, not to the button: with a
|
|
1904
1968
|
* label on (desktop) the button is wide, and a badge pinned to
|
|
1905
1969
|
* its corner would float far from the glyph it counts. The
|
|
1906
1970
|
* count, its tone and their rules: yui_badge.js. */
|
|
1971
|
+
badge = badge_node(it.badge, it.badge_tone,
|
|
1972
|
+
"TOOLBAR_ITEM_BADGE yui-badge yui-toolbar-badge",
|
|
1973
|
+
`C_YUI_SHELL: toolbar item '${it.id || "?"}'`);
|
|
1907
1974
|
children.push(["span", {class: "icon yui-toolbar-icon"},
|
|
1908
1975
|
[
|
|
1909
1976
|
["i", {class: it.icon, "aria-hidden": "true"}],
|
|
1910
|
-
|
|
1911
|
-
"TOOLBAR_ITEM_BADGE yui-badge yui-toolbar-badge",
|
|
1912
|
-
`C_YUI_SHELL: toolbar item '${it.id || "?"}'`)
|
|
1977
|
+
badge
|
|
1913
1978
|
]
|
|
1914
1979
|
]);
|
|
1915
1980
|
}
|
|
@@ -1945,6 +2010,11 @@ function build_toolbar_action_item(gobj, it)
|
|
|
1945
2010
|
btn_attrs.title = tip;
|
|
1946
2011
|
btn_attrs["data-i18n-title"] = tip;
|
|
1947
2012
|
}
|
|
2013
|
+
if(badge && !empty_string(aria_key)) {
|
|
2014
|
+
/* The count is part of its name: inside a <button> the badge is
|
|
2015
|
+
* no node of its own for a reader (yui_badge.js). */
|
|
2016
|
+
name_control_with_badge(btn_attrs, badge);
|
|
2017
|
+
}
|
|
1948
2018
|
let link = toolbar_link_attrs(it);
|
|
1949
2019
|
if(link) {
|
|
1950
2020
|
delete btn_attrs.type; /* meaningless, and invalid, on an <a> */
|
|
@@ -3195,37 +3265,43 @@ function update_secondary_nav_visibility(gobj, entry)
|
|
|
3195
3265
|
}
|
|
3196
3266
|
|
|
3197
3267
|
/************************************************************
|
|
3198
|
-
*
|
|
3199
|
-
* of a menu of app_config.json, of one of
|
|
3200
|
-
* one given to yui_shell_set_submenu() since
|
|
3268
|
+
* The item `item_id` a menu of this shell declares, null when
|
|
3269
|
+
* none does: an item of a menu of app_config.json, of one of
|
|
3270
|
+
* their submenus, or one given to yui_shell_set_submenu() since
|
|
3271
|
+
* -- which comes first, because it replaced what the submenu
|
|
3272
|
+
* declared and is what the navs draw.
|
|
3201
3273
|
************************************************************/
|
|
3202
|
-
function
|
|
3274
|
+
function nav_item_of(gobj, item_id)
|
|
3203
3275
|
{
|
|
3204
3276
|
let priv = gobj.priv;
|
|
3205
3277
|
let config = gobj_read_attr(gobj, "config") || {};
|
|
3206
3278
|
let menus = is_object(config.menu) ? config.menu : {};
|
|
3207
|
-
let
|
|
3279
|
+
let find = (items) => (is_array(items) &&
|
|
3280
|
+
items.find((it) => it && it.id === item_id)) || null;
|
|
3208
3281
|
|
|
3282
|
+
for(let parent_item_id in priv.dynamic_items) {
|
|
3283
|
+
let it = find(priv.dynamic_items[parent_item_id]);
|
|
3284
|
+
if(it) {
|
|
3285
|
+
return it;
|
|
3286
|
+
}
|
|
3287
|
+
}
|
|
3209
3288
|
for(let menu_id in menus) {
|
|
3210
3289
|
let items = menus[menu_id] && menus[menu_id].items;
|
|
3211
3290
|
if(!is_array(items)) {
|
|
3212
3291
|
continue;
|
|
3213
3292
|
}
|
|
3214
|
-
|
|
3215
|
-
|
|
3293
|
+
let it = find(items);
|
|
3294
|
+
if(it) {
|
|
3295
|
+
return it;
|
|
3216
3296
|
}
|
|
3217
|
-
for(let
|
|
3218
|
-
|
|
3219
|
-
|
|
3297
|
+
for(let parent of items) {
|
|
3298
|
+
let sub = parent && parent.submenu && find(parent.submenu.items);
|
|
3299
|
+
if(sub) {
|
|
3300
|
+
return sub;
|
|
3220
3301
|
}
|
|
3221
3302
|
}
|
|
3222
3303
|
}
|
|
3223
|
-
|
|
3224
|
-
if(holds(priv.dynamic_items[parent_item_id])) {
|
|
3225
|
-
return true;
|
|
3226
|
-
}
|
|
3227
|
-
}
|
|
3228
|
-
return false;
|
|
3304
|
+
return null;
|
|
3229
3305
|
}
|
|
3230
3306
|
|
|
3231
3307
|
/************************************************************
|
|
@@ -3248,6 +3324,71 @@ function shell_navs(gobj)
|
|
|
3248
3324
|
return navs;
|
|
3249
3325
|
}
|
|
3250
3326
|
|
|
3327
|
+
/************************************************************
|
|
3328
|
+
* Say a badge that changed, with the name of what it counts:
|
|
3329
|
+
* "Alarms, 3", in the shell's live region (build_ui()).
|
|
3330
|
+
*
|
|
3331
|
+
* The badge itself is no live region (yui_badge.js): its number
|
|
3332
|
+
* alone said nothing, hidden while empty it missed the first
|
|
3333
|
+
* count, and inside a <button> it was not there at all. The
|
|
3334
|
+
* setters call this only when the text a reader would see
|
|
3335
|
+
* changes and is not empty: a count going away is not news, and
|
|
3336
|
+
* the same count again is not either.
|
|
3337
|
+
*
|
|
3338
|
+
* `name_key` is the key of the control's name; the phrase is a
|
|
3339
|
+
* key of its own, so a language orders and punctuates it its
|
|
3340
|
+
* way. Said once: a live region needs no re-translation.
|
|
3341
|
+
************************************************************/
|
|
3342
|
+
function announce_badge(gobj, name_key, text)
|
|
3343
|
+
{
|
|
3344
|
+
let priv = gobj.priv;
|
|
3345
|
+
let $status = priv.badge_status;
|
|
3346
|
+
if(!$status) {
|
|
3347
|
+
log_error(`C_YUI_SHELL: no live region to say badge '${name_key}': ${text}`);
|
|
3348
|
+
return;
|
|
3349
|
+
}
|
|
3350
|
+
let message;
|
|
3351
|
+
if(typeof priv.translator === "function") {
|
|
3352
|
+
const tr = priv.translator;
|
|
3353
|
+
message = tr("{{name}}, {{n}}", {name: tr(name_key), n: text});
|
|
3354
|
+
} else {
|
|
3355
|
+
message = `${name_key}, ${text}`;
|
|
3356
|
+
}
|
|
3357
|
+
/* Written even when it reads the same: the callers filter what is
|
|
3358
|
+
* no news, and a count that went away and came back IS news -- a
|
|
3359
|
+
* new text node is a change a reader announces. */
|
|
3360
|
+
$status.textContent = message;
|
|
3361
|
+
}
|
|
3362
|
+
|
|
3363
|
+
/************************************************************
|
|
3364
|
+
* A shell was built (mt_create) or is being destroyed
|
|
3365
|
+
* (mt_destroy): tell every gobj that keeps DOM in a shell's
|
|
3366
|
+
* layers without being its child (yui_shell_watch_changes()).
|
|
3367
|
+
* Each one decides in its own action what it means for it --
|
|
3368
|
+
* the trace shows EV_SHELL_CHANGED arriving, from a `!!` shell
|
|
3369
|
+
* when it is the one going.
|
|
3370
|
+
*
|
|
3371
|
+
* An event, not a call into the window's module: the shell does
|
|
3372
|
+
* not import its watchers, and the move is work, so it crosses
|
|
3373
|
+
* their FSM. A watcher that is being destroyed is one whose
|
|
3374
|
+
* mt_destroy did not unwatch: it is dropped, and said.
|
|
3375
|
+
************************************************************/
|
|
3376
|
+
function tell_shell_watchers(gobj)
|
|
3377
|
+
{
|
|
3378
|
+
for(let watcher of __shell_watchers__.slice()) {
|
|
3379
|
+
if(gobj_is_destroying(watcher)) {
|
|
3380
|
+
log_error(
|
|
3381
|
+
`C_YUI_SHELL: a destroyed gobj still watches the shell ` +
|
|
3382
|
+
`(${gobj_short_name(watcher)}): it must call ` +
|
|
3383
|
+
`yui_shell_unwatch_changes() in its mt_destroy`
|
|
3384
|
+
);
|
|
3385
|
+
yui_shell_unwatch_changes(watcher);
|
|
3386
|
+
continue;
|
|
3387
|
+
}
|
|
3388
|
+
gobj_send_event(watcher, "EV_SHELL_CHANGED", {}, gobj);
|
|
3389
|
+
}
|
|
3390
|
+
}
|
|
3391
|
+
|
|
3251
3392
|
|
|
3252
3393
|
|
|
3253
3394
|
|
|
@@ -3903,6 +4044,95 @@ function yui_shell_zone(shell_gobj, zone_id)
|
|
|
3903
4044
|
return $zone;
|
|
3904
4045
|
}
|
|
3905
4046
|
|
|
4047
|
+
/************************************************************
|
|
4048
|
+
* The DOM where a C_YUI_WINDOW lives when its host names no
|
|
4049
|
+
* `$parent`: the WINDOW layer, a stacking context of its own
|
|
4050
|
+
* inside the overlay layer -- above the content, below the
|
|
4051
|
+
* drawer, the toolbar's dropdowns, the dialogs and the toasts,
|
|
4052
|
+
* whatever z-index a window takes there. Null with no shell (the
|
|
4053
|
+
* window then lives in document.body).
|
|
4054
|
+
************************************************************/
|
|
4055
|
+
function yui_shell_window_layer(shell_gobj)
|
|
4056
|
+
{
|
|
4057
|
+
if(!shell_gobj || !shell_gobj.priv) {
|
|
4058
|
+
return null;
|
|
4059
|
+
}
|
|
4060
|
+
let priv = shell_gobj.priv;
|
|
4061
|
+
return priv.window_layer || (priv.layers && priv.layers.overlay) || null;
|
|
4062
|
+
}
|
|
4063
|
+
|
|
4064
|
+
/************************************************************
|
|
4065
|
+
* Put a window on top of the others: the next number of ONE
|
|
4066
|
+
* counter, the same for a window of the dock and for a floating
|
|
4067
|
+
* one.
|
|
4068
|
+
*
|
|
4069
|
+
* Up to 7.26.34 there were two orders: the dock raised its windows
|
|
4070
|
+
* to a counter of its own (11, 12, ...), and a window without a
|
|
4071
|
+
* dock sat at 3 and was raised by being moved to the end of its
|
|
4072
|
+
* parent -- so a window opened LATER without a dock (a treedb's
|
|
4073
|
+
* raw json) opened UNDER any docked one, and no click could bring
|
|
4074
|
+
* it up. They share the window layer, and this one number orders
|
|
4075
|
+
* them all. It grows for ever, and it can: nothing else lives in
|
|
4076
|
+
* the window layer's stacking context.
|
|
4077
|
+
*
|
|
4078
|
+
* A number, not a move in the DOM: moving an element takes its
|
|
4079
|
+
* focus, its scroll positions and the pointer press that started
|
|
4080
|
+
* on it.
|
|
4081
|
+
************************************************************/
|
|
4082
|
+
function yui_shell_raise_window($el)
|
|
4083
|
+
{
|
|
4084
|
+
if(!$el || !$el.style) {
|
|
4085
|
+
log_error("C_YUI_SHELL: yui_shell_raise_window() needs an element");
|
|
4086
|
+
return;
|
|
4087
|
+
}
|
|
4088
|
+
__window_z__ += 1;
|
|
4089
|
+
$el.style.zIndex = String(__window_z__);
|
|
4090
|
+
}
|
|
4091
|
+
|
|
4092
|
+
/************************************************************
|
|
4093
|
+
* Ask to be told (EV_SHELL_CHANGED, an INPUT event the gclass of
|
|
4094
|
+
* `gobj` must take in its FSM) when a shell is built or
|
|
4095
|
+
* destroyed.
|
|
4096
|
+
*
|
|
4097
|
+
* For a gobj that keeps DOM in a shell's layers without being
|
|
4098
|
+
* its child -- a window parented to the app, the dock. A shell's
|
|
4099
|
+
* children are destroyed with it; such a gobj is not, and its
|
|
4100
|
+
* place goes with the shell: a logout destroys the shell, the
|
|
4101
|
+
* login builds another. Told, it takes its place again in the
|
|
4102
|
+
* action.
|
|
4103
|
+
*
|
|
4104
|
+
* Idempotent. Pair it with yui_shell_unwatch_changes() in
|
|
4105
|
+
* mt_destroy, or the next shell sends the event to a destroyed
|
|
4106
|
+
* gobj.
|
|
4107
|
+
*
|
|
4108
|
+
* function mt_create(gobj) {
|
|
4109
|
+
* ...
|
|
4110
|
+
* yui_shell_watch_changes(gobj);
|
|
4111
|
+
* }
|
|
4112
|
+
* function mt_destroy(gobj) {
|
|
4113
|
+
* yui_shell_unwatch_changes(gobj);
|
|
4114
|
+
* ...
|
|
4115
|
+
* }
|
|
4116
|
+
************************************************************/
|
|
4117
|
+
function yui_shell_watch_changes(gobj)
|
|
4118
|
+
{
|
|
4119
|
+
if(!is_gobj(gobj)) {
|
|
4120
|
+
log_error("C_YUI_SHELL: yui_shell_watch_changes() needs a gobj");
|
|
4121
|
+
return;
|
|
4122
|
+
}
|
|
4123
|
+
if(__shell_watchers__.indexOf(gobj) < 0) {
|
|
4124
|
+
__shell_watchers__.push(gobj);
|
|
4125
|
+
}
|
|
4126
|
+
}
|
|
4127
|
+
|
|
4128
|
+
function yui_shell_unwatch_changes(gobj)
|
|
4129
|
+
{
|
|
4130
|
+
let idx = __shell_watchers__.indexOf(gobj);
|
|
4131
|
+
if(idx >= 0) {
|
|
4132
|
+
__shell_watchers__.splice(idx, 1);
|
|
4133
|
+
}
|
|
4134
|
+
}
|
|
4135
|
+
|
|
3906
4136
|
/************************************************************
|
|
3907
4137
|
* Programmatic navigation.
|
|
3908
4138
|
*
|
|
@@ -4303,7 +4533,17 @@ function yui_shell_set_toolbar_item_badge(shell_gobj, item_id, value, tone)
|
|
|
4303
4533
|
if(!$badge) {
|
|
4304
4534
|
return;
|
|
4305
4535
|
}
|
|
4536
|
+
let before = $badge.hasAttribute("hidden") ? "" : $badge.textContent;
|
|
4306
4537
|
paint_badge($badge, value, tone);
|
|
4538
|
+
|
|
4539
|
+
/* Said with the item's name when what a reader sees changed: the
|
|
4540
|
+
* badge is in the button's name, but nobody is told it moved. */
|
|
4541
|
+
let now = badge_text(value);
|
|
4542
|
+
if(now && now !== before) {
|
|
4543
|
+
let $item = $badge.closest("[data-toolbar-item-id]");
|
|
4544
|
+
const name_key = ($item && $item.getAttribute("data-i18n-aria-label")) || item_id;
|
|
4545
|
+
announce_badge(shell_gobj, /* i18n: data */ name_key, now);
|
|
4546
|
+
}
|
|
4307
4547
|
}
|
|
4308
4548
|
|
|
4309
4549
|
/************************************************************
|
|
@@ -4346,16 +4586,28 @@ function yui_shell_set_nav_item_badge(shell_gobj, item_id, value, tone)
|
|
|
4346
4586
|
return;
|
|
4347
4587
|
}
|
|
4348
4588
|
let priv = shell_gobj.priv;
|
|
4349
|
-
|
|
4589
|
+
let item = priv ? nav_item_of(shell_gobj, item_id) : null;
|
|
4590
|
+
if(!item) {
|
|
4350
4591
|
return;
|
|
4351
4592
|
}
|
|
4352
4593
|
let badge = (value === undefined) ? null : value;
|
|
4594
|
+
/* What a reader saw before: the last value set for the id, or the
|
|
4595
|
+
* badge its declaration seeds. */
|
|
4596
|
+
let was = priv.nav_badges[item_id];
|
|
4597
|
+
let before = badge_text(was ? was.badge : item.badge);
|
|
4353
4598
|
priv.nav_badges[item_id] = {badge: badge, badge_tone: tone};
|
|
4354
4599
|
|
|
4355
4600
|
for(let nav of shell_navs(shell_gobj)) {
|
|
4356
4601
|
gobj_send_event(nav, "EV_SET_ITEM_BADGE",
|
|
4357
4602
|
{item_id: item_id, badge: badge, badge_tone: tone}, shell_gobj);
|
|
4358
4603
|
}
|
|
4604
|
+
|
|
4605
|
+
/* Said ONCE, here, with the item's name -- not by each nav that
|
|
4606
|
+
* draws the item (a rail, an icon bar, a drawer). */
|
|
4607
|
+
let now = badge_text(badge);
|
|
4608
|
+
if(now && now !== before) {
|
|
4609
|
+
announce_badge(shell_gobj, /* i18n: data */ item.name || item_id, now);
|
|
4610
|
+
}
|
|
4359
4611
|
}
|
|
4360
4612
|
|
|
4361
4613
|
/************************************************************
|
|
@@ -4385,6 +4637,10 @@ export {
|
|
|
4385
4637
|
register_c_yui_shell,
|
|
4386
4638
|
yui_shell_of,
|
|
4387
4639
|
yui_shell_zone,
|
|
4640
|
+
yui_shell_window_layer,
|
|
4641
|
+
yui_shell_raise_window,
|
|
4642
|
+
yui_shell_watch_changes,
|
|
4643
|
+
yui_shell_unwatch_changes,
|
|
4388
4644
|
yui_shell_navigate,
|
|
4389
4645
|
yui_shell_last_route_under,
|
|
4390
4646
|
yui_shell_last_route_outside,
|
|
@@ -92,7 +92,7 @@ import {
|
|
|
92
92
|
set_active_state,
|
|
93
93
|
set_pressed_state,
|
|
94
94
|
} from "./lib_graph.js";
|
|
95
|
-
import {yui_shell_show_error, yui_shell_show_modal
|
|
95
|
+
import {yui_shell_show_error, yui_shell_show_modal} from "./shell_modals.js";
|
|
96
96
|
import {yui_shell_of, yui_shell_set_sub_routes, yui_shell_navigate, yui_shell_previous_route}
|
|
97
97
|
from "./c_yui_shell.js";
|
|
98
98
|
|
|
@@ -1243,9 +1243,13 @@ function open_json_viewer(gobj)
|
|
|
1243
1243
|
});
|
|
1244
1244
|
gobj_start(jv); /* mounted: now it can measure itself */
|
|
1245
1245
|
} else {
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1246
|
+
/* The WINDOW layer, where every window lives (the legacy
|
|
1247
|
+
* '#top-layer' when a page has one): no `$parent` takes it. It
|
|
1248
|
+
* was the shell's popup layer -- above the drawer, in the way of
|
|
1249
|
+
* the toolbar's dropdowns (they opened BEHIND it where they met),
|
|
1250
|
+
* and in an order of its own no other window could rise above. */
|
|
1251
|
+
let $win_parent = (typeof document !== "undefined" &&
|
|
1252
|
+
document.getElementById("top-layer")) || null;
|
|
1249
1253
|
|
|
1250
1254
|
priv.json_win = gobj_create_service(
|
|
1251
1255
|
`treedb-jsonwin-${clean_name(gobj_name(gobj))}`,
|
|
@@ -1414,8 +1418,16 @@ function request_print_tranger(gobj, path)
|
|
|
1414
1418
|
}, gobj);
|
|
1415
1419
|
if(ret) {
|
|
1416
1420
|
log_error(ret);
|
|
1417
|
-
|
|
1418
|
-
|
|
1421
|
+
/* The transport's own words travel as `error`, text the viewer
|
|
1422
|
+
* draws as it came. Our fallback is a KEY, and travels as `i18n`:
|
|
1423
|
+
* as `error` it was drawn raw, "print-tranger failed" in English
|
|
1424
|
+
* in every language. */
|
|
1425
|
+
const refusal = refusal_text(ret);
|
|
1426
|
+
if(refusal) {
|
|
1427
|
+
answer_print_tranger_refused(gobj, path, {error: /* i18n: data */ refusal});
|
|
1428
|
+
} else {
|
|
1429
|
+
answer_print_tranger_refused(gobj, path, {i18n: "print-tranger failed"});
|
|
1430
|
+
}
|
|
1419
1431
|
return;
|
|
1420
1432
|
}
|
|
1421
1433
|
if(path) {
|
|
@@ -2173,9 +2185,15 @@ function ac_mt_command_answer(gobj, event, kw, src)
|
|
|
2173
2185
|
priv.drills_in_flight.clear();
|
|
2174
2186
|
}
|
|
2175
2187
|
if(result < 0) {
|
|
2176
|
-
if(path) {
|
|
2188
|
+
if(path && comment) {
|
|
2189
|
+
gobj_send_event(jv, "EV_SUBTREE_ERROR",
|
|
2190
|
+
{path: path, error: comment}, gobj); // the backend's words
|
|
2191
|
+
} else if(path) {
|
|
2192
|
+
/* No word from the backend: our fallback, a KEY, as `i18n`
|
|
2193
|
+
* -- the viewer draws it translated, and it changes
|
|
2194
|
+
* language with the app. As `error` it was drawn raw. */
|
|
2177
2195
|
gobj_send_event(jv, "EV_SUBTREE_ERROR",
|
|
2178
|
-
{path: path,
|
|
2196
|
+
{path: path, i18n: "print-tranger failed"}, gobj);
|
|
2179
2197
|
} else {
|
|
2180
2198
|
yui_shell_show_error(yui_shell_of(gobj),
|
|
2181
2199
|
/* i18n: data */ comment || "print-tranger failed", {t: t});
|
|
@@ -64,7 +64,6 @@ import {
|
|
|
64
64
|
import {
|
|
65
65
|
yui_shell_confirm_yesnocancel,
|
|
66
66
|
yui_shell_confirm_ok,
|
|
67
|
-
yui_shell_popup_layer,
|
|
68
67
|
yui_shell_show_modal,
|
|
69
68
|
} from "./shell_modals.js";
|
|
70
69
|
|
|
@@ -78,7 +77,8 @@ import {attach_clear} from "./yui_inputs.js";
|
|
|
78
77
|
|
|
79
78
|
import {yui_tabulator_lang, yui_tabulator_relocalize,
|
|
80
79
|
yui_tabulator_name_filters,
|
|
81
|
-
yui_tabulator_name_row_selects
|
|
80
|
+
yui_tabulator_name_row_selects,
|
|
81
|
+
yui_tabulator_name_editor} from "./yui_tabulator_i18n.js";
|
|
82
82
|
|
|
83
83
|
import {
|
|
84
84
|
yui_selection_column,
|
|
@@ -287,13 +287,6 @@ let PRIVATE_DATA = {
|
|
|
287
287
|
|
|
288
288
|
let __gclass__ = null;
|
|
289
289
|
|
|
290
|
-
/* Where to mount the edit/delete modal. Under a C_YUI_SHELL use
|
|
291
|
-
* its popup layer (z 20): the shell confirms live on the modal
|
|
292
|
-
* layer (z 99), so they always paint above this dialog — a
|
|
293
|
-
* body-mounted Bulma `.modal` would cover them (the shell is a
|
|
294
|
-
* stacking context below body-level modals). The legacy
|
|
295
|
-
* C_YUI_MAIN "#popup-layer" and document.body (Bulma `.modal` is
|
|
296
|
-
* position:fixed) remain the shell-less fallbacks. */
|
|
297
290
|
/* True on a phone-width viewport (Bulma's mobile breakpoint). */
|
|
298
291
|
function is_mobile()
|
|
299
292
|
{
|
|
@@ -341,7 +334,7 @@ function present_json_popup(gobj, $box, opts)
|
|
|
341
334
|
opts.name,
|
|
342
335
|
"C_YUI_WINDOW",
|
|
343
336
|
{
|
|
344
|
-
$parent: popup_mount_layer(
|
|
337
|
+
$parent: popup_mount_layer(),
|
|
345
338
|
subscriber: null,
|
|
346
339
|
modal: false,
|
|
347
340
|
showMax: true,
|
|
@@ -408,13 +401,18 @@ function retire_popup_window(win)
|
|
|
408
401
|
}
|
|
409
402
|
}
|
|
410
403
|
|
|
411
|
-
|
|
404
|
+
/************************************************************
|
|
405
|
+
* Where a json window of this view lives: null, the WINDOW
|
|
406
|
+
* layer of the shell, where every window lives -- below the
|
|
407
|
+
* shell's dialogs, so a confirm asked over it paints above it
|
|
408
|
+
* (the legacy C_YUI_MAIN '#popup-layer' when a page has one).
|
|
409
|
+
* It was the shell's popup layer: above the drawer, in the way
|
|
410
|
+
* of the toolbar's dropdowns, and in an order of its own no
|
|
411
|
+
* other window could rise above.
|
|
412
|
+
************************************************************/
|
|
413
|
+
function popup_mount_layer()
|
|
412
414
|
{
|
|
413
|
-
|
|
414
|
-
if($layer) {
|
|
415
|
-
return $layer;
|
|
416
|
-
}
|
|
417
|
-
return document.getElementById("popup-layer") || document.body;
|
|
415
|
+
return document.getElementById("popup-layer") || null;
|
|
418
416
|
}
|
|
419
417
|
|
|
420
418
|
|
|
@@ -1144,8 +1142,25 @@ function col_title_formatter(gobj, col)
|
|
|
1144
1142
|
let text = col_label(col, topic_name);
|
|
1145
1143
|
let attrs = {};
|
|
1146
1144
|
const NO_I18N = "\x00";
|
|
1147
|
-
|
|
1148
|
-
|
|
1145
|
+
/* The key a language change re-translates the header by is the
|
|
1146
|
+
* one the cascade TOOK -- '<topic>.<col>' before '<col>', as
|
|
1147
|
+
* col_label() reads them. Up to 7.26.34 it was always the bare
|
|
1148
|
+
* '<col>': a header translated by its topic's key alone carried
|
|
1149
|
+
* none (and stayed in its language), one translated by both went
|
|
1150
|
+
* to the bare column's text. The shell's apps did not show it --
|
|
1151
|
+
* their view rebuilds its columns on EV_LANGUAGE_CHANGED, after
|
|
1152
|
+
* refresh_language(), and this formatter runs the cascade again
|
|
1153
|
+
* -- but a refresh_language() alone did. The column is the
|
|
1154
|
+
* host's schema: data. */
|
|
1155
|
+
let key = null;
|
|
1156
|
+
if(topic_name &&
|
|
1157
|
+
t(/* i18n: data */ topic_name + "." + col.id, {defaultValue: NO_I18N}) !== NO_I18N) {
|
|
1158
|
+
key = topic_name + "." + col.id;
|
|
1159
|
+
} else if(t(col.id, {defaultValue: NO_I18N}) !== NO_I18N) {
|
|
1160
|
+
key = col.id;
|
|
1161
|
+
}
|
|
1162
|
+
if(key) {
|
|
1163
|
+
attrs.i18n = /* i18n: data */ key;
|
|
1149
1164
|
}
|
|
1150
1165
|
return createElement2(['span', attrs, text_node(text)]);
|
|
1151
1166
|
};
|
|
@@ -1312,6 +1327,13 @@ function create_tabulator(gobj)
|
|
|
1312
1327
|
return gobj_read_bool_attr(gobj, "editable") &&
|
|
1313
1328
|
!gobj_read_bool_attr(gobj, "readonly");
|
|
1314
1329
|
};
|
|
1330
|
+
|
|
1331
|
+
/* The box Tabulator draws in the cell being edited is named after
|
|
1332
|
+
* its column -- the header's own cascade -- in the language of
|
|
1333
|
+
* that moment. */
|
|
1334
|
+
yui_tabulator_name_editor(colDef, function() {
|
|
1335
|
+
return col_label(col, gobj_read_str_attr(gobj, "topic_name"));
|
|
1336
|
+
});
|
|
1315
1337
|
}
|
|
1316
1338
|
|
|
1317
1339
|
const FILTERABLE = [
|