@yuneta/gobj-ui 5.4.0 → 5.6.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 +114 -0
- package/dist/gobj-ui.cjs.js +562 -216
- package/dist/gobj-ui.es.js +559 -217
- package/index.js +6 -0
- package/package.json +44 -44
- package/src/c_yui_nav.js +6 -1
- package/src/c_yui_node.js +34 -2
- package/src/c_yui_service_view.js +362 -0
- package/src/c_yui_shell.css +47 -0
- package/src/c_yui_shell.js +148 -16
package/index.js
CHANGED
|
@@ -24,6 +24,11 @@ export { register_c_yui_uplot } from "./src/c_yui_uplot.js";
|
|
|
24
24
|
export { register_c_yui_json_graph } from "./src/c_yui_json_graph.js";
|
|
25
25
|
export { register_c_yui_json } from "./src/c_yui_json.js";
|
|
26
26
|
export { register_c_yui_gobj_tree_js } from "./src/c_yui_gobj_tree_js.js";
|
|
27
|
+
export {
|
|
28
|
+
register_c_yui_service_view,
|
|
29
|
+
yui_mount_service_view,
|
|
30
|
+
expose_view_container,
|
|
31
|
+
} from "./src/c_yui_service_view.js";
|
|
27
32
|
|
|
28
33
|
/*
|
|
29
34
|
* Declarative shell + menu navigation (new in v7.4)
|
|
@@ -41,6 +46,7 @@ export {
|
|
|
41
46
|
yui_shell_set_translator,
|
|
42
47
|
yui_shell_set_connection_state,
|
|
43
48
|
yui_shell_set_toolbar_item_icon,
|
|
49
|
+
yui_shell_set_toolbar_item_badge,
|
|
44
50
|
yui_shell_close_dropdown,
|
|
45
51
|
yui_shell_register_event_handler,
|
|
46
52
|
yui_shell_set_sub_routes,
|
package/package.json
CHANGED
|
@@ -1,47 +1,47 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
},
|
|
12
|
-
"./index.js": "./index.js",
|
|
13
|
-
"./vite-plugin-yuneta-html.js": "./vite-plugin-yuneta-html.js",
|
|
14
|
-
"./src/*": "./src/*"
|
|
2
|
+
"name": "@yuneta/gobj-ui",
|
|
3
|
+
"version": "5.6.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/gobj-ui.cjs.js",
|
|
6
|
+
"module": "dist/gobj-ui.es.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./dist/gobj-ui.es.js",
|
|
10
|
+
"require": "./dist/gobj-ui.cjs.js"
|
|
15
11
|
},
|
|
16
|
-
"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
12
|
+
"./index.js": "./index.js",
|
|
13
|
+
"./vite-plugin-yuneta-html.js": "./vite-plugin-yuneta-html.js",
|
|
14
|
+
"./src/*": "./src/*"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist/gobj-ui.es.js",
|
|
18
|
+
"dist/gobj-ui.cjs.js",
|
|
19
|
+
"index.js",
|
|
20
|
+
"src/",
|
|
21
|
+
"vite-plugin-yuneta-html.js"
|
|
22
|
+
],
|
|
23
|
+
"description": "Yuneta UI Library — the active line: declarative shell (C_YUI_SHELL+NAV+PAGER+WIZARD) + windows, TreeDB views, forms, charts and maps. Consumed by wattyzer and the yunetas JS yunos (gui_agent, gui_treedb). The frozen legacy GClass GUI stack (C_YUI_MAIN/TABS/ROUTING) lives on the v1 branch, npm dist-tag 'legacy' (consumed by estadodelaire and hidraulia).",
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "vite build",
|
|
26
|
+
"test": "vitest run",
|
|
27
|
+
"prepublishOnly": "npm run build"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@rollup/plugin-terser": "^1.0.0",
|
|
31
|
+
"@vitest/coverage-v8": "^4.1.10",
|
|
32
|
+
"@yuneta/gobj-js": "^7.8.7",
|
|
33
|
+
"vite": "^8.1.5",
|
|
34
|
+
"vitest": "^4.1.10"
|
|
35
|
+
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"@antv/g6": "^5.1.0",
|
|
38
|
+
"@yuneta/gobj-js": "^7.8.7",
|
|
39
|
+
"bulma": "^1.0.4",
|
|
40
|
+
"i18next": "^26.3.6",
|
|
41
|
+
"maplibre-gl": "^6.0.0",
|
|
42
|
+
"tabulator-tables": "^6.5.2",
|
|
43
|
+
"tom-select": "^2.6.2",
|
|
44
|
+
"uplot": "^1.6.32",
|
|
45
|
+
"vanilla-jsoneditor": "^3.13.0"
|
|
46
|
+
}
|
|
47
47
|
}
|
package/src/c_yui_nav.js
CHANGED
|
@@ -32,7 +32,12 @@
|
|
|
32
32
|
* container; build_ui re-applies them on every rebuild.
|
|
33
33
|
*
|
|
34
34
|
* Each item supports:
|
|
35
|
-
* id, name, icon (CSS class or svg id), route,
|
|
35
|
+
* id, name, icon (CSS class or svg id), route, disabled
|
|
36
|
+
* (`badge` was listed here for a long time and NOTHING ever
|
|
37
|
+
* rendered it — a menu item cannot carry a count today. The
|
|
38
|
+
* toolbar can: `badge` on a toolbar item plus
|
|
39
|
+
* yui_shell_set_toolbar_item_badge(). Implement it here the
|
|
40
|
+
* day a menu entry needs one, rather than re-listing it.)
|
|
36
41
|
* class — extra CSS class(es) on the item (tabs layout), e.g.
|
|
37
42
|
* a per-item state colour like "yui-nav-disconnected"
|
|
38
43
|
* closable — render a trailing ✕ that emits EV_NAV_ITEM_CLOSE
|
package/src/c_yui_node.js
CHANGED
|
@@ -137,6 +137,7 @@ import {
|
|
|
137
137
|
yui_shell_zone,
|
|
138
138
|
yui_shell_navigate,
|
|
139
139
|
yui_shell_set_sub_routes,
|
|
140
|
+
yui_shell_translate,
|
|
140
141
|
} from "./c_yui_shell.js";
|
|
141
142
|
|
|
142
143
|
import "./c_yui_node.css";
|
|
@@ -582,6 +583,30 @@ function walk_path(node, path)
|
|
|
582
583
|
return {node: g, missing: segs.slice(i)};
|
|
583
584
|
}
|
|
584
585
|
|
|
586
|
+
/************************************************************
|
|
587
|
+
* Translate a subtree this node just built.
|
|
588
|
+
*
|
|
589
|
+
* A node renders its chrome WHEN YOU WALK INTO IT, long after
|
|
590
|
+
* the host's one-shot refresh_language over the shell tree, and
|
|
591
|
+
* its zone navs are appended into shell zones, outside this
|
|
592
|
+
* node's own $container. Neither pass reaches them, so without
|
|
593
|
+
* this every strip renders its raw i18n key: `treedb`,
|
|
594
|
+
* `central database`, `data` in lower-case English next to a
|
|
595
|
+
* translated menu — which reads as a MISSING key and sent more
|
|
596
|
+
* than one person looking for the wrong bug.
|
|
597
|
+
*
|
|
598
|
+
* This is library-built DOM, so it goes through the shell's
|
|
599
|
+
* registered translator. What a node MOUNTS is not ours: an app
|
|
600
|
+
* view gclass translates its own DOM.
|
|
601
|
+
************************************************************/
|
|
602
|
+
function translate_own_dom(gobj, $el)
|
|
603
|
+
{
|
|
604
|
+
if(!$el) {
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
yui_shell_translate(yui_shell_of(gobj), $el);
|
|
608
|
+
}
|
|
609
|
+
|
|
585
610
|
/************************************************************
|
|
586
611
|
* Destroy the C_YUI_NAV gobjs rendering this node's projection.
|
|
587
612
|
************************************************************/
|
|
@@ -669,6 +694,7 @@ function render_projection(gobj, mode, $where, active_route, active_id, zones_on
|
|
|
669
694
|
let $nav = gobj_read_attr(nav, "$container");
|
|
670
695
|
if($nav) {
|
|
671
696
|
$target.appendChild($nav);
|
|
697
|
+
translate_own_dom(gobj, $nav);
|
|
672
698
|
}
|
|
673
699
|
priv.navs.push(nav);
|
|
674
700
|
i++;
|
|
@@ -716,11 +742,14 @@ function project_into_zone(gobj, render, items, active_route, active_id)
|
|
|
716
742
|
let $nav = gobj_read_attr(nav, "$container");
|
|
717
743
|
if($nav) {
|
|
718
744
|
$zone.appendChild($nav);
|
|
745
|
+
translate_own_dom(gobj, $nav);
|
|
719
746
|
}
|
|
720
747
|
priv.zone_navs[key] = nav;
|
|
721
748
|
priv.zone_sig[key] = sig;
|
|
722
749
|
} else if(priv.zone_sig[key] !== sig) {
|
|
723
750
|
gobj_send_event(nav, "EV_SET_ITEMS", {items: items}, gobj);
|
|
751
|
+
/* EV_SET_ITEMS rebuilt the nav's DOM: translate it again. */
|
|
752
|
+
translate_own_dom(gobj, gobj_read_attr(nav, "$container"));
|
|
724
753
|
priv.zone_sig[key] = sig;
|
|
725
754
|
}
|
|
726
755
|
|
|
@@ -821,10 +850,12 @@ function render_self(gobj)
|
|
|
821
850
|
}
|
|
822
851
|
|
|
823
852
|
if(!view) {
|
|
824
|
-
|
|
853
|
+
let $empty = createElement2(
|
|
825
854
|
["div", {class: "NODE_EMPTY", i18n: "nothing here yet"},
|
|
826
855
|
"Nothing here yet."]
|
|
827
|
-
)
|
|
856
|
+
);
|
|
857
|
+
priv.$body.appendChild($empty);
|
|
858
|
+
translate_own_dom(gobj, $empty);
|
|
828
859
|
}
|
|
829
860
|
}
|
|
830
861
|
|
|
@@ -917,6 +948,7 @@ function render_path(gobj, $where)
|
|
|
917
948
|
let $nav = gobj_read_attr(nav, "$container");
|
|
918
949
|
if($nav) {
|
|
919
950
|
$where.appendChild($nav);
|
|
951
|
+
translate_own_dom(gobj, $nav);
|
|
920
952
|
}
|
|
921
953
|
priv.navs.push(nav);
|
|
922
954
|
i++;
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
/***********************************************************************
|
|
2
|
+
* c_yui_service_view.js
|
|
3
|
+
*
|
|
4
|
+
* Mounting a view that TALKS TO A BACKEND.
|
|
5
|
+
*
|
|
6
|
+
* THE PROBLEM
|
|
7
|
+
* -----------
|
|
8
|
+
* A view that asks the backend for data does it with
|
|
9
|
+
* gobj_command(remote, "...", kw, src = itself)
|
|
10
|
+
* and C_IEVENT_CLI routes the answer back with
|
|
11
|
+
* gobj_find_service(gobj_name(src))
|
|
12
|
+
* which only finds REGISTERED SERVICES.
|
|
13
|
+
*
|
|
14
|
+
* Neither host creates one: C_YUI_SHELL mounts a route's view with
|
|
15
|
+
* `gobj_create()` and C_YUI_NODE with `gobj_create_pure_child()`.
|
|
16
|
+
* So a backend-talking view mounted directly never receives a single
|
|
17
|
+
* answer — and not quietly: the ievent logs "service not found" once
|
|
18
|
+
* per answer while the view sits empty forever.
|
|
19
|
+
*
|
|
20
|
+
* There is a second half. A route's `target.kw` is STATIC JSON, so
|
|
21
|
+
* it cannot carry the live transport pointer the view needs. It has
|
|
22
|
+
* to be resolved at mount time and injected.
|
|
23
|
+
*
|
|
24
|
+
* THE PIECE
|
|
25
|
+
* ---------
|
|
26
|
+
* Both halves had been written four separate times, in three repos,
|
|
27
|
+
* each wrapper saying the same thing in its own header. They are
|
|
28
|
+
* here now, in two shapes, because the four callers are not alike:
|
|
29
|
+
*
|
|
30
|
+
* yui_mount_service_view(host, spec) — the helper. For a wrapper
|
|
31
|
+
* that has its OWN extras on top (bridging url segments to the
|
|
32
|
+
* hosted view, rebinding it when a connection drops). Those
|
|
33
|
+
* extras are app or route logic and do NOT belong here; the
|
|
34
|
+
* wrapper keeps them and drops only the boilerplate.
|
|
35
|
+
*
|
|
36
|
+
* C_YUI_SERVICE_VIEW — the gclass, for a route with NO extras:
|
|
37
|
+
* declare it in the route/node and name the view it hosts. It
|
|
38
|
+
* is the helper plus a lifecycle, nothing more.
|
|
39
|
+
*
|
|
40
|
+
* CONTRACT of the hosted gclass:
|
|
41
|
+
* - declares the attr the transport is injected under
|
|
42
|
+
* (`gobj_remote_yuno` by default),
|
|
43
|
+
* - builds its `$container` in mt_create (re-exposed as the
|
|
44
|
+
* host's, which is what the shell/node mounts),
|
|
45
|
+
* - flags EVF_PUBLIC_EVENT on what arrives from the backend
|
|
46
|
+
* (EV_MT_COMMAND_ANSWER and anything the remote publishes): the
|
|
47
|
+
* ievent drops events that are not public.
|
|
48
|
+
*
|
|
49
|
+
* NAMES: the service name must be UNIQUE per mount. A duplicate is
|
|
50
|
+
* not fatal and that is the danger — gobj-js logs "service ALREADY
|
|
51
|
+
* REGISTERED. Will be UPDATED" and REBINDS the name, so the answers
|
|
52
|
+
* of one view would land in the other. Derive it from the route (or
|
|
53
|
+
* from whatever else makes the mount unique: a connection id, a
|
|
54
|
+
* workspace) and never from the gclass alone.
|
|
55
|
+
*
|
|
56
|
+
* Copyright (c) 2026, ArtGins.
|
|
57
|
+
* All Rights Reserved.
|
|
58
|
+
***********************************************************************/
|
|
59
|
+
import {
|
|
60
|
+
SDATA,
|
|
61
|
+
SDATA_END,
|
|
62
|
+
data_type_t,
|
|
63
|
+
gclass_create,
|
|
64
|
+
gclass_find_by_name,
|
|
65
|
+
log_error,
|
|
66
|
+
gobj_parent,
|
|
67
|
+
gobj_read_attr,
|
|
68
|
+
gobj_read_str_attr,
|
|
69
|
+
gobj_read_pointer_attr,
|
|
70
|
+
gobj_write_attr,
|
|
71
|
+
gobj_subscribe_event,
|
|
72
|
+
gobj_find_service,
|
|
73
|
+
gobj_create_service,
|
|
74
|
+
gobj_start,
|
|
75
|
+
gobj_stop,
|
|
76
|
+
gobj_is_running,
|
|
77
|
+
gobj_short_name,
|
|
78
|
+
is_gobj,
|
|
79
|
+
is_string,
|
|
80
|
+
empty_string,
|
|
81
|
+
createElement2,
|
|
82
|
+
} from "@yuneta/gobj-js";
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
/***************************************************************
|
|
86
|
+
* Constants
|
|
87
|
+
***************************************************************/
|
|
88
|
+
const GCLASS_NAME = "C_YUI_SERVICE_VIEW";
|
|
89
|
+
|
|
90
|
+
/* What the four wrappers all did: the app's single transport, injected
|
|
91
|
+
* under the attr every backend-talking gclass in this library declares. */
|
|
92
|
+
const DEFAULT_TRANSPORT_SERVICE = "__remote_service__";
|
|
93
|
+
const DEFAULT_TRANSPORT_ATTR = "gobj_remote_yuno";
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
/***************************************************************
|
|
97
|
+
* Data
|
|
98
|
+
***************************************************************/
|
|
99
|
+
const attrs_table = [
|
|
100
|
+
SDATA(data_type_t.DTP_POINTER, "subscriber", 0, null, "Subscriber of output events"),
|
|
101
|
+
|
|
102
|
+
SDATA(data_type_t.DTP_STRING, "view_gclass", 0, "", "GClass of the hosted view (must be registered)"),
|
|
103
|
+
SDATA(data_type_t.DTP_STRING, "service_name", 0, "", "Service name of the hosted view — UNIQUE per mount"),
|
|
104
|
+
SDATA(data_type_t.DTP_JSON, "view_kw", 0, null, "kw passed to the hosted view"),
|
|
105
|
+
SDATA(data_type_t.DTP_STRING, "transport_service", 0, DEFAULT_TRANSPORT_SERVICE, "Service name of the live transport"),
|
|
106
|
+
SDATA(data_type_t.DTP_STRING, "transport_attr", 0, DEFAULT_TRANSPORT_ATTR, "Attr of the hosted view the transport is injected under"),
|
|
107
|
+
SDATA(data_type_t.DTP_POINTER, "$container", 0, null, "Root HTMLElement"),
|
|
108
|
+
SDATA_END()
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
let PRIVATE_DATA = {
|
|
112
|
+
view: null,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
let __gclass__ = null;
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
/***************************
|
|
121
|
+
* Public helper
|
|
122
|
+
***************************/
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
/************************************************************
|
|
128
|
+
* Create `spec.gclass` as a NAMED SERVICE under `host`, with the
|
|
129
|
+
* live transport injected, and return it (null on failure, error
|
|
130
|
+
* already logged). The caller owns what happens next — starting
|
|
131
|
+
* it, exposing its DOM, bridging routes into it.
|
|
132
|
+
*
|
|
133
|
+
* spec = {
|
|
134
|
+
* gclass, // string, required — must be registered
|
|
135
|
+
* name, // string, required — service name, unique per mount
|
|
136
|
+
* kw, // object, optional — the view's kw
|
|
137
|
+
* transport, // gobj | service name | omitted:
|
|
138
|
+
* // a gobj is used as-is (the caller already
|
|
139
|
+
* // resolved it — e.g. per connection), a string
|
|
140
|
+
* // is looked up with gobj_find_service(), and
|
|
141
|
+
* // omitted means "__remote_service__".
|
|
142
|
+
* // null DISABLES injection: for a hosted view
|
|
143
|
+
* // that takes its transport some other way.
|
|
144
|
+
* transport_attr // string, optional — default "gobj_remote_yuno"
|
|
145
|
+
* }
|
|
146
|
+
************************************************************/
|
|
147
|
+
function yui_mount_service_view(host, spec)
|
|
148
|
+
{
|
|
149
|
+
if(!host || !is_gobj(host) || !spec) {
|
|
150
|
+
log_error(`${GCLASS_NAME}: yui_mount_service_view without host or spec`);
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const gclass = spec.gclass;
|
|
155
|
+
const name = spec.name;
|
|
156
|
+
|
|
157
|
+
if(empty_string(gclass) || empty_string(name)) {
|
|
158
|
+
log_error(
|
|
159
|
+
`${gobj_short_name(host)}: mounting a service view needs gclass and name`
|
|
160
|
+
);
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
if(!gclass_find_by_name(gclass)) {
|
|
164
|
+
log_error(`${gobj_short_name(host)}: gclass '${gclass}' is not registered`);
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
let kw = Object.assign({}, spec.kw || {});
|
|
169
|
+
|
|
170
|
+
/* `undefined` means "the app's single transport"; an explicit null
|
|
171
|
+
* means "do not inject one". They are NOT the same, hence the
|
|
172
|
+
* hasOwnProperty test instead of a truthiness check. */
|
|
173
|
+
let transport = Object.prototype.hasOwnProperty.call(spec, "transport")
|
|
174
|
+
? spec.transport
|
|
175
|
+
: DEFAULT_TRANSPORT_SERVICE;
|
|
176
|
+
|
|
177
|
+
if(transport !== null) {
|
|
178
|
+
if(is_string(transport)) {
|
|
179
|
+
const service_name = transport;
|
|
180
|
+
transport = gobj_find_service(service_name);
|
|
181
|
+
if(!transport) {
|
|
182
|
+
log_error(
|
|
183
|
+
`${gobj_short_name(host)}: transport service ` +
|
|
184
|
+
`'${service_name}' not found`
|
|
185
|
+
);
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if(!is_gobj(transport)) {
|
|
190
|
+
log_error(`${gobj_short_name(host)}: transport is not a gobj`);
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
kw[spec.transport_attr || DEFAULT_TRANSPORT_ATTR] = transport;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const view = gobj_create_service(name, gclass, kw, host);
|
|
197
|
+
if(!view) {
|
|
198
|
+
log_error(`${gobj_short_name(host)}: cannot create '${gclass}' as '${name}'`);
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
return view;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/************************************************************
|
|
205
|
+
* The hosted view's $container, re-exposed as the host's — the
|
|
206
|
+
* shell/node mounts the HOST, so without this nothing is drawn.
|
|
207
|
+
* Never returns without setting one: an empty div beats a blank
|
|
208
|
+
* screen with the reason only in the console.
|
|
209
|
+
************************************************************/
|
|
210
|
+
function expose_view_container(host, view)
|
|
211
|
+
{
|
|
212
|
+
let $c = view ? gobj_read_attr(view, "$container") : null;
|
|
213
|
+
if(!$c) {
|
|
214
|
+
log_error(
|
|
215
|
+
`${gobj_short_name(host)}: hosted view does not expose $container ` +
|
|
216
|
+
`— check its mt_create`
|
|
217
|
+
);
|
|
218
|
+
$c = createElement2(["div", {}, ""]);
|
|
219
|
+
}
|
|
220
|
+
gobj_write_attr(host, "$container", $c);
|
|
221
|
+
return $c;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
/***************************
|
|
228
|
+
* Framework Methods
|
|
229
|
+
***************************/
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
/***************************************************************
|
|
235
|
+
* Framework Method: Create
|
|
236
|
+
***************************************************************/
|
|
237
|
+
function mt_create(gobj)
|
|
238
|
+
{
|
|
239
|
+
let priv = gobj.priv;
|
|
240
|
+
|
|
241
|
+
/*
|
|
242
|
+
* CHILD subscription model
|
|
243
|
+
*/
|
|
244
|
+
let subscriber = gobj_read_pointer_attr(gobj, "subscriber");
|
|
245
|
+
if(!subscriber) {
|
|
246
|
+
subscriber = gobj_parent(gobj);
|
|
247
|
+
}
|
|
248
|
+
gobj_subscribe_event(gobj, null, {}, subscriber);
|
|
249
|
+
|
|
250
|
+
priv.view = yui_mount_service_view(gobj, {
|
|
251
|
+
gclass: gobj_read_str_attr(gobj, "view_gclass"),
|
|
252
|
+
name: gobj_read_str_attr(gobj, "service_name"),
|
|
253
|
+
kw: gobj_read_attr(gobj, "view_kw") || {},
|
|
254
|
+
transport: gobj_read_str_attr(gobj, "transport_service"),
|
|
255
|
+
transport_attr: gobj_read_str_attr(gobj, "transport_attr")
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
expose_view_container(gobj, priv.view);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/***************************************************************
|
|
262
|
+
* Framework Method: Start
|
|
263
|
+
*
|
|
264
|
+
* The view starts HERE and not in mt_create, so its first
|
|
265
|
+
* request goes out when the host shows it.
|
|
266
|
+
***************************************************************/
|
|
267
|
+
function mt_start(gobj)
|
|
268
|
+
{
|
|
269
|
+
const view = gobj.priv.view;
|
|
270
|
+
if(view && !gobj_is_running(view)) {
|
|
271
|
+
gobj_start(view);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/***************************************************************
|
|
276
|
+
* Framework Method: Stop
|
|
277
|
+
***************************************************************/
|
|
278
|
+
function mt_stop(gobj)
|
|
279
|
+
{
|
|
280
|
+
const view = gobj.priv.view;
|
|
281
|
+
if(view && gobj_is_running(view)) {
|
|
282
|
+
gobj_stop(view);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/***************************************************************
|
|
287
|
+
* Framework Method: Destroy
|
|
288
|
+
*
|
|
289
|
+
* The view is a SERVICE created with this gobj as its parent, so
|
|
290
|
+
* gobj_destroy cascades onto it — and deregisters its name. Do
|
|
291
|
+
* NOT destroy it again here.
|
|
292
|
+
***************************************************************/
|
|
293
|
+
function mt_destroy(gobj)
|
|
294
|
+
{
|
|
295
|
+
let priv = gobj.priv;
|
|
296
|
+
|
|
297
|
+
priv.view = null;
|
|
298
|
+
|
|
299
|
+
const $c = gobj_read_attr(gobj, "$container");
|
|
300
|
+
if($c && $c.parentNode) {
|
|
301
|
+
$c.parentNode.removeChild($c);
|
|
302
|
+
}
|
|
303
|
+
gobj_write_attr(gobj, "$container", null);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
/***************************
|
|
310
|
+
* FSM
|
|
311
|
+
***************************/
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
const gmt = {
|
|
317
|
+
mt_create: mt_create,
|
|
318
|
+
mt_start: mt_start,
|
|
319
|
+
mt_stop: mt_stop,
|
|
320
|
+
mt_destroy: mt_destroy
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
function create_gclass(gclass_name)
|
|
324
|
+
{
|
|
325
|
+
if(__gclass__) {
|
|
326
|
+
log_error(`GClass ALREADY created: ${gclass_name}`);
|
|
327
|
+
return -1;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const states = [
|
|
331
|
+
["ST_IDLE", []]
|
|
332
|
+
];
|
|
333
|
+
|
|
334
|
+
const event_types = [];
|
|
335
|
+
|
|
336
|
+
__gclass__ = gclass_create(
|
|
337
|
+
gclass_name,
|
|
338
|
+
event_types,
|
|
339
|
+
states,
|
|
340
|
+
gmt,
|
|
341
|
+
0,
|
|
342
|
+
attrs_table,
|
|
343
|
+
PRIVATE_DATA,
|
|
344
|
+
0,
|
|
345
|
+
0,
|
|
346
|
+
0,
|
|
347
|
+
0
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
return __gclass__ ? 0 : -1;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function register_c_yui_service_view()
|
|
354
|
+
{
|
|
355
|
+
return create_gclass(GCLASS_NAME);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export {
|
|
359
|
+
register_c_yui_service_view,
|
|
360
|
+
yui_mount_service_view,
|
|
361
|
+
expose_view_container,
|
|
362
|
+
};
|
package/src/c_yui_shell.css
CHANGED
|
@@ -449,6 +449,53 @@
|
|
|
449
449
|
background-color: var(--bulma-scheme-main-bis, rgba(0,0,0,0.04));
|
|
450
450
|
}
|
|
451
451
|
|
|
452
|
+
/*------------------------------------------------------------
|
|
453
|
+
* Toolbar badge — the count pinned to an item's icon
|
|
454
|
+
*
|
|
455
|
+
* Anchored to the ICON and not to the button: with its label on
|
|
456
|
+
* (desktop) the button is wide, and a badge in its corner would
|
|
457
|
+
* float away from the glyph it counts.
|
|
458
|
+
*
|
|
459
|
+
* `hidden` is honoured explicitly: the element is always in the
|
|
460
|
+
* DOM (so the runtime setter has somewhere to write) and
|
|
461
|
+
* [hidden] loses to `display:inline-flex` without this rule.
|
|
462
|
+
*
|
|
463
|
+
* Danger colour on purpose — a badge exists to pull the eye. It
|
|
464
|
+
* is its own colour pair in both schemes rather than a Bulma
|
|
465
|
+
* helper, so it keeps its contrast when the toolbar background
|
|
466
|
+
* changes with the theme. No transition: house rule.
|
|
467
|
+
*------------------------------------------------------------*/
|
|
468
|
+
.yui-toolbar-icon {
|
|
469
|
+
position: relative;
|
|
470
|
+
overflow: visible;
|
|
471
|
+
}
|
|
472
|
+
.yui-toolbar-badge {
|
|
473
|
+
position: absolute;
|
|
474
|
+
top: -0.35em;
|
|
475
|
+
right: -0.55em;
|
|
476
|
+
display: inline-flex;
|
|
477
|
+
align-items: center;
|
|
478
|
+
justify-content: center;
|
|
479
|
+
box-sizing: border-box;
|
|
480
|
+
/* A round dot for one digit, a pill from two on. */
|
|
481
|
+
min-width: 1.15em;
|
|
482
|
+
height: 1.15em;
|
|
483
|
+
padding: 0 0.3em;
|
|
484
|
+
border-radius: 999px;
|
|
485
|
+
font-size: 0.62em;
|
|
486
|
+
font-weight: 700;
|
|
487
|
+
line-height: 1;
|
|
488
|
+
font-family: inherit;
|
|
489
|
+
background-color: var(--bulma-danger, #f14668);
|
|
490
|
+
color: var(--bulma-danger-invert, #fff);
|
|
491
|
+
/* Separates the pill from the glyph when they overlap. */
|
|
492
|
+
box-shadow: 0 0 0 0.12em var(--bulma-scheme-main, #fff);
|
|
493
|
+
pointer-events: none;
|
|
494
|
+
}
|
|
495
|
+
.yui-toolbar-badge[hidden] {
|
|
496
|
+
display: none;
|
|
497
|
+
}
|
|
498
|
+
|
|
452
499
|
/*============================================================
|
|
453
500
|
* Toolbar — type:"brand"
|
|
454
501
|
* Logo image + wordmark text. Renders as <button> when
|