@yuneta/gobj-ui 2.0.0 → 2.2.3
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 +2340 -528
- package/dist/gobj-ui.es.js +2340 -530
- package/index.js +32 -31
- package/package.json +6 -7
- 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} +175 -13
- 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 +1484 -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
|
/************************************************************
|
|
@@ -1049,10 +1050,11 @@ function navigate_to(gobj, route)
|
|
|
1049
1050
|
let prev_route = stage.active_route;
|
|
1050
1051
|
if(prev_route && prev_route !== matched_route) {
|
|
1051
1052
|
let prev_gobj = stage.items[prev_route];
|
|
1053
|
+
let $prev = null;
|
|
1052
1054
|
if(prev_gobj) {
|
|
1053
|
-
|
|
1054
|
-
if($
|
|
1055
|
-
$
|
|
1055
|
+
$prev = gobj_read_attr(prev_gobj, "$container");
|
|
1056
|
+
if($prev) {
|
|
1057
|
+
$prev.classList.add("is-hidden");
|
|
1056
1058
|
}
|
|
1057
1059
|
}
|
|
1058
1060
|
/* lazy_destroy: drop previous on exit */
|
|
@@ -1064,6 +1066,14 @@ function navigate_to(gobj, route)
|
|
|
1064
1066
|
} catch(e) {
|
|
1065
1067
|
log_warning(`C_YUI_SHELL: lazy_destroy of '${prev_route}' failed: ${e}`);
|
|
1066
1068
|
}
|
|
1069
|
+
/* The shell appended $container on mount (build_view_gobj), so
|
|
1070
|
+
* remove it symmetrically here: a view that doesn't remove its
|
|
1071
|
+
* own container in mt_destroy would otherwise leak a hidden
|
|
1072
|
+
* copy in the stage on every revisit — and any fixed DOM id
|
|
1073
|
+
* inside it then shadows the fresh instance's. */
|
|
1074
|
+
if($prev && $prev.parentNode) {
|
|
1075
|
+
$prev.parentNode.removeChild($prev);
|
|
1076
|
+
}
|
|
1067
1077
|
delete stage.items[prev_route];
|
|
1068
1078
|
}
|
|
1069
1079
|
}
|
|
@@ -2085,6 +2095,151 @@ function ac_drawer_close_requested(gobj, event, kw, src)
|
|
|
2085
2095
|
return 0;
|
|
2086
2096
|
}
|
|
2087
2097
|
|
|
2098
|
+
/************************************************************
|
|
2099
|
+
* Close 'x' on a closable tab (from a C_YUI_NAV child). The
|
|
2100
|
+
* shell does not own the item set — re-publish so the app (which
|
|
2101
|
+
* owns the underlying data, e.g. the selected-nodes list) removes
|
|
2102
|
+
* the item and calls yui_shell_set_submenu() with the new list.
|
|
2103
|
+
************************************************************/
|
|
2104
|
+
function ac_nav_item_close(gobj, event, kw, src)
|
|
2105
|
+
{
|
|
2106
|
+
gobj_publish_event(gobj, "EV_NAV_ITEM_CLOSE", {
|
|
2107
|
+
item_id: (kw && kw.item_id) || "",
|
|
2108
|
+
route: (kw && kw.route) || "",
|
|
2109
|
+
menu_id: (kw && kw.menu_id) || "",
|
|
2110
|
+
zone: (kw && kw.zone) || ""
|
|
2111
|
+
});
|
|
2112
|
+
return 0;
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
/************************************************************
|
|
2116
|
+
* Runtime nav API (Yuneta philosophy: the app_config path is the
|
|
2117
|
+
* first, startup caller of the very same machinery — build_item_index
|
|
2118
|
+
* + instantiate + set items; this is its dynamic counterpart).
|
|
2119
|
+
*
|
|
2120
|
+
* yui_shell_set_submenu(shell, parent_item_id, items) replaces the
|
|
2121
|
+
* items of a primary item's submenu (its secondary nav) at runtime
|
|
2122
|
+
* and re-registers their routes so navigation resolves. Item
|
|
2123
|
+
* descriptors may carry { id, name, icon, route, class, closable,
|
|
2124
|
+
* target:{stage,gclass,kw,lifecycle} }. Routes present before but
|
|
2125
|
+
* absent now are pruned (index entry removed, mounted view destroyed).
|
|
2126
|
+
************************************************************/
|
|
2127
|
+
function find_secondary_nav(priv, parent_item_id)
|
|
2128
|
+
{
|
|
2129
|
+
let suffix = "." + parent_item_id;
|
|
2130
|
+
for(let nav of priv.navs) {
|
|
2131
|
+
let menu_id = gobj_read_attr(nav, "menu_id") || "";
|
|
2132
|
+
if(menu_id.startsWith("secondary.") && menu_id.endsWith(suffix)) {
|
|
2133
|
+
return nav;
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
return null;
|
|
2137
|
+
}
|
|
2138
|
+
|
|
2139
|
+
function find_primary_item(shell_gobj, menu_id, item_id)
|
|
2140
|
+
{
|
|
2141
|
+
let config = gobj_read_attr(shell_gobj, "config") || {};
|
|
2142
|
+
let menu = (config.menu && config.menu[menu_id]) || null;
|
|
2143
|
+
if(menu && is_array(menu.items)) {
|
|
2144
|
+
for(let it of menu.items) {
|
|
2145
|
+
if(it && it.id === item_id) {
|
|
2146
|
+
return it;
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
return null;
|
|
2151
|
+
}
|
|
2152
|
+
|
|
2153
|
+
/* Drop a route from the index and destroy any mounted view for it. */
|
|
2154
|
+
function prune_route(shell_gobj, route)
|
|
2155
|
+
{
|
|
2156
|
+
let priv = gobj_read_attr(shell_gobj, "priv");
|
|
2157
|
+
delete priv.item_index[route];
|
|
2158
|
+
for(let stage_name in priv.stages) {
|
|
2159
|
+
let stage = priv.stages[stage_name];
|
|
2160
|
+
let view = stage.items && stage.items[route];
|
|
2161
|
+
if(!view) {
|
|
2162
|
+
continue;
|
|
2163
|
+
}
|
|
2164
|
+
let $c = gobj_read_attr(view, "$container");
|
|
2165
|
+
if($c) {
|
|
2166
|
+
$c.classList.add("is-hidden");
|
|
2167
|
+
}
|
|
2168
|
+
try {
|
|
2169
|
+
gobj_stop(view);
|
|
2170
|
+
gobj_destroy(view);
|
|
2171
|
+
} catch(e) {
|
|
2172
|
+
log_warning(`C_YUI_SHELL: prune_route destroy '${route}' failed: ${e}`);
|
|
2173
|
+
}
|
|
2174
|
+
delete stage.items[route];
|
|
2175
|
+
if(stage.active_route === route) {
|
|
2176
|
+
stage.active_route = "";
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2181
|
+
function yui_shell_set_submenu(shell_gobj, parent_item_id, items)
|
|
2182
|
+
{
|
|
2183
|
+
let priv = gobj_read_attr(shell_gobj, "priv");
|
|
2184
|
+
if(!priv) {
|
|
2185
|
+
return -1;
|
|
2186
|
+
}
|
|
2187
|
+
let nav = find_secondary_nav(priv, parent_item_id);
|
|
2188
|
+
if(!nav) {
|
|
2189
|
+
log_warning(
|
|
2190
|
+
`C_YUI_SHELL: yui_shell_set_submenu — no secondary nav for '${parent_item_id}'`
|
|
2191
|
+
);
|
|
2192
|
+
return -1;
|
|
2193
|
+
}
|
|
2194
|
+
items = is_array(items) ? items : [];
|
|
2195
|
+
|
|
2196
|
+
/* Resolve owning menu_id + primary item from the nav's synthesized
|
|
2197
|
+
* id "secondary.<menu_id>.<parent_item_id>", so registered routes
|
|
2198
|
+
* highlight correctly and keep this secondary nav visible. */
|
|
2199
|
+
let nav_menu_id = gobj_read_attr(nav, "menu_id") || "";
|
|
2200
|
+
let m = /^secondary\.(.+)\.([^.]+)$/.exec(nav_menu_id);
|
|
2201
|
+
let owning_menu_id = m ? m[1] : "";
|
|
2202
|
+
let parent_item = find_primary_item(shell_gobj, owning_menu_id, parent_item_id);
|
|
2203
|
+
|
|
2204
|
+
/* Track the routes THIS submenu owns dynamically, so we only prune
|
|
2205
|
+
* our own previous routes — never the static config routes (e.g. a
|
|
2206
|
+
* base "/console/agent" landing declared in app_config). */
|
|
2207
|
+
priv.dynamic_routes = priv.dynamic_routes || {};
|
|
2208
|
+
let prev_routes = priv.dynamic_routes[parent_item_id] || [];
|
|
2209
|
+
let new_routes = [];
|
|
2210
|
+
let new_set = {};
|
|
2211
|
+
for(let it of items) {
|
|
2212
|
+
if(it && it.route) {
|
|
2213
|
+
new_routes.push(it.route);
|
|
2214
|
+
new_set[it.route] = true;
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
for(let route of prev_routes) {
|
|
2218
|
+
if(!new_set[route]) {
|
|
2219
|
+
prune_route(shell_gobj, route);
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2223
|
+
/* Register / refresh routes for the new items. */
|
|
2224
|
+
for(let it of items) {
|
|
2225
|
+
if(!it || !it.route) {
|
|
2226
|
+
continue;
|
|
2227
|
+
}
|
|
2228
|
+
priv.item_index[it.route] = {
|
|
2229
|
+
item: it,
|
|
2230
|
+
parent_item: parent_item,
|
|
2231
|
+
stage: (it.target && it.target.stage) || null,
|
|
2232
|
+
target: it.target || null,
|
|
2233
|
+
menu_id: owning_menu_id
|
|
2234
|
+
};
|
|
2235
|
+
}
|
|
2236
|
+
priv.dynamic_routes[parent_item_id] = new_routes;
|
|
2237
|
+
|
|
2238
|
+
/* Push the new items into the nav (rebuilds its DOM in place). */
|
|
2239
|
+
gobj_send_event(nav, "EV_SET_ITEMS", {items: items}, shell_gobj);
|
|
2240
|
+
return 0;
|
|
2241
|
+
}
|
|
2242
|
+
|
|
2088
2243
|
/***************************************************************
|
|
2089
2244
|
* FSM
|
|
2090
2245
|
***************************************************************/
|
|
@@ -2113,13 +2268,19 @@ function create_gclass(gclass_name)
|
|
|
2113
2268
|
["EV_NAV_CLICKED", ac_nav_clicked, null],
|
|
2114
2269
|
/* Drawer backdrop close (from a C_YUI_NAV child whose
|
|
2115
2270
|
* layout is "drawer"). */
|
|
2116
|
-
["EV_DRAWER_CLOSE_REQUESTED", ac_drawer_close_requested, null]
|
|
2271
|
+
["EV_DRAWER_CLOSE_REQUESTED", ac_drawer_close_requested, null],
|
|
2272
|
+
/* Close 'x' on a closable tab (from a child C_YUI_NAV);
|
|
2273
|
+
* re-published for the app. */
|
|
2274
|
+
["EV_NAV_ITEM_CLOSE", ac_nav_item_close, null]
|
|
2117
2275
|
]]
|
|
2118
2276
|
];
|
|
2119
2277
|
|
|
2120
2278
|
const event_types = [
|
|
2121
2279
|
["EV_NAV_CLICKED", 0],
|
|
2122
2280
|
["EV_DRAWER_CLOSE_REQUESTED", 0],
|
|
2281
|
+
["EV_NAV_ITEM_CLOSE", event_flag_t.EVF_OUTPUT_EVENT
|
|
2282
|
+
|event_flag_t.EVF_PUBLIC_EVENT
|
|
2283
|
+
|event_flag_t.EVF_NO_WARN_SUBS],
|
|
2123
2284
|
/* Audit witness: every navigation attempt publishes this BEFORE
|
|
2124
2285
|
* any work is done, so the FSM trace records intent regardless
|
|
2125
2286
|
* of whether the route resolves successfully. Pairs with
|
|
@@ -2269,11 +2430,11 @@ function yui_shell_set_connection_state(shell_gobj, connected)
|
|
|
2269
2430
|
************************************************************/
|
|
2270
2431
|
function yui_shell_set_toolbar_item_icon(shell_gobj, item_id, icon_class)
|
|
2271
2432
|
{
|
|
2272
|
-
let $
|
|
2273
|
-
if(!$
|
|
2433
|
+
let $container = gobj_read_attr(shell_gobj, "$container");
|
|
2434
|
+
if(!$container || empty_string(item_id) || empty_string(icon_class)) {
|
|
2274
2435
|
return;
|
|
2275
2436
|
}
|
|
2276
|
-
let $i = $
|
|
2437
|
+
let $i = $container.querySelector(
|
|
2277
2438
|
`[data-toolbar-item-id="${item_id}"] .icon i`
|
|
2278
2439
|
);
|
|
2279
2440
|
if($i) {
|
|
@@ -2317,5 +2478,6 @@ export {
|
|
|
2317
2478
|
yui_shell_set_translator,
|
|
2318
2479
|
yui_shell_set_connection_state,
|
|
2319
2480
|
yui_shell_set_toolbar_item_icon,
|
|
2320
|
-
yui_shell_close_dropdown
|
|
2481
|
+
yui_shell_close_dropdown,
|
|
2482
|
+
yui_shell_set_submenu
|
|
2321
2483
|
};
|
|
@@ -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) {
|