@yuneta/gobj-ui 7.25.15 → 7.25.17
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 +106 -4
- package/dist/gobj-ui.cjs.js +274 -66
- package/dist/gobj-ui.es.js +274 -66
- package/package.json +2 -2
- package/src/c_g6_nodes_tree.js +37 -4
- package/src/c_yui_schema_editor.js +280 -47
- package/src/c_yui_treedb_graph.js +22 -6
- package/src/form_busy.test.js +1 -1
- package/src/form_writes_in_flight.test.js +1 -1
- package/src/g6_graphs_write_owed.wiring.test.js +200 -0
- package/src/host_drafts.test.js +2 -2
- package/src/schema_editor_drafts.wiring.test.js +1 -1
- package/src/schema_editor_reload.wiring.test.js +313 -4
- package/src/shell_modals.js +29 -8
- package/src/shell_toast_once.test.js +63 -0
- package/src/table_cell_text.test.js +1 -1
- package/src/treedb_graph_writes.wiring.test.js +8 -1
- package/src/treedb_topics_transport.wiring.test.js +1 -1
- package/src/yui_install.js +14 -3
- package/src/yui_install.test.js +23 -0
package/src/shell_modals.js
CHANGED
|
@@ -613,9 +613,30 @@ function build_dialog(shell, message, buttons, opts)
|
|
|
613
613
|
}
|
|
614
614
|
|
|
615
615
|
|
|
616
|
+
/***************************************************************
|
|
617
|
+
* The labels a confirmation shows when the caller names none.
|
|
618
|
+
*
|
|
619
|
+
* They are i18n KEYS, lower-case like every key: a button
|
|
620
|
+
* translates its label with the app's t() -- its text, its title
|
|
621
|
+
* and its aria-label. Until 7.25.16 they were "OK", "Yes", "No",
|
|
622
|
+
* "Delete" and "Cancel", keys that no locale carries, so a dialog
|
|
623
|
+
* that took the defaults read in English in every language.
|
|
624
|
+
*
|
|
625
|
+
* Written as `i18n:` entries so the validate-locales script of a
|
|
626
|
+
* consumer finds them when it scans this module: an app that
|
|
627
|
+
* mounts these dialogs must define the five keys.
|
|
628
|
+
***************************************************************/
|
|
629
|
+
const DEFAULT_LABEL = Object.freeze({
|
|
630
|
+
ok: Object.freeze({i18n: "ok"}),
|
|
631
|
+
yes: Object.freeze({i18n: "yes"}),
|
|
632
|
+
no: Object.freeze({i18n: "no"}),
|
|
633
|
+
delete: Object.freeze({i18n: "delete"}),
|
|
634
|
+
cancel: Object.freeze({i18n: "cancel"})
|
|
635
|
+
});
|
|
636
|
+
|
|
616
637
|
export function yui_shell_confirm_ok(shell, message, opts)
|
|
617
638
|
{
|
|
618
|
-
let label = (opts && opts.ok_label) ||
|
|
639
|
+
let label = (opts && opts.ok_label) || DEFAULT_LABEL.ok.i18n;
|
|
619
640
|
if(!opts || !opts.type) {
|
|
620
641
|
opts = Object.assign({}, opts, {type: "success"});
|
|
621
642
|
}
|
|
@@ -626,8 +647,8 @@ export function yui_shell_confirm_ok(shell, message, opts)
|
|
|
626
647
|
|
|
627
648
|
export function yui_shell_confirm_yesno(shell, message, opts)
|
|
628
649
|
{
|
|
629
|
-
let yes_label = (opts && opts.yes_label) ||
|
|
630
|
-
let no_label = (opts && opts.no_label) ||
|
|
650
|
+
let yes_label = (opts && opts.yes_label) || DEFAULT_LABEL.yes.i18n;
|
|
651
|
+
let no_label = (opts && opts.no_label) || DEFAULT_LABEL.no.i18n;
|
|
631
652
|
return build_dialog(shell, message, [
|
|
632
653
|
{label: yes_label, value: "yes", kind: "primary"},
|
|
633
654
|
{label: no_label, value: "no"}
|
|
@@ -650,8 +671,8 @@ export function yui_shell_confirm_yesno(shell, message, opts)
|
|
|
650
671
|
***************************************************************/
|
|
651
672
|
export function yui_shell_confirm_danger(shell, message, opts)
|
|
652
673
|
{
|
|
653
|
-
let confirm_label = (opts && opts.confirm_label) ||
|
|
654
|
-
let cancel_label = (opts && opts.cancel_label) ||
|
|
674
|
+
let confirm_label = (opts && opts.confirm_label) || DEFAULT_LABEL.delete.i18n;
|
|
675
|
+
let cancel_label = (opts && opts.cancel_label) || DEFAULT_LABEL.cancel.i18n;
|
|
655
676
|
if(!opts || !opts.type) {
|
|
656
677
|
opts = Object.assign({}, opts, {type: "danger"});
|
|
657
678
|
}
|
|
@@ -663,9 +684,9 @@ export function yui_shell_confirm_danger(shell, message, opts)
|
|
|
663
684
|
|
|
664
685
|
export function yui_shell_confirm_yesnocancel(shell, message, opts)
|
|
665
686
|
{
|
|
666
|
-
let yes_label = (opts && opts.yes_label) ||
|
|
667
|
-
let no_label = (opts && opts.no_label) ||
|
|
668
|
-
let cancel_label = (opts && opts.cancel_label) ||
|
|
687
|
+
let yes_label = (opts && opts.yes_label) || DEFAULT_LABEL.yes.i18n;
|
|
688
|
+
let no_label = (opts && opts.no_label) || DEFAULT_LABEL.no.i18n;
|
|
689
|
+
let cancel_label = (opts && opts.cancel_label) || DEFAULT_LABEL.cancel.i18n;
|
|
669
690
|
return build_dialog(shell, message, [
|
|
670
691
|
{label: yes_label, value: "yes", kind: "primary"},
|
|
671
692
|
{label: no_label, value: "no"},
|
|
@@ -30,6 +30,9 @@ const {
|
|
|
30
30
|
yui_shell_show_info,
|
|
31
31
|
yui_shell_show_modal,
|
|
32
32
|
yui_shell_confirm_danger,
|
|
33
|
+
yui_shell_confirm_ok,
|
|
34
|
+
yui_shell_confirm_yesno,
|
|
35
|
+
yui_shell_confirm_yesnocancel,
|
|
33
36
|
} = await import("./shell_modals.js");
|
|
34
37
|
|
|
35
38
|
let shell = null;
|
|
@@ -200,3 +203,63 @@ describe("every other button of a dialog is named", () => {
|
|
|
200
203
|
]);
|
|
201
204
|
});
|
|
202
205
|
});
|
|
206
|
+
|
|
207
|
+
/*
|
|
208
|
+
* A confirmation that names no label shows the DEFAULT ones, and those
|
|
209
|
+
* are i18n keys like every other label: lower-case, translated by the
|
|
210
|
+
* app's t(), and carried as the key of the text, the title and the
|
|
211
|
+
* aria-label. They were "OK", "Yes", "No", "Delete" and "Cancel" --
|
|
212
|
+
* keys no locale has, so the button read in English in every language.
|
|
213
|
+
*/
|
|
214
|
+
describe("the default labels of a confirmation are i18n keys", () => {
|
|
215
|
+
|
|
216
|
+
function keys_of($layer)
|
|
217
|
+
{
|
|
218
|
+
return [...$layer.querySelectorAll(".CONFIRM_BTN")].map(($b) => [
|
|
219
|
+
$b.getAttribute("data-i18n"),
|
|
220
|
+
$b.getAttribute("data-i18n-title"),
|
|
221
|
+
$b.getAttribute("data-i18n-aria-label"),
|
|
222
|
+
]);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/* What the reader hears: the aria-label, translated by t() when
|
|
226
|
+
* the button is built. */
|
|
227
|
+
function texts_of($layer)
|
|
228
|
+
{
|
|
229
|
+
return [...$layer.querySelectorAll(".CONFIRM_BTN")].map(($b) => $b.getAttribute("aria-label"));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const k = (key) => [key, key, key];
|
|
233
|
+
|
|
234
|
+
/* A translator that tells a key it knows from one it does not. */
|
|
235
|
+
const ES = {ok: "Aceptar", yes: "Sí", no: "No", delete: "Borrar", cancel: "Cancelar"};
|
|
236
|
+
const t = (key) => ES[key] || `?${key}?`;
|
|
237
|
+
|
|
238
|
+
test("ok", () => {
|
|
239
|
+
shell.priv.layers.modal = document.createElement("div");
|
|
240
|
+
yui_shell_confirm_ok(shell, "saved", {t: t});
|
|
241
|
+
expect(keys_of(shell.priv.layers.modal)).toEqual([k("ok")]);
|
|
242
|
+
expect(texts_of(shell.priv.layers.modal)).toEqual(["Aceptar"]);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test("yes / no", () => {
|
|
246
|
+
shell.priv.layers.modal = document.createElement("div");
|
|
247
|
+
yui_shell_confirm_yesno(shell, "go on?", {t: t});
|
|
248
|
+
expect(keys_of(shell.priv.layers.modal)).toEqual([k("yes"), k("no")]);
|
|
249
|
+
expect(texts_of(shell.priv.layers.modal)).toEqual(["Sí", "No"]);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
test("yes / no / cancel", () => {
|
|
253
|
+
shell.priv.layers.modal = document.createElement("div");
|
|
254
|
+
yui_shell_confirm_yesnocancel(shell, "keep it?", {t: t});
|
|
255
|
+
expect(keys_of(shell.priv.layers.modal)).toEqual([k("yes"), k("no"), k("cancel")]);
|
|
256
|
+
expect(texts_of(shell.priv.layers.modal)).toEqual(["Sí", "No", "Cancelar"]);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
test("delete / cancel", () => {
|
|
260
|
+
shell.priv.layers.modal = document.createElement("div");
|
|
261
|
+
yui_shell_confirm_danger(shell, "delete this column?", {t: t});
|
|
262
|
+
expect(keys_of(shell.priv.layers.modal)).toEqual([k("delete"), k("cancel")]);
|
|
263
|
+
expect(texts_of(shell.priv.layers.modal)).toEqual(["Borrar", "Cancelar"]);
|
|
264
|
+
});
|
|
265
|
+
});
|
|
@@ -9,7 +9,7 @@ const doc = {
|
|
|
9
9
|
describe("table_cell_text", () => {
|
|
10
10
|
|
|
11
11
|
it("record text is a text node, markup included", () => {
|
|
12
|
-
/*
|
|
12
|
+
/* Tabulator puts a string through innerHTML */
|
|
13
13
|
const node = cell_text("limit a<b and c>d", doc);
|
|
14
14
|
expect(node.nodeType).toBe(3);
|
|
15
15
|
expect(node.textContent).toBe("limit a<b and c>d");
|
|
@@ -149,13 +149,19 @@ function save_arrangement(engine, topic)
|
|
|
149
149
|
});
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
+
/* The answer as the real transports build it: the command frame
|
|
153
|
+
* carries ONLY the request's `__md_command__` (C_IEVENT_CLI's
|
|
154
|
+
* mt_command, gui_agent's C_AGENT_TREEDB_LINK), never the request's
|
|
155
|
+
* own kw. Echoing the whole kw here once hid that the refusal read
|
|
156
|
+
* its topic from a `record` no real answer carries. */
|
|
152
157
|
function answer(host, remote, request, result, comment)
|
|
153
158
|
{
|
|
159
|
+
const md_command = request.kw.__md_command__ || {};
|
|
154
160
|
gobj_send_event(host, "EV_MT_COMMAND_ANSWER", {
|
|
155
161
|
result: result,
|
|
156
162
|
comment: comment || "",
|
|
157
163
|
data: null,
|
|
158
|
-
__md_iev__: {command_stack: [{command: request.command, kw:
|
|
164
|
+
__md_iev__: {command_stack: [{command: request.command, kw: md_command}]}
|
|
159
165
|
}, remote);
|
|
160
166
|
}
|
|
161
167
|
|
|
@@ -172,6 +178,7 @@ describe("a __graphs__ write that does not land is said to the engine", () => {
|
|
|
172
178
|
expect(commands.length).toBe(1);
|
|
173
179
|
answer(host, remote, commands[0], -1, "not authorized");
|
|
174
180
|
expect(refused()).toEqual(["yunos"]);
|
|
181
|
+
expect(logged.filter((l) => /names no topic/.test(l.msg))).toEqual([]);
|
|
175
182
|
});
|
|
176
183
|
|
|
177
184
|
test("landed: nothing is said", () => {
|
|
@@ -169,7 +169,7 @@ function errors()
|
|
|
169
169
|
return logged.filter((l) => l.level === "error").map((l) => l.msg);
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
-
describe("a Save in flight when the backend drops
|
|
172
|
+
describe("a Save in flight when the backend drops", () => {
|
|
173
173
|
|
|
174
174
|
test("the shell's connection edge answers every form, with no host forwarding", () => {
|
|
175
175
|
const {shell, topics, forms} = build("t1");
|
package/src/yui_install.js
CHANGED
|
@@ -194,6 +194,17 @@ function yui_install_start_watch()
|
|
|
194
194
|
* app can call yui_install_prompt() from a button of its
|
|
195
195
|
* own whenever it likes.
|
|
196
196
|
***************************************************************/
|
|
197
|
+
/* The question and its answers when the app names none. They are i18n
|
|
198
|
+
* KEYS, lower-case, and the ones every consumer already defines; the
|
|
199
|
+
* question was "Install this app?" until 7.25.16, a key no locale
|
|
200
|
+
* carries. Written as `i18n:` entries so the validate-locales script of
|
|
201
|
+
* a consumer finds them when it scans this module. */
|
|
202
|
+
const DEFAULT_ASK = Object.freeze({
|
|
203
|
+
message: Object.freeze({i18n: "install this app"}),
|
|
204
|
+
yes: Object.freeze({i18n: "install"}),
|
|
205
|
+
no: Object.freeze({i18n: "not now"})
|
|
206
|
+
});
|
|
207
|
+
|
|
197
208
|
function yui_install_ask_once(shell, opts)
|
|
198
209
|
{
|
|
199
210
|
opts = opts || {};
|
|
@@ -213,10 +224,10 @@ function yui_install_ask_once(shell, opts)
|
|
|
213
224
|
S.asking = true;
|
|
214
225
|
kw_set_local_storage_value(ASKED_KEY, true);
|
|
215
226
|
|
|
216
|
-
yui_shell_confirm_yesno(shell, opts.message ||
|
|
227
|
+
yui_shell_confirm_yesno(shell, opts.message || DEFAULT_ASK.message.i18n, {
|
|
217
228
|
t: opts.t,
|
|
218
|
-
yes_label: opts.yes_label ||
|
|
219
|
-
no_label: opts.no_label ||
|
|
229
|
+
yes_label: opts.yes_label || DEFAULT_ASK.yes.i18n,
|
|
230
|
+
no_label: opts.no_label || DEFAULT_ASK.no.i18n
|
|
220
231
|
}).then(function(yes) {
|
|
221
232
|
S.asking = false;
|
|
222
233
|
if(yes) {
|
package/src/yui_install.test.js
CHANGED
|
@@ -140,3 +140,26 @@ test("subscribe(): a throwing listener does not break the others", async () => {
|
|
|
140
140
|
off1();
|
|
141
141
|
off2();
|
|
142
142
|
});
|
|
143
|
+
|
|
144
|
+
/* The question asked when the app names none is an i18n KEY, the one
|
|
145
|
+
* every consumer already defines ("install this app"). It was
|
|
146
|
+
* "Install this app?", a key no locale carries, so an app that took
|
|
147
|
+
* the default asked in English in every language. */
|
|
148
|
+
test("ask_once(): the default question and answers are i18n keys", async () => {
|
|
149
|
+
const asked = [];
|
|
150
|
+
vi.resetModules();
|
|
151
|
+
vi.doMock("./shell_modals.js", () => ({
|
|
152
|
+
yui_shell_confirm_yesno: (shell, message, opts) => {
|
|
153
|
+
asked.push({message, yes: opts.yes_label, no: opts.no_label});
|
|
154
|
+
return new Promise(() => {});
|
|
155
|
+
}
|
|
156
|
+
}));
|
|
157
|
+
try {
|
|
158
|
+
const {yui_install_ask_once} = await import("./yui_install.js");
|
|
159
|
+
window[STASH] = fake_event("accepted");
|
|
160
|
+
yui_install_ask_once({fake_shell: true}, {});
|
|
161
|
+
expect(asked).toEqual([{message: "install this app", yes: "install", no: "not now"}]);
|
|
162
|
+
} finally {
|
|
163
|
+
vi.doUnmock("./shell_modals.js");
|
|
164
|
+
}
|
|
165
|
+
});
|