@yuneta/gobj-ui 2.0.0 → 2.2.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/README.md +25 -20
- package/dist/gobj-ui.cjs.js +2331 -526
- package/dist/gobj-ui.es.js +2331 -528
- package/index.js +32 -31
- package/package.json +5 -6
- package/{c_g6_nodes_tree.js → src/c_g6_nodes_tree.js} +6 -1
- package/{c_yui_form.js → src/c_yui_form.js} +1 -1
- package/{c_yui_gobj_tree_js.js → src/c_yui_gobj_tree_js.js} +1 -1
- package/{c_yui_json_graph.js → src/c_yui_json_graph.js} +1 -1
- package/{c_yui_main.js → src/c_yui_main.js} +3 -3
- package/{c_yui_map.js → src/c_yui_map.js} +6 -1
- package/{c_yui_nav.js → src/c_yui_nav.js} +134 -26
- package/{c_yui_pager.js → src/c_yui_pager.js} +1 -1
- package/{c_yui_routing.css → src/c_yui_routing.css} +1 -1
- package/{c_yui_routing.js → src/c_yui_routing.js} +1 -1
- package/{c_yui_shell.css → src/c_yui_shell.css} +110 -0
- package/{c_yui_shell.js → src/c_yui_shell.js} +163 -10
- package/{c_yui_tabs.js → src/c_yui_tabs.js} +1 -1
- package/{c_yui_treedb_graph.js → src/c_yui_treedb_graph.js} +35 -9
- package/{c_yui_treedb_topic_with_form.js → src/c_yui_treedb_topic_with_form.js} +1 -1
- package/{c_yui_treedb_topics.js → src/c_yui_treedb_topics.js} +36 -8
- package/{c_yui_uplot.js → src/c_yui_uplot.js} +1 -1
- package/{c_yui_window.js → src/c_yui_window.js} +242 -21
- package/src/c_yui_window_manager.js +602 -0
- package/{c_yui_wizard.js → src/c_yui_wizard.js} +1 -1
- package/{shell_modals.js → src/shell_modals.js} +53 -13
- package/src/tabulator.css +53 -0
- package/src/yui_dev.js +1478 -0
- package/{yui_icons.css → src/yui_icons.css} +5 -0
- package/src/yui_inputs.css +32 -0
- package/src/yui_inputs.js +71 -0
- package/vite.config.js +0 -131
- package/yui_dev.js +0 -583
- /package/{c_yui_main.css → src/c_yui_main.css} +0 -0
- /package/{c_yui_map.css → src/c_yui_map.css} +0 -0
- /package/{c_yui_treedb_topic_with_form.css → src/c_yui_treedb_topic_with_form.css} +0 -0
- /package/{g6_drag_canvas_touch.js → src/g6_drag_canvas_touch.js} +0 -0
- /package/{lib_graph.css → src/lib_graph.css} +0 -0
- /package/{lib_graph.js → src/lib_graph.js} +0 -0
- /package/{lib_icons.js → src/lib_icons.js} +0 -0
- /package/{lib_maplibre.js → src/lib_maplibre.js} +0 -0
- /package/{pager_helpers.js → src/pager_helpers.js} +0 -0
- /package/{pager_helpers.test.js → src/pager_helpers.test.js} +0 -0
- /package/{route_resolver.js → src/route_resolver.js} +0 -0
- /package/{route_resolver.test.js → src/route_resolver.test.js} +0 -0
- /package/{shell_focus_trap.js → src/shell_focus_trap.js} +0 -0
- /package/{shell_focus_trap.test.js → src/shell_focus_trap.test.js} +0 -0
- /package/{shell_show_on.js → src/shell_show_on.js} +0 -0
- /package/{shell_show_on.test.js → src/shell_show_on.test.js} +0 -0
- /package/{shell_toolbar_helpers.js → src/shell_toolbar_helpers.js} +0 -0
- /package/{shell_toolbar_helpers.test.js → src/shell_toolbar_helpers.test.js} +0 -0
- /package/{themes.js → src/themes.js} +0 -0
- /package/{wizard_helpers.js → src/wizard_helpers.js} +0 -0
- /package/{wizard_helpers.test.js → src/wizard_helpers.test.js} +0 -0
- /package/{ytable.css → src/ytable.css} +0 -0
- /package/{ytable.js → src/ytable.js} +0 -0
- /package/{yui_toolbar.css → src/yui_toolbar.css} +0 -0
- /package/{yui_toolbar.js → src/yui_toolbar.js} +0 -0
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
gobj_create, gobj_destroy,
|
|
31
31
|
gobj_start, gobj_stop,
|
|
32
32
|
gobj_publish_event,
|
|
33
|
+
gobj_send_event,
|
|
33
34
|
gobj_subscribe_event,
|
|
34
35
|
gobj_read_attr, gobj_read_pointer_attr, gobj_write_attr,
|
|
35
36
|
createElement2, empty_string, is_object, is_array, is_string,
|
|
@@ -495,8 +496,8 @@ function build_ui(gobj)
|
|
|
495
496
|
let shell_cfg = config.shell || {};
|
|
496
497
|
|
|
497
498
|
/* Root */
|
|
498
|
-
let $
|
|
499
|
-
["div", {class: "yui-shell"}]
|
|
499
|
+
let $container = createElement2(
|
|
500
|
+
["div", {class: "C_YUI_SHELL yui-shell"}]
|
|
500
501
|
);
|
|
501
502
|
|
|
502
503
|
/* Build layers */
|
|
@@ -504,7 +505,7 @@ function build_ui(gobj)
|
|
|
504
505
|
let $layer = createElement2(
|
|
505
506
|
["div", {class: `yui-layer yui-layer-${id}`, style: `z-index:${z};`}]
|
|
506
507
|
);
|
|
507
|
-
$
|
|
508
|
+
$container.appendChild($layer);
|
|
508
509
|
priv.layers[id] = $layer;
|
|
509
510
|
}
|
|
510
511
|
|
|
@@ -561,9 +562,9 @@ function build_ui(gobj)
|
|
|
561
562
|
|
|
562
563
|
/* Mount */
|
|
563
564
|
let $mount = gobj_read_attr(gobj, "mount_element") || document.body;
|
|
564
|
-
$mount.appendChild($
|
|
565
|
+
$mount.appendChild($container);
|
|
565
566
|
|
|
566
|
-
gobj_write_attr(gobj, "$container", $
|
|
567
|
+
gobj_write_attr(gobj, "$container", $container);
|
|
567
568
|
}
|
|
568
569
|
|
|
569
570
|
/************************************************************
|
|
@@ -2085,6 +2086,151 @@ function ac_drawer_close_requested(gobj, event, kw, src)
|
|
|
2085
2086
|
return 0;
|
|
2086
2087
|
}
|
|
2087
2088
|
|
|
2089
|
+
/************************************************************
|
|
2090
|
+
* Close 'x' on a closable tab (from a C_YUI_NAV child). The
|
|
2091
|
+
* shell does not own the item set — re-publish so the app (which
|
|
2092
|
+
* owns the underlying data, e.g. the selected-nodes list) removes
|
|
2093
|
+
* the item and calls yui_shell_set_submenu() with the new list.
|
|
2094
|
+
************************************************************/
|
|
2095
|
+
function ac_nav_item_close(gobj, event, kw, src)
|
|
2096
|
+
{
|
|
2097
|
+
gobj_publish_event(gobj, "EV_NAV_ITEM_CLOSE", {
|
|
2098
|
+
item_id: (kw && kw.item_id) || "",
|
|
2099
|
+
route: (kw && kw.route) || "",
|
|
2100
|
+
menu_id: (kw && kw.menu_id) || "",
|
|
2101
|
+
zone: (kw && kw.zone) || ""
|
|
2102
|
+
});
|
|
2103
|
+
return 0;
|
|
2104
|
+
}
|
|
2105
|
+
|
|
2106
|
+
/************************************************************
|
|
2107
|
+
* Runtime nav API (Yuneta philosophy: the app_config path is the
|
|
2108
|
+
* first, startup caller of the very same machinery — build_item_index
|
|
2109
|
+
* + instantiate + set items; this is its dynamic counterpart).
|
|
2110
|
+
*
|
|
2111
|
+
* yui_shell_set_submenu(shell, parent_item_id, items) replaces the
|
|
2112
|
+
* items of a primary item's submenu (its secondary nav) at runtime
|
|
2113
|
+
* and re-registers their routes so navigation resolves. Item
|
|
2114
|
+
* descriptors may carry { id, name, icon, route, class, closable,
|
|
2115
|
+
* target:{stage,gclass,kw,lifecycle} }. Routes present before but
|
|
2116
|
+
* absent now are pruned (index entry removed, mounted view destroyed).
|
|
2117
|
+
************************************************************/
|
|
2118
|
+
function find_secondary_nav(priv, parent_item_id)
|
|
2119
|
+
{
|
|
2120
|
+
let suffix = "." + parent_item_id;
|
|
2121
|
+
for(let nav of priv.navs) {
|
|
2122
|
+
let menu_id = gobj_read_attr(nav, "menu_id") || "";
|
|
2123
|
+
if(menu_id.startsWith("secondary.") && menu_id.endsWith(suffix)) {
|
|
2124
|
+
return nav;
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
return null;
|
|
2128
|
+
}
|
|
2129
|
+
|
|
2130
|
+
function find_primary_item(shell_gobj, menu_id, item_id)
|
|
2131
|
+
{
|
|
2132
|
+
let config = gobj_read_attr(shell_gobj, "config") || {};
|
|
2133
|
+
let menu = (config.menu && config.menu[menu_id]) || null;
|
|
2134
|
+
if(menu && is_array(menu.items)) {
|
|
2135
|
+
for(let it of menu.items) {
|
|
2136
|
+
if(it && it.id === item_id) {
|
|
2137
|
+
return it;
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
return null;
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
/* Drop a route from the index and destroy any mounted view for it. */
|
|
2145
|
+
function prune_route(shell_gobj, route)
|
|
2146
|
+
{
|
|
2147
|
+
let priv = gobj_read_attr(shell_gobj, "priv");
|
|
2148
|
+
delete priv.item_index[route];
|
|
2149
|
+
for(let stage_name in priv.stages) {
|
|
2150
|
+
let stage = priv.stages[stage_name];
|
|
2151
|
+
let view = stage.items && stage.items[route];
|
|
2152
|
+
if(!view) {
|
|
2153
|
+
continue;
|
|
2154
|
+
}
|
|
2155
|
+
let $c = gobj_read_attr(view, "$container");
|
|
2156
|
+
if($c) {
|
|
2157
|
+
$c.classList.add("is-hidden");
|
|
2158
|
+
}
|
|
2159
|
+
try {
|
|
2160
|
+
gobj_stop(view);
|
|
2161
|
+
gobj_destroy(view);
|
|
2162
|
+
} catch(e) {
|
|
2163
|
+
log_warning(`C_YUI_SHELL: prune_route destroy '${route}' failed: ${e}`);
|
|
2164
|
+
}
|
|
2165
|
+
delete stage.items[route];
|
|
2166
|
+
if(stage.active_route === route) {
|
|
2167
|
+
stage.active_route = "";
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2172
|
+
function yui_shell_set_submenu(shell_gobj, parent_item_id, items)
|
|
2173
|
+
{
|
|
2174
|
+
let priv = gobj_read_attr(shell_gobj, "priv");
|
|
2175
|
+
if(!priv) {
|
|
2176
|
+
return -1;
|
|
2177
|
+
}
|
|
2178
|
+
let nav = find_secondary_nav(priv, parent_item_id);
|
|
2179
|
+
if(!nav) {
|
|
2180
|
+
log_warning(
|
|
2181
|
+
`C_YUI_SHELL: yui_shell_set_submenu — no secondary nav for '${parent_item_id}'`
|
|
2182
|
+
);
|
|
2183
|
+
return -1;
|
|
2184
|
+
}
|
|
2185
|
+
items = is_array(items) ? items : [];
|
|
2186
|
+
|
|
2187
|
+
/* Resolve owning menu_id + primary item from the nav's synthesized
|
|
2188
|
+
* id "secondary.<menu_id>.<parent_item_id>", so registered routes
|
|
2189
|
+
* highlight correctly and keep this secondary nav visible. */
|
|
2190
|
+
let nav_menu_id = gobj_read_attr(nav, "menu_id") || "";
|
|
2191
|
+
let m = /^secondary\.(.+)\.([^.]+)$/.exec(nav_menu_id);
|
|
2192
|
+
let owning_menu_id = m ? m[1] : "";
|
|
2193
|
+
let parent_item = find_primary_item(shell_gobj, owning_menu_id, parent_item_id);
|
|
2194
|
+
|
|
2195
|
+
/* Track the routes THIS submenu owns dynamically, so we only prune
|
|
2196
|
+
* our own previous routes — never the static config routes (e.g. a
|
|
2197
|
+
* base "/console/agent" landing declared in app_config). */
|
|
2198
|
+
priv.dynamic_routes = priv.dynamic_routes || {};
|
|
2199
|
+
let prev_routes = priv.dynamic_routes[parent_item_id] || [];
|
|
2200
|
+
let new_routes = [];
|
|
2201
|
+
let new_set = {};
|
|
2202
|
+
for(let it of items) {
|
|
2203
|
+
if(it && it.route) {
|
|
2204
|
+
new_routes.push(it.route);
|
|
2205
|
+
new_set[it.route] = true;
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
for(let route of prev_routes) {
|
|
2209
|
+
if(!new_set[route]) {
|
|
2210
|
+
prune_route(shell_gobj, route);
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
|
|
2214
|
+
/* Register / refresh routes for the new items. */
|
|
2215
|
+
for(let it of items) {
|
|
2216
|
+
if(!it || !it.route) {
|
|
2217
|
+
continue;
|
|
2218
|
+
}
|
|
2219
|
+
priv.item_index[it.route] = {
|
|
2220
|
+
item: it,
|
|
2221
|
+
parent_item: parent_item,
|
|
2222
|
+
stage: (it.target && it.target.stage) || null,
|
|
2223
|
+
target: it.target || null,
|
|
2224
|
+
menu_id: owning_menu_id
|
|
2225
|
+
};
|
|
2226
|
+
}
|
|
2227
|
+
priv.dynamic_routes[parent_item_id] = new_routes;
|
|
2228
|
+
|
|
2229
|
+
/* Push the new items into the nav (rebuilds its DOM in place). */
|
|
2230
|
+
gobj_send_event(nav, "EV_SET_ITEMS", {items: items}, shell_gobj);
|
|
2231
|
+
return 0;
|
|
2232
|
+
}
|
|
2233
|
+
|
|
2088
2234
|
/***************************************************************
|
|
2089
2235
|
* FSM
|
|
2090
2236
|
***************************************************************/
|
|
@@ -2113,13 +2259,19 @@ function create_gclass(gclass_name)
|
|
|
2113
2259
|
["EV_NAV_CLICKED", ac_nav_clicked, null],
|
|
2114
2260
|
/* Drawer backdrop close (from a C_YUI_NAV child whose
|
|
2115
2261
|
* layout is "drawer"). */
|
|
2116
|
-
["EV_DRAWER_CLOSE_REQUESTED", ac_drawer_close_requested, null]
|
|
2262
|
+
["EV_DRAWER_CLOSE_REQUESTED", ac_drawer_close_requested, null],
|
|
2263
|
+
/* Close 'x' on a closable tab (from a child C_YUI_NAV);
|
|
2264
|
+
* re-published for the app. */
|
|
2265
|
+
["EV_NAV_ITEM_CLOSE", ac_nav_item_close, null]
|
|
2117
2266
|
]]
|
|
2118
2267
|
];
|
|
2119
2268
|
|
|
2120
2269
|
const event_types = [
|
|
2121
2270
|
["EV_NAV_CLICKED", 0],
|
|
2122
2271
|
["EV_DRAWER_CLOSE_REQUESTED", 0],
|
|
2272
|
+
["EV_NAV_ITEM_CLOSE", event_flag_t.EVF_OUTPUT_EVENT
|
|
2273
|
+
|event_flag_t.EVF_PUBLIC_EVENT
|
|
2274
|
+
|event_flag_t.EVF_NO_WARN_SUBS],
|
|
2123
2275
|
/* Audit witness: every navigation attempt publishes this BEFORE
|
|
2124
2276
|
* any work is done, so the FSM trace records intent regardless
|
|
2125
2277
|
* of whether the route resolves successfully. Pairs with
|
|
@@ -2269,11 +2421,11 @@ function yui_shell_set_connection_state(shell_gobj, connected)
|
|
|
2269
2421
|
************************************************************/
|
|
2270
2422
|
function yui_shell_set_toolbar_item_icon(shell_gobj, item_id, icon_class)
|
|
2271
2423
|
{
|
|
2272
|
-
let $
|
|
2273
|
-
if(!$
|
|
2424
|
+
let $container = gobj_read_attr(shell_gobj, "$container");
|
|
2425
|
+
if(!$container || empty_string(item_id) || empty_string(icon_class)) {
|
|
2274
2426
|
return;
|
|
2275
2427
|
}
|
|
2276
|
-
let $i = $
|
|
2428
|
+
let $i = $container.querySelector(
|
|
2277
2429
|
`[data-toolbar-item-id="${item_id}"] .icon i`
|
|
2278
2430
|
);
|
|
2279
2431
|
if($i) {
|
|
@@ -2317,5 +2469,6 @@ export {
|
|
|
2317
2469
|
yui_shell_set_translator,
|
|
2318
2470
|
yui_shell_set_connection_state,
|
|
2319
2471
|
yui_shell_set_toolbar_item_icon,
|
|
2320
|
-
yui_shell_close_dropdown
|
|
2472
|
+
yui_shell_close_dropdown,
|
|
2473
|
+
yui_shell_set_submenu
|
|
2321
2474
|
};
|
|
@@ -183,7 +183,7 @@ function build_ui(gobj)
|
|
|
183
183
|
let tabs_style = gobj_read_attr(gobj, "tabs_style");
|
|
184
184
|
let $container = createElement2(
|
|
185
185
|
// Don't use is-flex, don't work well with is-hidden
|
|
186
|
-
['div', {class: `
|
|
186
|
+
['div', {class: `C_YUI_TABS`, style: 'height:100%; display:flex; flex-direction:column;'}, [
|
|
187
187
|
['div', {class: 'is-flex-grow-0'}, [
|
|
188
188
|
['div', {class: `tabs ${tabs_style}`, style: ''}, [
|
|
189
189
|
['ul', {}]
|
|
@@ -319,7 +319,7 @@ function build_ui(gobj)
|
|
|
319
319
|
|
|
320
320
|
let $container = createElement2(
|
|
321
321
|
// Don't use is-flex, don't work well with is-hidden
|
|
322
|
-
['div', {class: '
|
|
322
|
+
['div', {class: 'C_YUI_TREEDB_GRAPH', style: `height:100%; display:flex; flex-direction:column;`}, [
|
|
323
323
|
['div', {class: 'is-flex-grow-0 is-flex is-align-items-center toolbar_yui_treedb_graph'}, $toolbar],
|
|
324
324
|
['div', {class: `is-flex-grow-1 ${padding}`, style: 'height:100%; min-height:0; overflow:hidden;'}, [
|
|
325
325
|
['div', {id: priv.canvas_id, class: `graph-container`, style: 'height:100%; min-height:0;border: 1px solid var(--bulma-border-weak);border-radius:0.2rem;'}, [
|
|
@@ -333,6 +333,28 @@ function build_ui(gobj)
|
|
|
333
333
|
return $container;
|
|
334
334
|
}
|
|
335
335
|
|
|
336
|
+
/************************************************************
|
|
337
|
+
* Show a non-blocking inline error banner at the top of the view
|
|
338
|
+
* (used when the treedb schema `descs` cannot load). Reuses a single
|
|
339
|
+
* banner so retries don't stack.
|
|
340
|
+
************************************************************/
|
|
341
|
+
function show_load_error(gobj, message)
|
|
342
|
+
{
|
|
343
|
+
let $container = gobj_read_attr(gobj, "$container");
|
|
344
|
+
if(!$container) {
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
let $err = $container.querySelector(".treedb-load-error");
|
|
348
|
+
if(!$err) {
|
|
349
|
+
$err = createElement2(
|
|
350
|
+
["div", {class: "notification is-danger is-light m-3 treedb-load-error"}, []]
|
|
351
|
+
);
|
|
352
|
+
$container.insertBefore($err, $container.firstChild);
|
|
353
|
+
}
|
|
354
|
+
let treedb = gobj_read_attr(gobj, "treedb_name") || "";
|
|
355
|
+
$err.textContent = (treedb ? `${treedb}: ` : "") + (message || "cannot load treedb");
|
|
356
|
+
}
|
|
357
|
+
|
|
336
358
|
/************************************************************
|
|
337
359
|
* Destroy UI
|
|
338
360
|
************************************************************/
|
|
@@ -908,18 +930,22 @@ function ac_mt_command_answer(gobj, event, kw, src)
|
|
|
908
930
|
log_error(e);
|
|
909
931
|
return;
|
|
910
932
|
}
|
|
911
|
-
if(result < 0) {
|
|
912
|
-
display_error_message(
|
|
913
|
-
"Error",
|
|
914
|
-
t(comment)
|
|
915
|
-
);
|
|
916
|
-
// HACK don't return, pass errors when need it.
|
|
917
|
-
}
|
|
918
|
-
|
|
919
933
|
let __command__ = msg_iev_get_stack(gobj, kw, "command_stack", true);
|
|
920
934
|
let command = kw_get_str(gobj, __command__, "command", "", kw_flag_t.KW_REQUIRED);
|
|
921
935
|
let kw_command = kw_get_dict(gobj, __command__, "kw", {}, kw_flag_t.KW_REQUIRED);
|
|
922
936
|
|
|
937
|
+
if(result < 0) {
|
|
938
|
+
if(command === "descs") {
|
|
939
|
+
/* The schema couldn't load (not a treedb, no authz for it, backend
|
|
940
|
+
* down…). Show an inline banner in the view rather than a blocking
|
|
941
|
+
* app-modal that wedges the whole SPA behind an empty tab. */
|
|
942
|
+
show_load_error(gobj, t(comment));
|
|
943
|
+
} else {
|
|
944
|
+
display_error_message("Error", t(comment));
|
|
945
|
+
}
|
|
946
|
+
// HACK don't return for non-descs, pass errors when need it.
|
|
947
|
+
}
|
|
948
|
+
|
|
923
949
|
switch(command) {
|
|
924
950
|
case "descs":
|
|
925
951
|
if(result >= 0) {
|
|
@@ -506,7 +506,7 @@ function build_ui(gobj)
|
|
|
506
506
|
*----------------------------------------------*/
|
|
507
507
|
let table_id = gobj_read_str_attr(gobj, "table_id");
|
|
508
508
|
let $container = createElement2(
|
|
509
|
-
['div', {class: '
|
|
509
|
+
['div', {class: 'C_YUI_TREEDB_TOPIC_WITH_FORM', style: 'height:100%;'}, [
|
|
510
510
|
['div', {class: 'toolbar_tabulator_table m-1', style: 'display:flex; justify-content:space-between; align-items:center; flex-wrap:wrap;'}],
|
|
511
511
|
['div',
|
|
512
512
|
{
|
|
@@ -216,7 +216,7 @@ function build_ui(gobj)
|
|
|
216
216
|
* Layout Schema
|
|
217
217
|
*----------------------------------------------*/
|
|
218
218
|
let $container = createElement2(
|
|
219
|
-
['div', {class:
|
|
219
|
+
['div', {class: `C_YUI_TREEDB_TOPICS ${gobj_read_attr(gobj, "treedb_name")}`, style: 'height:100%; display:flex; flex-direction:column;'}, [
|
|
220
220
|
['div', {class: 'is-flex-grow-0'}, [
|
|
221
221
|
['div', {class: `tabs ${gobj_read_attr(gobj, "tabs_style")}`, style: ''}, [
|
|
222
222
|
['ul', {}]
|
|
@@ -229,6 +229,28 @@ function build_ui(gobj)
|
|
|
229
229
|
gobj_write_attr(gobj, "$container", $container);
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
+
/************************************************************
|
|
233
|
+
* Show a non-blocking inline error banner at the top of the view
|
|
234
|
+
* (used when the treedb schema `descs` cannot load). Reuses a single
|
|
235
|
+
* banner so retries don't stack.
|
|
236
|
+
************************************************************/
|
|
237
|
+
function show_load_error(gobj, message)
|
|
238
|
+
{
|
|
239
|
+
let $container = gobj_read_attr(gobj, "$container");
|
|
240
|
+
if(!$container) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
let $err = $container.querySelector(".treedb-load-error");
|
|
244
|
+
if(!$err) {
|
|
245
|
+
$err = createElement2(
|
|
246
|
+
["div", {class: "notification is-danger is-light m-3 treedb-load-error"}, []]
|
|
247
|
+
);
|
|
248
|
+
$container.insertBefore($err, $container.firstChild);
|
|
249
|
+
}
|
|
250
|
+
let treedb = gobj_read_attr(gobj, "treedb_name") || "";
|
|
251
|
+
$err.textContent = (treedb ? `${treedb}: ` : "") + (message || "cannot load treedb");
|
|
252
|
+
}
|
|
253
|
+
|
|
232
254
|
/************************************************************
|
|
233
255
|
* Destroy UI
|
|
234
256
|
************************************************************/
|
|
@@ -786,16 +808,22 @@ function ac_mt_command_answer(gobj, event, kw, src)
|
|
|
786
808
|
log_error(e);
|
|
787
809
|
return;
|
|
788
810
|
}
|
|
789
|
-
if(result < 0) {
|
|
790
|
-
display_error_message(
|
|
791
|
-
"Error",
|
|
792
|
-
t(comment)
|
|
793
|
-
);
|
|
794
|
-
}
|
|
795
|
-
|
|
796
811
|
let __command__ = msg_iev_get_stack(gobj, kw, "command_stack", true);
|
|
797
812
|
let command = kw_get_str(gobj, __command__, "command", "", kw_flag_t.KW_REQUIRED);
|
|
798
813
|
let kw_command = kw_get_dict(gobj, __command__, "kw", {}, kw_flag_t.KW_REQUIRED);
|
|
814
|
+
|
|
815
|
+
if(result < 0) {
|
|
816
|
+
if(command === "descs") {
|
|
817
|
+
/* The schema couldn't load (not a treedb, no authz for it, backend
|
|
818
|
+
* down…). Show an inline banner in the view rather than a blocking
|
|
819
|
+
* app-modal that wedges the whole SPA behind an empty tab. */
|
|
820
|
+
show_load_error(gobj, t(comment));
|
|
821
|
+
} else {
|
|
822
|
+
display_error_message("Error", t(comment));
|
|
823
|
+
}
|
|
824
|
+
return 0;
|
|
825
|
+
}
|
|
826
|
+
|
|
799
827
|
switch(command) {
|
|
800
828
|
case "descs":
|
|
801
829
|
if(result >= 0) {
|