@yuneta/gobj-ui 7.26.26 → 7.26.28
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 +473 -19
- package/dist/gobj-ui.cjs.js +1100 -231
- package/dist/gobj-ui.es.js +1101 -232
- package/package.json +1 -1
- package/scripts/validate-locales.mjs +2334 -275
- package/src/c_g6_nodes_tree.js +1143 -260
- package/src/c_yui_form.js +11 -1
- package/src/c_yui_fsm_graph.js +5 -0
- package/src/c_yui_gclass.js +11 -0
- package/src/c_yui_gobj_tree_js.js +17 -0
- package/src/c_yui_json_graph.js +5 -0
- package/src/c_yui_nav.js +9 -6
- package/src/c_yui_pager.js +1 -1
- package/src/c_yui_period.js +11 -4
- package/src/c_yui_schema_editor.js +19 -7
- package/src/c_yui_shell.js +7 -7
- package/src/c_yui_treedb_graph.js +15 -6
- package/src/c_yui_treedb_schema.js +5 -0
- package/src/c_yui_treedb_topic_with_form.js +10 -10
- package/src/c_yui_treedb_topics.js +6 -6
- package/src/c_yui_window.js +2 -2
- package/src/c_yui_wizard.js +8 -1
- package/src/form_json_field.js +7 -0
- package/src/g6_canvas_tab_order.js +84 -0
- package/src/g6_canvas_tab_order.wiring.test.js +232 -0
- package/src/g6_nodes_tree_controls.test.js +547 -0
- package/src/g6_nodes_tree_focus.wiring.test.js +1360 -0
- package/src/g6_nodes_tree_i18n_keys.test.js +275 -0
- package/src/i18n_key_functions.js +146 -0
- package/src/i18n_key_functions.test.js +1204 -0
- package/src/lib_graph.js +38 -1
- package/src/nav_backbar_name.wiring.test.js +82 -0
- package/src/schema_flags.js +47 -0
- package/src/schema_import.js +7 -0
- package/src/shell_modals.js +20 -16
- package/src/shell_route_map.js +12 -2
- package/src/wizard_helpers.js +8 -0
- package/src/yui_dev.js +44 -33
- package/src/yui_dev_controls.test.js +174 -0
- package/src/yui_gclass_view.js +2 -2
- package/src/yui_graph_camera.js +1 -1
- package/src/yui_install.js +10 -8
- package/src/yui_table_select.js +2 -2
- package/src/yui_time.js +18 -3
package/src/c_g6_nodes_tree.js
CHANGED
|
@@ -178,11 +178,29 @@ import {yui_shell_of} from "./c_yui_shell.js";
|
|
|
178
178
|
import {yui_shell_show_error} from "./shell_modals.js";
|
|
179
179
|
import {g6_event_kw} from "./g6_event_kw.js";
|
|
180
180
|
import {css_color_or_null} from "./yui_data_color.js";
|
|
181
|
+
import {keep_canvas_tab_order} from "./g6_canvas_tab_order.js";
|
|
181
182
|
|
|
182
183
|
/***************************************************************
|
|
183
184
|
* The markup of one toolbar item. Everything that goes into it is
|
|
184
185
|
* escaped, the read-out branch too: its text, title and value are
|
|
185
186
|
* values (a zoom figure, a translated title), never markup.
|
|
187
|
+
*
|
|
188
|
+
* An item is a BUTTON for the keyboard too: `role="button"` and
|
|
189
|
+
* `tabindex="0"`, and YuiToolbar presses it on Enter or Space.
|
|
190
|
+
* `title_key` is the i18n key of `title`: written as
|
|
191
|
+
* `data-i18n-title` / `data-i18n-aria-label`, so the name carries
|
|
192
|
+
* its key like every other control's.
|
|
193
|
+
*
|
|
194
|
+
* A `disabled` item is born the way disableElements() (lib_graph.js)
|
|
195
|
+
* leaves one: `aria-disabled`, out of the Tab order, and the
|
|
196
|
+
* tabindex it takes once enabled kept in `data-enabled-tabindex`,
|
|
197
|
+
* where enableElements() finds it. A toggle (`pressed` given, true
|
|
198
|
+
* or false) says its state in `aria-pressed`, as its look says it
|
|
199
|
+
* with `pressed_state`. `data-control` names the CONTROL the item
|
|
200
|
+
* is, whatever state it shows: `control` when the item gives one
|
|
201
|
+
* (full screen is `request-fullscreen` or `exit-fullscreen`, and
|
|
202
|
+
* one control), its `value` otherwise -- it is what a rebuild puts
|
|
203
|
+
* the focus back on.
|
|
186
204
|
***************************************************************/
|
|
187
205
|
function toolbar_item_html(item)
|
|
188
206
|
{
|
|
@@ -192,17 +210,28 @@ function toolbar_item_html(item)
|
|
|
192
210
|
|
|
193
211
|
const extra_class = item.className ? ` ${escapeHtml(String(item.className))}` : '';
|
|
194
212
|
const title = escapeHtml(String(item.title ?? ''));
|
|
213
|
+
let i18n_attrs = '';
|
|
214
|
+
if(item.title_key) {
|
|
215
|
+
const key = escapeHtml(String(item.title_key));
|
|
216
|
+
i18n_attrs = ` data-i18n-title="${key}" data-i18n-aria-label="${key}"`;
|
|
217
|
+
}
|
|
195
218
|
|
|
196
219
|
if(item.readout) {
|
|
197
220
|
return (
|
|
198
221
|
`<div class="g6-toolbar-readout${extra_class}"` +
|
|
199
|
-
` title="${title}" aria-label="${title}">` +
|
|
222
|
+
` title="${title}" aria-label="${title}"${i18n_attrs}>` +
|
|
200
223
|
`${escapeHtml(String(item.text ?? ''))}` +
|
|
201
224
|
`</div>`
|
|
202
225
|
);
|
|
203
226
|
}
|
|
204
227
|
|
|
205
|
-
const
|
|
228
|
+
const state = item.disabled
|
|
229
|
+
? ' tabindex="-1" aria-disabled="true" data-enabled-tabindex="0" disabled'
|
|
230
|
+
: ' tabindex="0"';
|
|
231
|
+
const pressed = (item.pressed === undefined)
|
|
232
|
+
? ''
|
|
233
|
+
: ` aria-pressed="${item.pressed ? 'true' : 'false'}"`;
|
|
234
|
+
const control = escapeHtml(String(item.control ?? item.value));
|
|
206
235
|
const is_text = (item.text !== undefined);
|
|
207
236
|
const kind_class = is_text ? ' g6-toolbar-item-text' : '';
|
|
208
237
|
const body = is_text
|
|
@@ -213,8 +242,9 @@ function toolbar_item_html(item)
|
|
|
213
242
|
|
|
214
243
|
return (
|
|
215
244
|
`<div class="g6-toolbar-item${kind_class}${extra_class}"` +
|
|
216
|
-
`
|
|
217
|
-
`
|
|
245
|
+
` role="button"${state}${pressed}` +
|
|
246
|
+
` value="${escapeHtml(String(item.value))}" data-control="${control}"` +
|
|
247
|
+
` title="${title}" aria-label="${title}"${i18n_attrs}>` +
|
|
218
248
|
`${body}` +
|
|
219
249
|
`</div>`
|
|
220
250
|
);
|
|
@@ -233,6 +263,11 @@ function toolbar_item_html(item)
|
|
|
233
263
|
* readout — a text item that REPORTS instead of acting (the
|
|
234
264
|
* zoom level); it is not a button
|
|
235
265
|
* separator — a group divider, not an item
|
|
266
|
+
* title_key — the i18n key of `title` (see toolbar_item_html)
|
|
267
|
+
* pressed — boolean; the item is a TOGGLE, and this is its
|
|
268
|
+
* state (`aria-pressed`)
|
|
269
|
+
* control — the identity of the control when its `value`
|
|
270
|
+
* changes with its state (full screen enter/exit)
|
|
236
271
|
*
|
|
237
272
|
* The base class fires onClick only for elements whose class list
|
|
238
273
|
* contains `g6-toolbar-item`, so a separator and a readout carry
|
|
@@ -243,15 +278,168 @@ function toolbar_item_html(item)
|
|
|
243
278
|
* functions (set_submit_state, disableElements, …) work on toolbar
|
|
244
279
|
* icons exactly like on regular Bulma/FA buttons — no special
|
|
245
280
|
* icon-specific helpers needed.
|
|
281
|
+
*
|
|
282
|
+
* The items are buttons for the KEYBOARD as well:
|
|
283
|
+
* - Enter or Space CLICKS the focused item, and the click bubbles
|
|
284
|
+
* to the base class's own handler, so the key reaches the same
|
|
285
|
+
* onClick a pointer does. A `disabled` item takes the key and
|
|
286
|
+
* does nothing, as its `pointer-events: none` does for a press.
|
|
287
|
+
* - A POINTER press does not move the focus to an item (it stays
|
|
288
|
+
* on the canvas, where the graph's keys are heard), the way the
|
|
289
|
+
* floating buttons keep it (see keep_focus_on_press).
|
|
290
|
+
* - A disabled item is out of the Tab order and says it is
|
|
291
|
+
* disabled (`aria-disabled`), from its first render
|
|
292
|
+
* (toolbar_item_html) and on every change (disableElements /
|
|
293
|
+
* enableElements in lib_graph.js).
|
|
294
|
+
* - A rebuild (update) replaces the innerHTML, the focused item
|
|
295
|
+
* with it: the focus goes back to the item of the same CONTROL
|
|
296
|
+
* (`data-control`: Enter on "enter full screen" lands on "exit
|
|
297
|
+
* full screen"), or to the canvas when there is none. And a
|
|
298
|
+
* toolbar that goes away (destroy) hands it to the canvas
|
|
299
|
+
* first. A removed element that held the focus leaves it on
|
|
300
|
+
* <body> in Firefox, which fires no focusout for it, and the
|
|
301
|
+
* graph goes deaf.
|
|
246
302
|
***************************************************************/
|
|
247
303
|
class YuiToolbar extends Toolbar
|
|
248
304
|
{
|
|
305
|
+
constructor(context, options)
|
|
306
|
+
{
|
|
307
|
+
super(context, options);
|
|
308
|
+
this.$element.addEventListener('pointerdown', keep_focus_on_toolbar_press);
|
|
309
|
+
this.$element.addEventListener('keydown', press_toolbar_item_on_key);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async update(options)
|
|
313
|
+
{
|
|
314
|
+
const control = focused_toolbar_item_control(this.$element);
|
|
315
|
+
await super.update(options);
|
|
316
|
+
if(control !== null) {
|
|
317
|
+
refocus_toolbar_item(this.$element, control);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
destroy()
|
|
322
|
+
{
|
|
323
|
+
if(!this.destroyed && this.$element) {
|
|
324
|
+
this.$element.removeEventListener('pointerdown', keep_focus_on_toolbar_press);
|
|
325
|
+
this.$element.removeEventListener('keydown', press_toolbar_item_on_key);
|
|
326
|
+
if(this.$element.contains(document.activeElement)) {
|
|
327
|
+
focus_main_canvas(this.$element.parentNode);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
super.destroy();
|
|
331
|
+
}
|
|
332
|
+
|
|
249
333
|
async getDOMContent()
|
|
250
334
|
{
|
|
251
335
|
const items = await this.options.getItems();
|
|
252
336
|
return items.map(toolbar_item_html).join('');
|
|
253
337
|
}
|
|
254
338
|
}
|
|
339
|
+
|
|
340
|
+
/***************************************************************
|
|
341
|
+
* A POINTER press on a button of this view does not take the
|
|
342
|
+
* focus. The container's capture listener has just put it on the
|
|
343
|
+
* canvas, where the graph's keys are heard; a press that moved it
|
|
344
|
+
* on to the button would leave it on something this view removes
|
|
345
|
+
* -- Escape deselects and takes the floating buttons away, the ✕
|
|
346
|
+
* closes its popover, a rebuild replaces the toolbar -- and
|
|
347
|
+
* Firefox fires no focusout for a removed element: the focus falls
|
|
348
|
+
* to <body> and the graph hears nothing until it is clicked again.
|
|
349
|
+
* Cancelling the pointerdown cancels the mousedown that moves the
|
|
350
|
+
* focus; the click still comes, and Tab still reaches the button.
|
|
351
|
+
***************************************************************/
|
|
352
|
+
function keep_focus_on_press(el)
|
|
353
|
+
{
|
|
354
|
+
el.addEventListener('pointerdown', (e) => {
|
|
355
|
+
e.preventDefault();
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/* The same, for the items of a toolbar, which is rebuilt whole:
|
|
360
|
+
* delegated from the toolbar element. */
|
|
361
|
+
function keep_focus_on_toolbar_press(e)
|
|
362
|
+
{
|
|
363
|
+
let target = e.target;
|
|
364
|
+
if(target && typeof target.closest === 'function' &&
|
|
365
|
+
target.closest('.g6-toolbar-item')) {
|
|
366
|
+
e.preventDefault();
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/***************************************************************
|
|
371
|
+
* The keyboard to the graph: the focus on its main canvas.
|
|
372
|
+
***************************************************************/
|
|
373
|
+
function focus_main_canvas($container)
|
|
374
|
+
{
|
|
375
|
+
if(!$container) {
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
let $canvas = main_canvas_of($container);
|
|
379
|
+
if($canvas && $canvas.isConnected && typeof $canvas.focus === 'function') {
|
|
380
|
+
$canvas.focus({preventScroll: true});
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/***************************************************************
|
|
385
|
+
* Enter or Space on a toolbar item clicks it; the click bubbles
|
|
386
|
+
* to G6's toolbar handler, which reads the item's `value`.
|
|
387
|
+
***************************************************************/
|
|
388
|
+
function press_toolbar_item_on_key(e)
|
|
389
|
+
{
|
|
390
|
+
if(e.key !== 'Enter' && e.key !== ' ') {
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
let target = e.target;
|
|
394
|
+
let item = (target && typeof target.closest === 'function')?
|
|
395
|
+
target.closest('.g6-toolbar-item') : null;
|
|
396
|
+
if(!item) {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
/* The key pressed a button: it is not the graph's (the
|
|
400
|
+
* container forwards every keydown to G6), and Space would
|
|
401
|
+
* scroll the page. */
|
|
402
|
+
e.preventDefault();
|
|
403
|
+
e.stopPropagation();
|
|
404
|
+
if(item.hasAttribute('disabled')) {
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
item.click();
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/***************************************************************
|
|
411
|
+
* The control (`data-control`) of the toolbar item that has the
|
|
412
|
+
* focus, or null.
|
|
413
|
+
***************************************************************/
|
|
414
|
+
function focused_toolbar_item_control($toolbar)
|
|
415
|
+
{
|
|
416
|
+
let active = document.activeElement;
|
|
417
|
+
if(!$toolbar || !active || active === $toolbar || !$toolbar.contains(active)) {
|
|
418
|
+
return null;
|
|
419
|
+
}
|
|
420
|
+
if(!active.classList || !active.classList.contains('g6-toolbar-item')) {
|
|
421
|
+
return null;
|
|
422
|
+
}
|
|
423
|
+
return active.getAttribute('data-control');
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/***************************************************************
|
|
427
|
+
* After a rebuild: the focus on the item of that control, in
|
|
428
|
+
* whatever state the rebuild shows it, or on the canvas when the
|
|
429
|
+
* rebuild has none (a fold took it away).
|
|
430
|
+
***************************************************************/
|
|
431
|
+
function refocus_toolbar_item($toolbar, control)
|
|
432
|
+
{
|
|
433
|
+
let items = $toolbar.querySelectorAll('.g6-toolbar-item');
|
|
434
|
+
for(let i = 0; i < items.length; i++) {
|
|
435
|
+
if(items[i].getAttribute('data-control') === control) {
|
|
436
|
+
items[i].focus({preventScroll: true});
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
focus_main_canvas($toolbar.parentNode);
|
|
441
|
+
}
|
|
442
|
+
|
|
255
443
|
register(ExtensionCategory.PLUGIN, 'yui-toolbar', YuiToolbar);
|
|
256
444
|
ensure_drag_canvas_patch();
|
|
257
445
|
ensure_brush_select_owned();
|
|
@@ -340,6 +528,23 @@ const SHAPE_SIZE = {
|
|
|
340
528
|
* default: a figure is a POINT of the topology, and a circle is
|
|
341
529
|
* the one figure that says nothing more than that. */
|
|
342
530
|
const NODE_SHAPES = ['square', 'circle', 'diamond', 'triangle', 'hexagon', 'star'];
|
|
531
|
+
|
|
532
|
+
/* The keys of this view that reach t() as DATA or as an ARGUMENT -- the
|
|
533
|
+
* shape tables, the labels of the property popovers (create_form_label),
|
|
534
|
+
* the confirm button (show_confirm_popover), the names of the floating
|
|
535
|
+
* buttons (create_floating_icon, show_dual_icons) and the shell's error
|
|
536
|
+
* messages (yui_shell_show_error): read by a consumer's validate-locales,
|
|
537
|
+
* which asks the locales for every key of an exported `*_I18N_KEYS` array
|
|
538
|
+
* of a module the app mounts. A shape added to NODE_SHAPES or PORT_SHAPES,
|
|
539
|
+
* or a new key handed to one of those helpers, goes here too
|
|
540
|
+
* (g6_nodes_tree_i18n_keys.test.js checks every call). */
|
|
541
|
+
export const G6_NODES_TREE_I18N_KEYS = [
|
|
542
|
+
'square', 'circle', 'diamond', 'triangle', 'hexagon', 'star',
|
|
543
|
+
'shape', 'radius', 'apply to', 'line width', 'color', 'fill color', 'stroke color',
|
|
544
|
+
'delete', 'unlink',
|
|
545
|
+
'node properties', 'delete node', 'edge properties', 'link to hook', 'port properties',
|
|
546
|
+
'some records were gone before the delete'
|
|
547
|
+
];
|
|
343
548
|
const DEFAULT_NODE_SHAPE = 'circle';
|
|
344
549
|
|
|
345
550
|
/***************************************************************
|
|
@@ -506,6 +711,7 @@ let PRIVATE_DATA = {
|
|
|
506
711
|
_saved_graph_properties: {}, // the same, as the BACKEND has it: what a Save compares against
|
|
507
712
|
_graphs_writes_owed: {}, // topic_name → true: a __graphs__ write the host said did not land
|
|
508
713
|
_graphs_load: 0, // the LOAD a __graphs__ write belongs to: EV_CLEAR_DATA starts another
|
|
714
|
+
_save_lit: false, // what the Save was last told (light_save_button)
|
|
509
715
|
yet_showed: false,
|
|
510
716
|
edit_mode: false,
|
|
511
717
|
operation_mode: null,
|
|
@@ -540,6 +746,7 @@ let PRIVATE_DATA = {
|
|
|
540
746
|
_detail_node_id: null, // node id the detail popover is showing
|
|
541
747
|
_delete_confirm_el: null, // delete confirmation popover
|
|
542
748
|
_unlink_confirm_el: null, // unlink confirmation popover
|
|
749
|
+
_confirm_questions: {}, // priv key of a confirm popover → the {what, id|ids} it asks
|
|
543
750
|
_create_popover_el: null, // create node popover element
|
|
544
751
|
_context_node_id: null, // node id for context menu target
|
|
545
752
|
_context_port_key: null, // port key for context menu target (null = node body)
|
|
@@ -548,6 +755,7 @@ let PRIVATE_DATA = {
|
|
|
548
755
|
_link_source: null, // {node_id, port_key, col, topic_name} of fkey being linked
|
|
549
756
|
_link_icon_el: null, // floating link icon on fkey port
|
|
550
757
|
_link_drag_svg: null, // SVG overlay for drag line
|
|
758
|
+
_link_drag: null, // the link drag in flight: {left, top, unwire}
|
|
551
759
|
_link_valid_hooks: [], // [{node_id, port_key}] compatible hook targets
|
|
552
760
|
_link_saved_styles: [], // saved port styles to restore on cancel
|
|
553
761
|
_focus_topic: null, // topic currently focused (EV_FOCUS_TOPIC)
|
|
@@ -1034,6 +1242,10 @@ function build_graph(gobj)
|
|
|
1034
1242
|
*/
|
|
1035
1243
|
graph.setTheme(priv.theme);
|
|
1036
1244
|
|
|
1245
|
+
/* Tab goes from the graph to its own buttons: the main canvas in
|
|
1246
|
+
* the Tab order, the other layers out (g6_canvas_tab_order.js). */
|
|
1247
|
+
keep_canvas_tab_order(gobj, graph);
|
|
1248
|
+
|
|
1037
1249
|
/* A card is an HTML node, and G6's HTML nodes do not pass the wheel
|
|
1038
1250
|
* on: over a card the graph neither scrolled nor zoomed. Only a wheel
|
|
1039
1251
|
* over a CARD is handed back to the canvas -- the toolbars and the
|
|
@@ -1410,10 +1622,13 @@ function update_toolbar(gobj)
|
|
|
1410
1622
|
});
|
|
1411
1623
|
}
|
|
1412
1624
|
|
|
1413
|
-
/*
|
|
1414
|
-
*
|
|
1415
|
-
|
|
1416
|
-
|
|
1625
|
+
/* Nothing to tell the items after this: what each one shows --
|
|
1626
|
+
* pressed, lit, disabled -- is computed in its getItems(). The
|
|
1627
|
+
* rebuild lands LATER (G6 reads the list now and writes the
|
|
1628
|
+
* innerHTML once its promise resolves), so an element told here
|
|
1629
|
+
* was the one being replaced: zoom to selection came back
|
|
1630
|
+
* disabled over a selection, and save, undo and redo over
|
|
1631
|
+
* history still pending. */
|
|
1417
1632
|
}
|
|
1418
1633
|
|
|
1419
1634
|
/************************************************************
|
|
@@ -1477,12 +1692,12 @@ function toolbar_fold_item(gobj)
|
|
|
1477
1692
|
if(toolbars_collapsed(gobj)) {
|
|
1478
1693
|
return {
|
|
1479
1694
|
id: 'g6-icon-toolbar-show', value: 'toolbar-toggle',
|
|
1480
|
-
className: 'EV_TOGGLE_TOOLBARS', title: t('show toolbar')
|
|
1695
|
+
className: 'EV_TOGGLE_TOOLBARS', title: t('show toolbar'), title_key: 'show toolbar'
|
|
1481
1696
|
};
|
|
1482
1697
|
}
|
|
1483
1698
|
return {
|
|
1484
1699
|
id: 'g6-icon-toolbar-hide', value: 'toolbar-toggle',
|
|
1485
|
-
className: 'EV_TOGGLE_TOOLBARS', title: t('hide toolbar')
|
|
1700
|
+
className: 'EV_TOGGLE_TOOLBARS', title: t('hide toolbar'), title_key: 'hide toolbar'
|
|
1486
1701
|
};
|
|
1487
1702
|
}
|
|
1488
1703
|
|
|
@@ -1579,15 +1794,15 @@ function configure_toolbar(gobj)
|
|
|
1579
1794
|
}
|
|
1580
1795
|
|
|
1581
1796
|
let items = [
|
|
1582
|
-
{ id: 'g6-icon-zoom-in', value: 'zoom-in', className: 'EV_ZOOM_IN', title: t('zoom in') },
|
|
1583
|
-
{ id: 'g6-icon-zoom-out', value: 'zoom-out', className: 'EV_ZOOM_OUT', title: t('zoom out') },
|
|
1797
|
+
{ id: 'g6-icon-zoom-in', value: 'zoom-in', className: 'EV_ZOOM_IN', title: t('zoom in'), title_key: 'zoom in' },
|
|
1798
|
+
{ id: 'g6-icon-zoom-out', value: 'zoom-out', className: 'EV_ZOOM_OUT', title: t('zoom out'), title_key: 'zoom out' },
|
|
1584
1799
|
/* What the two buttons above change. Every editor
|
|
1585
1800
|
* that offers a zoom shows the number; without it
|
|
1586
1801
|
* `1:1` is a jump to a value nobody was told. */
|
|
1587
1802
|
{ readout: true, className: 'G6_ZOOM_LEVEL',
|
|
1588
|
-
text: zoom_percent_text(gobj), title: t('zoom level') },
|
|
1803
|
+
text: zoom_percent_text(gobj), title: t('zoom level'), title_key: 'zoom level' },
|
|
1589
1804
|
{ separator: true },
|
|
1590
|
-
{ id: 'g6-icon-fit', value: 'auto-fit', className: 'EV_AUTO_FIT', title: t('auto fit') },
|
|
1805
|
+
{ id: 'g6-icon-fit', value: 'auto-fit', className: 'EV_AUTO_FIT', title: t('auto fit'), title_key: 'auto fit' },
|
|
1591
1806
|
/* `1:1`, not a house. The action is `zoomTo(1)`: it
|
|
1592
1807
|
* sets the SCALE and leaves the camera where it was,
|
|
1593
1808
|
* which is neither of the two things a house means
|
|
@@ -1597,7 +1812,7 @@ function configure_toolbar(gobj)
|
|
|
1597
1812
|
* answered with the same corner of it at 100%.
|
|
1598
1813
|
* Actual size is WRITTEN in every editor that offers
|
|
1599
1814
|
* it, never drawn: there is no glyph for it. */
|
|
1600
|
-
{ text: '1:1', value: 'reset', className: 'EV_ZOOM_RESET', title: t('actual size') },
|
|
1815
|
+
{ text: '1:1', value: 'reset', className: 'EV_ZOOM_RESET', title: t('actual size'), title_key: 'actual size' },
|
|
1601
1816
|
/* Pick one node and every zoom leaves it in the
|
|
1602
1817
|
* middle. The pressed classes are baked in HERE and
|
|
1603
1818
|
* not set afterwards, for the same reason the
|
|
@@ -1612,21 +1827,25 @@ function configure_toolbar(gobj)
|
|
|
1612
1827
|
className: 'EV_TOGGLE_ANCHOR' +
|
|
1613
1828
|
(priv.anchor_state === "on"? ' pressed_state' : '') +
|
|
1614
1829
|
(priv.anchor_state === "arming"? ' color_pending_state' : ''),
|
|
1615
|
-
|
|
1830
|
+
pressed: priv.anchor_state === "on",
|
|
1831
|
+
...anchor_title(priv.anchor_state)
|
|
1616
1832
|
},
|
|
1617
1833
|
];
|
|
1618
1834
|
|
|
1619
1835
|
/* Only in edition, because that is where a selection
|
|
1620
1836
|
* can exist: a button that is permanently disabled is
|
|
1621
1837
|
* not a control, it is furniture. Disabled while the
|
|
1622
|
-
* selection is empty
|
|
1623
|
-
* funnel every change goes
|
|
1624
|
-
*
|
|
1838
|
+
* selection is empty, asked HERE as well as by
|
|
1839
|
+
* `paint_selection` (the one funnel every change goes
|
|
1840
|
+
* through, which tells the live item): a rebuild draws
|
|
1841
|
+
* the item from this list, and an item born disabled
|
|
1842
|
+
* came back that way over a selection. */
|
|
1625
1843
|
if(priv.edit_mode) {
|
|
1626
1844
|
items.push({
|
|
1627
1845
|
id: 'g6-icon-fit-selection', value: 'fit-selection',
|
|
1628
1846
|
className: 'EV_ZOOM_SELECTION',
|
|
1629
|
-
title: t('zoom to selection'),
|
|
1847
|
+
title: t('zoom to selection'), title_key: 'zoom to selection',
|
|
1848
|
+
disabled: !selection_is_painted(gobj)
|
|
1630
1849
|
});
|
|
1631
1850
|
}
|
|
1632
1851
|
|
|
@@ -1634,13 +1853,16 @@ function configure_toolbar(gobj)
|
|
|
1634
1853
|
/* Full screen is a WINDOW control, not a camera one:
|
|
1635
1854
|
* it goes in its own group. */
|
|
1636
1855
|
items.push({ separator: true });
|
|
1856
|
+
/* ONE control in two states (`control`): the
|
|
1857
|
+
* focus a rebuild gives back lands on the other
|
|
1858
|
+
* state's button. */
|
|
1637
1859
|
if(priv.is_fullscreen) {
|
|
1638
1860
|
items.push(
|
|
1639
|
-
{ id: 'g6-icon-fullscreen-exit', value: 'exit-fullscreen', className: 'EV_EXIT_FULLSCREEN', title: t('exit full screen') }
|
|
1861
|
+
{ id: 'g6-icon-fullscreen-exit', value: 'exit-fullscreen', control: 'fullscreen', className: 'EV_EXIT_FULLSCREEN', title: t('exit full screen'), title_key: 'exit full screen' }
|
|
1640
1862
|
);
|
|
1641
1863
|
} else {
|
|
1642
1864
|
items.push(
|
|
1643
|
-
{ id: 'g6-icon-fullscreen', value: 'request-fullscreen', className: 'EV_REQUEST_FULLSCREEN', title: t('enter full screen') }
|
|
1865
|
+
{ id: 'g6-icon-fullscreen', value: 'request-fullscreen', control: 'fullscreen', className: 'EV_REQUEST_FULLSCREEN', title: t('enter full screen'), title_key: 'enter full screen' }
|
|
1644
1866
|
);
|
|
1645
1867
|
}
|
|
1646
1868
|
}
|
|
@@ -1731,8 +1953,14 @@ function configure_toolbar_edit(gobj)
|
|
|
1731
1953
|
position: 'left-top',
|
|
1732
1954
|
style: toolbar_style(),
|
|
1733
1955
|
getItems: () => {
|
|
1956
|
+
/* Save, undo and redo bake their state for the same
|
|
1957
|
+
* reason as the selection mode below: told only to
|
|
1958
|
+
* the live element (update_history_buttons), they
|
|
1959
|
+
* came back disabled from every rebuild while the
|
|
1960
|
+
* history still had something to undo. */
|
|
1961
|
+
let lit = edit_buttons_lit(gobj);
|
|
1734
1962
|
return [
|
|
1735
|
-
{ id: 'g6-icon-plus', value: 'create-node', className: 'EV_CREATE_NODE_BTN color_create_state', title: t('create node') },
|
|
1963
|
+
{ id: 'g6-icon-plus', value: 'create-node', className: 'EV_CREATE_NODE_BTN color_create_state', title: t('create node'), title_key: 'create node' },
|
|
1736
1964
|
/* The mode is carried in the class getItems()
|
|
1737
1965
|
* returns, not only pushed onto the element after
|
|
1738
1966
|
* the fact: this toolbar is rebuilt whole (a fold,
|
|
@@ -1744,17 +1972,30 @@ function configure_toolbar_edit(gobj)
|
|
|
1744
1972
|
id: 'g6-icon-select-mode', value: 'selection-mode',
|
|
1745
1973
|
className: 'EV_TOGGLE_SELECTION_MODE' +
|
|
1746
1974
|
(priv.selection_mode? ' pressed_state' : ''),
|
|
1747
|
-
|
|
1975
|
+
pressed: !!priv.selection_mode,
|
|
1976
|
+
title: t('selection mode'), title_key: 'selection mode'
|
|
1977
|
+
},
|
|
1978
|
+
{
|
|
1979
|
+
id: 'g6-icon-save', value: 'save',
|
|
1980
|
+
className: 'EV_SAVE_GRAPH' + (lit.save? ' color_submit_state' : ''),
|
|
1981
|
+
title: t('save'), title_key: 'save', disabled: !lit.save
|
|
1982
|
+
},
|
|
1983
|
+
{
|
|
1984
|
+
id: 'g6-icon-undo', value: 'undo',
|
|
1985
|
+
className: 'EV_HISTORY_UNDO' + (lit.undo? ' color_active_state' : ''),
|
|
1986
|
+
title: t('undo'), title_key: 'undo', disabled: !lit.undo
|
|
1987
|
+
},
|
|
1988
|
+
{
|
|
1989
|
+
id: 'g6-icon-redo', value: 'redo',
|
|
1990
|
+
className: 'EV_HISTORY_REDO' + (lit.redo? ' color_active_state' : ''),
|
|
1991
|
+
title: t('redo'), title_key: 'redo', disabled: !lit.redo
|
|
1748
1992
|
},
|
|
1749
|
-
{ id: 'g6-icon-save', value: 'save', className: 'EV_SAVE_GRAPH', title: t('save'), disabled: true },
|
|
1750
|
-
{ id: 'g6-icon-undo', value: 'undo', className: 'EV_HISTORY_UNDO', title: t('undo'), disabled: true },
|
|
1751
|
-
{ id: 'g6-icon-redo', value: 'redo', className: 'EV_HISTORY_REDO', title: t('redo'), disabled: true },
|
|
1752
1993
|
];
|
|
1753
1994
|
},
|
|
1754
1995
|
onClick: (value) => {
|
|
1755
1996
|
switch(value) {
|
|
1756
1997
|
case 'create-node':
|
|
1757
|
-
|
|
1998
|
+
gobj_send_event(gobj, "EV_TOGGLE_CREATE_POPOVER", {}, gobj);
|
|
1758
1999
|
break;
|
|
1759
2000
|
case 'selection-mode':
|
|
1760
2001
|
gobj_send_event(gobj, "EV_TOGGLE_SELECTION_MODE", {}, gobj);
|
|
@@ -3421,8 +3662,7 @@ function update_history_buttons(gobj)
|
|
|
3421
3662
|
set_active_state($container, ".EV_HISTORY_UNDO", true);
|
|
3422
3663
|
|
|
3423
3664
|
// Pending changes exist, enable save
|
|
3424
|
-
|
|
3425
|
-
set_submit_state($container, ".EV_SAVE_GRAPH", true);
|
|
3665
|
+
light_save_button(gobj, true);
|
|
3426
3666
|
} else {
|
|
3427
3667
|
disableElements($container, ".EV_HISTORY_UNDO");
|
|
3428
3668
|
set_active_state($container, ".EV_HISTORY_UNDO", false);
|
|
@@ -3430,13 +3670,7 @@ function update_history_buttons(gobj)
|
|
|
3430
3670
|
/* No more undos: the graph is back to what was loaded
|
|
3431
3671
|
* or saved -- unless a write of it did not land. That
|
|
3432
3672
|
* one is still owed, and only a Save pays it. */
|
|
3433
|
-
|
|
3434
|
-
enableElements($container, ".EV_SAVE_GRAPH");
|
|
3435
|
-
set_submit_state($container, ".EV_SAVE_GRAPH", true);
|
|
3436
|
-
} else {
|
|
3437
|
-
disableElements($container, ".EV_SAVE_GRAPH");
|
|
3438
|
-
set_submit_state($container, ".EV_SAVE_GRAPH", false);
|
|
3439
|
-
}
|
|
3673
|
+
light_save_button(gobj, graphs_write_is_owed(gobj));
|
|
3440
3674
|
}
|
|
3441
3675
|
} else {
|
|
3442
3676
|
disableElements($container, ".EV_HISTORY_REDO");
|
|
@@ -3444,10 +3678,52 @@ function update_history_buttons(gobj)
|
|
|
3444
3678
|
|
|
3445
3679
|
disableElements($container, ".EV_HISTORY_UNDO");
|
|
3446
3680
|
set_active_state($container, ".EV_HISTORY_UNDO", false);
|
|
3681
|
+
|
|
3682
|
+
/* No history (a clear, a refresh): nothing to save but a
|
|
3683
|
+
* write still owed -- and `_save_lit` is what the next
|
|
3684
|
+
* rebuild of the toolbar draws. */
|
|
3685
|
+
light_save_button(gobj, graphs_write_is_owed(gobj));
|
|
3447
3686
|
}
|
|
3448
3687
|
}
|
|
3449
3688
|
}
|
|
3450
3689
|
|
|
3690
|
+
/************************************************************
|
|
3691
|
+
* What Save, Undo and Redo show when the edit toolbar is
|
|
3692
|
+
* built (its getItems): Undo and Redo what the history can do
|
|
3693
|
+
* now; Save what it was last told (`_save_lit`, see
|
|
3694
|
+
* light_save_button), because more than the history lights
|
|
3695
|
+
* it -- a change the history does not record
|
|
3696
|
+
* (mark_graph_dirty), a write owed -- and update_history_
|
|
3697
|
+
* buttons() is where those are weighed.
|
|
3698
|
+
************************************************************/
|
|
3699
|
+
function edit_buttons_lit(gobj)
|
|
3700
|
+
{
|
|
3701
|
+
let history = graph_get_plugin(gobj, "history");
|
|
3702
|
+
|
|
3703
|
+
return {
|
|
3704
|
+
undo: !!history && history.canUndo(),
|
|
3705
|
+
redo: !!history && history.canRedo(),
|
|
3706
|
+
save: !!gobj.priv._save_lit,
|
|
3707
|
+
};
|
|
3708
|
+
}
|
|
3709
|
+
|
|
3710
|
+
/************************************************************
|
|
3711
|
+
* Light the Save, or put it out: on the live element, and in
|
|
3712
|
+
* `_save_lit`, where the next rebuild of the toolbar reads it.
|
|
3713
|
+
************************************************************/
|
|
3714
|
+
function light_save_button(gobj, lit)
|
|
3715
|
+
{
|
|
3716
|
+
let $container = gobj_read_attr(gobj, "$container");
|
|
3717
|
+
|
|
3718
|
+
gobj.priv._save_lit = !!lit;
|
|
3719
|
+
if(lit) {
|
|
3720
|
+
enableElements($container, ".EV_SAVE_GRAPH");
|
|
3721
|
+
} else {
|
|
3722
|
+
disableElements($container, ".EV_SAVE_GRAPH");
|
|
3723
|
+
}
|
|
3724
|
+
set_submit_state($container, ".EV_SAVE_GRAPH", !!lit);
|
|
3725
|
+
}
|
|
3726
|
+
|
|
3451
3727
|
/************************************************************
|
|
3452
3728
|
* Graph utility functions
|
|
3453
3729
|
*
|
|
@@ -4707,22 +4983,63 @@ function floating_popover_dx()
|
|
|
4707
4983
|
return 4 + floating_icon_size() + 4;
|
|
4708
4984
|
}
|
|
4709
4985
|
|
|
4986
|
+
/************************************************************
|
|
4987
|
+
* A key press IS the click on an element drawn as a button.
|
|
4988
|
+
*
|
|
4989
|
+
* Enter or Space clicks it, so the keyboard reaches the very
|
|
4990
|
+
* handler the pointer does, whatever that handler is. Stopped
|
|
4991
|
+
* here, as the click is: this view's container forwards every
|
|
4992
|
+
* keydown to G6 (its shortcuts, and EV_KEY_DOWN), and a key
|
|
4993
|
+
* that pressed a button is not the graph's. Space would scroll
|
|
4994
|
+
* the page as well. Any other key goes on to the graph, so
|
|
4995
|
+
* Escape and Delete keep doing there what they do.
|
|
4996
|
+
************************************************************/
|
|
4997
|
+
function click_on_enter_or_space(el)
|
|
4998
|
+
{
|
|
4999
|
+
el.addEventListener('keydown', (e) => {
|
|
5000
|
+
if(e.key === 'Enter' || e.key === ' ') {
|
|
5001
|
+
e.preventDefault();
|
|
5002
|
+
e.stopPropagation();
|
|
5003
|
+
el.click();
|
|
5004
|
+
}
|
|
5005
|
+
});
|
|
5006
|
+
}
|
|
5007
|
+
|
|
4710
5008
|
/************************************************************
|
|
4711
5009
|
* Create a floating circular icon button.
|
|
4712
|
-
*
|
|
4713
|
-
*
|
|
4714
|
-
*
|
|
4715
|
-
*
|
|
4716
|
-
*
|
|
4717
|
-
*
|
|
5010
|
+
*
|
|
5011
|
+
* A button for the keyboard too: `role` and `tabindex`, and
|
|
5012
|
+
* Enter or Space click it (click_on_enter_or_space). Its name
|
|
5013
|
+
* carries its KEY, so refresh_language() renames it on a
|
|
5014
|
+
* language switch; a key handed in here goes in
|
|
5015
|
+
* G6_NODES_TREE_I18N_KEYS, because no scan sees it.
|
|
5016
|
+
*
|
|
5017
|
+
* Its click does one thing: it sends `event` to `gobj`, with
|
|
5018
|
+
* `kw` -- the plain identity of what the button belongs to --
|
|
5019
|
+
* and the ACTION does the work, where the `machine` trace sees
|
|
5020
|
+
* it. A pointer press does not take the focus
|
|
5021
|
+
* (keep_focus_on_press).
|
|
5022
|
+
*
|
|
5023
|
+
* @param {string} svgKey - key into SVG_ICONS
|
|
5024
|
+
* @param {string} color - border & text color (e.g. '#1890ff')
|
|
5025
|
+
* @param {number} left - CSS left in px
|
|
5026
|
+
* @param {number} top - CSS top in px
|
|
5027
|
+
* @param {string} title_key - i18n key of its title and aria-label
|
|
5028
|
+
* @param {object} gobj - who hears the click
|
|
5029
|
+
* @param {string} event - an input event of this gclass
|
|
5030
|
+
* @param {object} kw - plain JSON identity, copied per click
|
|
4718
5031
|
* @returns {HTMLElement}
|
|
4719
5032
|
************************************************************/
|
|
4720
|
-
function create_floating_icon(svgKey, color, left, top,
|
|
5033
|
+
function create_floating_icon(svgKey, color, left, top, title_key, gobj, event, kw)
|
|
4721
5034
|
{
|
|
4722
5035
|
const size = floating_icon_size();
|
|
4723
5036
|
const el = document.createElement('div');
|
|
4724
|
-
el.
|
|
4725
|
-
el.setAttribute('
|
|
5037
|
+
el.setAttribute('role', 'button');
|
|
5038
|
+
el.setAttribute('tabindex', '0');
|
|
5039
|
+
el.setAttribute('title', t(title_key));
|
|
5040
|
+
el.setAttribute('data-i18n-title', title_key);
|
|
5041
|
+
el.setAttribute('aria-label', t(title_key));
|
|
5042
|
+
el.setAttribute('data-i18n-aria-label', title_key);
|
|
4726
5043
|
el.innerHTML = SVG_ICONS[svgKey] || '';
|
|
4727
5044
|
el.style.cssText =
|
|
4728
5045
|
'position:absolute;' +
|
|
@@ -4749,13 +5066,25 @@ function create_floating_icon(svgKey, color, left, top, title, onClick)
|
|
|
4749
5066
|
|
|
4750
5067
|
el.addEventListener('click', (e) => {
|
|
4751
5068
|
e.stopPropagation();
|
|
4752
|
-
|
|
5069
|
+
gobj_send_event(gobj, event, json_deep_copy(kw || {}), gobj);
|
|
4753
5070
|
});
|
|
5071
|
+
keep_focus_on_press(el);
|
|
5072
|
+
click_on_enter_or_space(el);
|
|
4754
5073
|
return el;
|
|
4755
5074
|
}
|
|
4756
5075
|
|
|
4757
5076
|
/************************************************************
|
|
4758
5077
|
* Remove one or more overlay elements from priv and the DOM.
|
|
5078
|
+
*
|
|
5079
|
+
* An overlay that holds the focus -- a floating button reached
|
|
5080
|
+
* with Tab, the ✕ of the detail popover, a field of a popover --
|
|
5081
|
+
* gives it back to the canvas BEFORE it goes. Removing the
|
|
5082
|
+
* focused element is a focus loss the browsers do not report
|
|
5083
|
+
* alike: Chromium fires a focusout, which _on_focusout_restore
|
|
5084
|
+
* answers; Firefox fires none and leaves the focus on <body>,
|
|
5085
|
+
* and the graph is deaf to Escape, Delete and ctrl+A until the
|
|
5086
|
+
* next click on it.
|
|
5087
|
+
*
|
|
4759
5088
|
* @param {object} gobj
|
|
4760
5089
|
* @param {...string} keys - priv property names to remove
|
|
4761
5090
|
************************************************************/
|
|
@@ -4764,7 +5093,11 @@ function hide_overlay(gobj, ...keys)
|
|
|
4764
5093
|
let priv = gobj.priv;
|
|
4765
5094
|
for(let key of keys) {
|
|
4766
5095
|
if(priv[key]) {
|
|
4767
|
-
priv[key]
|
|
5096
|
+
let el = priv[key];
|
|
5097
|
+
if(el.contains(document.activeElement) && !gobj_is_destroying(gobj)) {
|
|
5098
|
+
focus_main_canvas(priv.$container);
|
|
5099
|
+
}
|
|
5100
|
+
el.remove();
|
|
4768
5101
|
priv[key] = null;
|
|
4769
5102
|
}
|
|
4770
5103
|
}
|
|
@@ -4822,9 +5155,7 @@ function graphs_write_is_owed(gobj)
|
|
|
4822
5155
|
|
|
4823
5156
|
function mark_graph_dirty(gobj)
|
|
4824
5157
|
{
|
|
4825
|
-
|
|
4826
|
-
enableElements($container, ".EV_SAVE_GRAPH");
|
|
4827
|
-
set_submit_state($container, ".EV_SAVE_GRAPH", true);
|
|
5158
|
+
light_save_button(gobj, true);
|
|
4828
5159
|
}
|
|
4829
5160
|
|
|
4830
5161
|
/************************************************************
|
|
@@ -4841,19 +5172,21 @@ function extract_size(style)
|
|
|
4841
5172
|
|
|
4842
5173
|
/************************************************************
|
|
4843
5174
|
* Show a pair of floating icons (gear + trash) next to an
|
|
4844
|
-
* element.
|
|
5175
|
+
* element. The titles are i18n KEYS (see create_floating_icon);
|
|
5176
|
+
* each click sends its event to `gobj` with the same identity
|
|
5177
|
+
* `kw`. Returns {icon_el, delete_el}.
|
|
4845
5178
|
************************************************************/
|
|
4846
|
-
function show_dual_icons(gobj, x, y,
|
|
5179
|
+
function show_dual_icons(gobj, x, y, gear_key, gear_event, trash_key, trash_event, kw)
|
|
4847
5180
|
{
|
|
4848
5181
|
let priv = gobj.priv;
|
|
4849
5182
|
|
|
4850
5183
|
const icon_el = create_floating_icon(
|
|
4851
|
-
'gear', '#1890ff', x, y,
|
|
5184
|
+
'gear', '#1890ff', x, y, gear_key, gobj, gear_event, kw
|
|
4852
5185
|
);
|
|
4853
5186
|
priv.$container.appendChild(icon_el);
|
|
4854
5187
|
|
|
4855
5188
|
const delete_el = create_floating_icon(
|
|
4856
|
-
'trash', '#ff4d4f', x, y + floating_icon_step(),
|
|
5189
|
+
'trash', '#ff4d4f', x, y + floating_icon_step(), trash_key, gobj, trash_event, kw
|
|
4857
5190
|
);
|
|
4858
5191
|
priv.$container.appendChild(delete_el);
|
|
4859
5192
|
|
|
@@ -4931,15 +5264,43 @@ function touch_control_css()
|
|
|
4931
5264
|
* one id.
|
|
4932
5265
|
*/
|
|
4933
5266
|
|
|
5267
|
+
/*
|
|
5268
|
+
* A form of the property popovers is a LABEL followed by its control. The
|
|
5269
|
+
* label carries its key (`text` is one) and the control built next is
|
|
5270
|
+
* NAMED by it: a `<label>` beside a control names nothing, so the control
|
|
5271
|
+
* gets the label's `for`, and its own `title` and `aria-label`, with their
|
|
5272
|
+
* keys (REV-CLOUD V19.4.1: 11 controls without a name).
|
|
5273
|
+
*/
|
|
5274
|
+
let form_control_seq = 0;
|
|
5275
|
+
|
|
4934
5276
|
function create_form_label(parent, text)
|
|
4935
5277
|
{
|
|
4936
5278
|
let label = document.createElement('label');
|
|
4937
5279
|
label.textContent = t(text);
|
|
5280
|
+
label.setAttribute('data-i18n', text);
|
|
4938
5281
|
label.style.cssText = 'display:block;margin-bottom:4px;font-weight:500;';
|
|
4939
5282
|
parent.appendChild(label);
|
|
4940
5283
|
return label;
|
|
4941
5284
|
}
|
|
4942
5285
|
|
|
5286
|
+
function name_form_control(parent, control)
|
|
5287
|
+
{
|
|
5288
|
+
let label = parent.lastElementChild;
|
|
5289
|
+
if(!label || label.tagName !== 'LABEL') {
|
|
5290
|
+
return;
|
|
5291
|
+
}
|
|
5292
|
+
let key = label.getAttribute('data-i18n');
|
|
5293
|
+
if(!key) {
|
|
5294
|
+
return;
|
|
5295
|
+
}
|
|
5296
|
+
control.id = 'g6-form-control-' + (++form_control_seq);
|
|
5297
|
+
label.htmlFor = control.id;
|
|
5298
|
+
control.setAttribute('title', t(key));
|
|
5299
|
+
control.setAttribute('data-i18n-title', key);
|
|
5300
|
+
control.setAttribute('aria-label', t(key));
|
|
5301
|
+
control.setAttribute('data-i18n-aria-label', key);
|
|
5302
|
+
}
|
|
5303
|
+
|
|
4943
5304
|
function create_form_color_input(parent, value, onInput)
|
|
4944
5305
|
{
|
|
4945
5306
|
let input = document.createElement('input');
|
|
@@ -4952,6 +5313,7 @@ function create_form_color_input(parent, value, onInput)
|
|
|
4952
5313
|
if(onInput) {
|
|
4953
5314
|
input.addEventListener('input', onInput);
|
|
4954
5315
|
}
|
|
5316
|
+
name_form_control(parent, input);
|
|
4955
5317
|
parent.appendChild(input);
|
|
4956
5318
|
return input;
|
|
4957
5319
|
}
|
|
@@ -4976,6 +5338,7 @@ function create_form_number_input(parent, value, min, max, onInput)
|
|
|
4976
5338
|
if(onInput) {
|
|
4977
5339
|
input.addEventListener('input', onInput);
|
|
4978
5340
|
}
|
|
5341
|
+
name_form_control(parent, input);
|
|
4979
5342
|
parent.appendChild(input);
|
|
4980
5343
|
return input;
|
|
4981
5344
|
}
|
|
@@ -4991,11 +5354,14 @@ function create_form_select(parent, options, marginBottom)
|
|
|
4991
5354
|
'box-sizing:border-box;margin-bottom:' + (marginBottom || '12px') + ';' +
|
|
4992
5355
|
touch_control_css();
|
|
4993
5356
|
for(let opt of options) {
|
|
5357
|
+
/* `i18n` is the option's key: it carries it, and `value` stays explicit */
|
|
4994
5358
|
let o = document.createElement('option');
|
|
4995
5359
|
o.value = opt.value;
|
|
4996
|
-
o.textContent = opt.
|
|
5360
|
+
o.textContent = t(opt.i18n);
|
|
5361
|
+
o.setAttribute('data-i18n', opt.i18n);
|
|
4997
5362
|
select.appendChild(o);
|
|
4998
5363
|
}
|
|
5364
|
+
name_form_control(parent, select);
|
|
4999
5365
|
parent.appendChild(select);
|
|
5000
5366
|
return select;
|
|
5001
5367
|
}
|
|
@@ -5006,11 +5372,23 @@ const BTN_STYLE_CANCEL =
|
|
|
5006
5372
|
'border:1px solid var(--bulma-border-weak, #d9d9d9);' +
|
|
5007
5373
|
'border-radius:4px;cursor:pointer;font-size:13px;font-weight:500;';
|
|
5008
5374
|
|
|
5009
|
-
|
|
5375
|
+
/************************************************************
|
|
5376
|
+
* The buttons of a popover: each one SENDS an input event of
|
|
5377
|
+
* this gclass to `gobj`, and the action does the work, where
|
|
5378
|
+
* the `machine` trace sees it (V20.5 #4). A button is
|
|
5379
|
+
* {i18n, style, event, kw, form}
|
|
5380
|
+
* `kw` is the plain identity of what the popover is about
|
|
5381
|
+
* (copied per click); `form` names the controls whose `value`
|
|
5382
|
+
* goes in the kw too, as the control says it -- a string, the
|
|
5383
|
+
* action parses it. The kw says what was asked for, in the
|
|
5384
|
+
* trace: the identity and what the form held when it was
|
|
5385
|
+
* pressed.
|
|
5386
|
+
************************************************************/
|
|
5387
|
+
function create_form_button_row(parent, buttons, gobj)
|
|
5010
5388
|
{
|
|
5011
5389
|
let btnRow = document.createElement('div');
|
|
5012
5390
|
btnRow.style.cssText = 'display:flex;gap:8px;';
|
|
5013
|
-
for(let {i18n, style,
|
|
5391
|
+
for(let {i18n, style, event, kw, form} of buttons) {
|
|
5014
5392
|
/* `i18n` is the key (named so that validate-locales sees a literal
|
|
5015
5393
|
* one): the button carries it, and its name */
|
|
5016
5394
|
let btn = document.createElement('button');
|
|
@@ -5023,7 +5401,7 @@ function create_form_button_row(parent, buttons)
|
|
|
5023
5401
|
btn.style.cssText = style + touch_control_css();
|
|
5024
5402
|
btn.addEventListener('click', (e) => {
|
|
5025
5403
|
e.stopPropagation();
|
|
5026
|
-
|
|
5404
|
+
gobj_send_event(gobj, event, form_kw(kw, form), gobj);
|
|
5027
5405
|
});
|
|
5028
5406
|
btnRow.appendChild(btn);
|
|
5029
5407
|
}
|
|
@@ -5031,11 +5409,27 @@ function create_form_button_row(parent, buttons)
|
|
|
5031
5409
|
return btnRow;
|
|
5032
5410
|
}
|
|
5033
5411
|
|
|
5412
|
+
/* The kw a popover button sends: its identity, and the value of
|
|
5413
|
+
* each control of its form. */
|
|
5414
|
+
function form_kw(kw, form)
|
|
5415
|
+
{
|
|
5416
|
+
let out = json_deep_copy(kw || {});
|
|
5417
|
+
for(let [name, control] of Object.entries(form || {})) {
|
|
5418
|
+
out[name] = String(control.value);
|
|
5419
|
+
}
|
|
5420
|
+
return out;
|
|
5421
|
+
}
|
|
5422
|
+
|
|
5034
5423
|
/************************************************************
|
|
5035
5424
|
* Show a confirmation popover next to a target element.
|
|
5036
|
-
*
|
|
5425
|
+
*
|
|
5426
|
+
* `question` is the plain identity of what is asked, {what,
|
|
5427
|
+
* id|ids}: the confirm button sends it with EV_CONFIRMED and the
|
|
5428
|
+
* cancel button with EV_CANCEL_CONFIRM, and the actions answer.
|
|
5429
|
+
* `priv_key` is where the popover is kept, and it must be the
|
|
5430
|
+
* one CONFIRM_POPOVER_OF names for `question.what`.
|
|
5037
5431
|
************************************************************/
|
|
5038
|
-
function show_confirm_popover(gobj, target_el, message, confirm_text, confirm_color,
|
|
5432
|
+
function show_confirm_popover(gobj, target_el, message, confirm_text, confirm_color, question, priv_key)
|
|
5039
5433
|
{
|
|
5040
5434
|
hide_overlay(gobj, priv_key);
|
|
5041
5435
|
|
|
@@ -5068,18 +5462,21 @@ function show_confirm_popover(gobj, target_el, message, confirm_text, confirm_co
|
|
|
5068
5462
|
{
|
|
5069
5463
|
i18n: 'cancel',
|
|
5070
5464
|
style: BTN_STYLE_CANCEL,
|
|
5071
|
-
|
|
5465
|
+
event: "EV_CANCEL_CONFIRM",
|
|
5466
|
+
kw: question,
|
|
5072
5467
|
},
|
|
5073
5468
|
{
|
|
5074
5469
|
i18n: confirm_text,
|
|
5075
5470
|
style: 'flex:1;padding:6px;background:' + confirm_color + ';color:#fff;border:none;' +
|
|
5076
5471
|
'border-radius:4px;cursor:pointer;font-size:13px;font-weight:500;',
|
|
5077
|
-
|
|
5472
|
+
event: "EV_CONFIRMED",
|
|
5473
|
+
kw: question,
|
|
5078
5474
|
},
|
|
5079
|
-
]);
|
|
5475
|
+
], gobj);
|
|
5080
5476
|
|
|
5081
5477
|
priv.$container.appendChild(popover);
|
|
5082
5478
|
priv[priv_key] = popover;
|
|
5479
|
+
priv._confirm_questions[priv_key] = json_deep_copy(question);
|
|
5083
5480
|
clamp_popover_position(gobj, popover);
|
|
5084
5481
|
}
|
|
5085
5482
|
|
|
@@ -5191,13 +5588,17 @@ function paint_selection(gobj, ids)
|
|
|
5191
5588
|
* Zoom-to-selection can only act on a selection.
|
|
5192
5589
|
*
|
|
5193
5590
|
* The attribute is toggled on the element instead of re-rendering
|
|
5194
|
-
* the toolbar, which would rebuild its innerHTML
|
|
5195
|
-
*
|
|
5196
|
-
*
|
|
5591
|
+
* the toolbar, which would rebuild its whole innerHTML each time a
|
|
5592
|
+
* card is ticked. A rebuild asks the same question
|
|
5593
|
+
* (selection_is_painted) in the toolbar's getItems().
|
|
5197
5594
|
************************************************************/
|
|
5595
|
+
function selection_is_painted(gobj)
|
|
5596
|
+
{
|
|
5597
|
+
return (gobj.priv._selected_paint_ids || []).length > 0;
|
|
5598
|
+
}
|
|
5599
|
+
|
|
5198
5600
|
function update_zoom_selection_button(gobj)
|
|
5199
5601
|
{
|
|
5200
|
-
let priv = gobj.priv;
|
|
5201
5602
|
let $container = gobj_read_attr(gobj, "$container");
|
|
5202
5603
|
|
|
5203
5604
|
if(!$container) {
|
|
@@ -5209,7 +5610,7 @@ function update_zoom_selection_button(gobj)
|
|
|
5209
5610
|
return; /* no toolbar, or not in edition: nothing to enable */
|
|
5210
5611
|
}
|
|
5211
5612
|
|
|
5212
|
-
if((
|
|
5613
|
+
if(selection_is_painted(gobj)) {
|
|
5213
5614
|
enableElements($container, ".EV_ZOOM_SELECTION");
|
|
5214
5615
|
} else {
|
|
5215
5616
|
disableElements($container, ".EV_ZOOM_SELECTION");
|
|
@@ -5219,12 +5620,13 @@ function update_zoom_selection_button(gobj)
|
|
|
5219
5620
|
/************************************************************
|
|
5220
5621
|
* The selection mode is a state, so it has to LOOK like one:
|
|
5221
5622
|
* a PRESSED toggle, not a button wearing one of the palette's
|
|
5222
|
-
* action colours (see `pressed_state` in lib_graph.css).
|
|
5623
|
+
* action colours (see `pressed_state` in lib_graph.css). And
|
|
5624
|
+
* it SAYS it, in `aria-pressed`, for whoever does not see it.
|
|
5223
5625
|
*
|
|
5224
5626
|
* Toggled on the element rather than re-rendered with the
|
|
5225
5627
|
* toolbar, for the same reason as the button above: a
|
|
5226
|
-
* re-render rebuilds the innerHTML
|
|
5227
|
-
*
|
|
5628
|
+
* re-render rebuilds the whole innerHTML. A rebuild draws the
|
|
5629
|
+
* same state, baked in the edit toolbar's getItems().
|
|
5228
5630
|
************************************************************/
|
|
5229
5631
|
function update_selection_mode_button(gobj)
|
|
5230
5632
|
{
|
|
@@ -5240,6 +5642,9 @@ function update_selection_mode_button(gobj)
|
|
|
5240
5642
|
}
|
|
5241
5643
|
|
|
5242
5644
|
set_pressed_state($container, ".EV_TOGGLE_SELECTION_MODE", priv.selection_mode);
|
|
5645
|
+
$container.querySelectorAll(".EV_TOGGLE_SELECTION_MODE").forEach(($el) => {
|
|
5646
|
+
$el.setAttribute("aria-pressed", priv.selection_mode? "true" : "false");
|
|
5647
|
+
});
|
|
5243
5648
|
}
|
|
5244
5649
|
|
|
5245
5650
|
/************************************************************
|
|
@@ -5934,21 +6339,22 @@ function show_link_icon_if_fkey(gobj)
|
|
|
5934
6339
|
}
|
|
5935
6340
|
let vp = graph.getViewportByCanvas([canvasPos.x, canvasPos.y]);
|
|
5936
6341
|
|
|
6342
|
+
/* A click (no drag) enters the linking mode; a press that drags
|
|
6343
|
+
* enters it on its own (start_link_drag, below). */
|
|
6344
|
+
let identity = {node_id: priv._selected_node_id, port_key: priv._selected_port_key};
|
|
5937
6345
|
let icon = create_floating_icon(
|
|
5938
6346
|
'link', '#52c41a',
|
|
5939
6347
|
vp[0] + floating_icon_size() / 2 + 4, vp[1] + floating_icon_dy(),
|
|
5940
|
-
|
|
5941
|
-
// Click (no drag) toggles linking mode
|
|
5942
|
-
if(!priv._linking_mode) {
|
|
5943
|
-
enter_linking_mode(gobj);
|
|
5944
|
-
}
|
|
5945
|
-
}
|
|
6348
|
+
'link to hook', gobj, "EV_ENTER_LINKING_MODE", identity
|
|
5946
6349
|
);
|
|
5947
|
-
|
|
6350
|
+
/* The press may be a drag: EV_START_LINK_DRAG, with the port's
|
|
6351
|
+
* identity and where the pointer went down. */
|
|
5948
6352
|
icon.addEventListener('pointerdown', (e) => {
|
|
5949
6353
|
e.stopPropagation();
|
|
5950
6354
|
e.preventDefault();
|
|
5951
|
-
|
|
6355
|
+
gobj_send_event(gobj, "EV_START_LINK_DRAG", Object.assign(
|
|
6356
|
+
json_deep_copy(identity), {client_x: e.clientX, client_y: e.clientY}
|
|
6357
|
+
), gobj);
|
|
5952
6358
|
});
|
|
5953
6359
|
priv.$container.appendChild(icon);
|
|
5954
6360
|
priv._link_icon_el = icon;
|
|
@@ -6024,7 +6430,8 @@ function show_port_icon(gobj)
|
|
|
6024
6430
|
}
|
|
6025
6431
|
let icon = create_floating_icon(
|
|
6026
6432
|
'gear', '#1890ff', xy.x, xy.y,
|
|
6027
|
-
|
|
6433
|
+
'port properties', gobj, "EV_TOGGLE_PORT_POPOVER",
|
|
6434
|
+
{node_id: priv._selected_node_id, port_key: priv._selected_port_key}
|
|
6028
6435
|
);
|
|
6029
6436
|
priv.$container.appendChild(icon);
|
|
6030
6437
|
priv._port_icon_el = icon;
|
|
@@ -6138,7 +6545,7 @@ function show_port_popover(gobj)
|
|
|
6138
6545
|
|
|
6139
6546
|
create_form_label(popover, 'shape');
|
|
6140
6547
|
let shapeSelect = create_form_select(popover, PORT_SHAPES.map((shape) => {
|
|
6141
|
-
return {value: shape,
|
|
6548
|
+
return {value: shape, i18n: shape};
|
|
6142
6549
|
}));
|
|
6143
6550
|
shapeSelect.value = orig_shape;
|
|
6144
6551
|
shapeSelect.addEventListener('change', preview_port);
|
|
@@ -6148,31 +6555,29 @@ function show_port_popover(gobj)
|
|
|
6148
6555
|
|
|
6149
6556
|
create_form_label(popover, 'apply to');
|
|
6150
6557
|
let scopeSelect = create_form_select(popover, [
|
|
6151
|
-
{value: 'this',
|
|
6152
|
-
{value: 'same_topic',
|
|
6153
|
-
{value: 'all',
|
|
6558
|
+
{value: 'this', i18n: 'this port'},
|
|
6559
|
+
{value: 'same_topic', i18n: 'same port in topic'},
|
|
6560
|
+
{value: 'all', i18n: 'all ports'},
|
|
6154
6561
|
]);
|
|
6155
6562
|
|
|
6156
6563
|
create_form_button_row(popover, [
|
|
6157
6564
|
{
|
|
6158
6565
|
i18n: 'cancel',
|
|
6159
6566
|
style: BTN_STYLE_CANCEL,
|
|
6160
|
-
|
|
6161
|
-
|
|
6162
|
-
},
|
|
6567
|
+
event: "EV_CANCEL_PORT_POPOVER",
|
|
6568
|
+
kw: {node_id: node_id, port_key: port_key},
|
|
6163
6569
|
},
|
|
6164
6570
|
{
|
|
6165
6571
|
i18n: 'apply',
|
|
6166
6572
|
style: 'flex:1;padding:6px;background:#1890ff;color:#fff;border:none;' +
|
|
6167
6573
|
'border-radius:4px;cursor:pointer;font-size:13px;font-weight:500;',
|
|
6168
|
-
|
|
6169
|
-
|
|
6170
|
-
|
|
6171
|
-
|
|
6172
|
-
|
|
6173
|
-
},
|
|
6574
|
+
event: "EV_APPLY_PORT_POPOVER",
|
|
6575
|
+
/* `r_before`: what an unreadable radius falls back to; the
|
|
6576
|
+
* preview has already written the field's on the port. */
|
|
6577
|
+
kw: {node_id: node_id, port_key: port_key, r_before: orig_r},
|
|
6578
|
+
form: {shape: shapeSelect, r: rInput, scope: scopeSelect},
|
|
6174
6579
|
},
|
|
6175
|
-
]);
|
|
6580
|
+
], gobj);
|
|
6176
6581
|
|
|
6177
6582
|
priv.$container.appendChild(popover);
|
|
6178
6583
|
priv._port_popover_el = popover;
|
|
@@ -6483,8 +6888,16 @@ function try_complete_link(gobj, clicked_node_id, clicked_port_key)
|
|
|
6483
6888
|
/************************************************************
|
|
6484
6889
|
* Drag-link: drag from link icon to a hook port.
|
|
6485
6890
|
* Draws a temporary SVG line from the fkey port to the cursor.
|
|
6891
|
+
*
|
|
6892
|
+
* Run by ac_start_link_drag. The pointer's MOVES only draw the
|
|
6893
|
+
* line and the cursor -- the drag's own picture, no action, and
|
|
6894
|
+
* one event per move would bury the machine trace -- while the
|
|
6895
|
+
* END of the gesture is an event: EV_LINK_DRAG_END {client_x,
|
|
6896
|
+
* client_y, dragged} on the release, EV_LINK_DRAG_CANCEL when
|
|
6897
|
+
* the browser takes the pointer. Their actions take the drag
|
|
6898
|
+
* down (end_link_drag) and link, or leave the linking mode.
|
|
6486
6899
|
************************************************************/
|
|
6487
|
-
function start_link_drag(gobj,
|
|
6900
|
+
function start_link_drag(gobj, client_x, client_y)
|
|
6488
6901
|
{
|
|
6489
6902
|
let priv = gobj.priv;
|
|
6490
6903
|
let graph = priv.graph;
|
|
@@ -6511,8 +6924,8 @@ function start_link_drag(gobj, e)
|
|
|
6511
6924
|
let line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
6512
6925
|
line.setAttribute('x1', startX);
|
|
6513
6926
|
line.setAttribute('y1', startY);
|
|
6514
|
-
line.setAttribute('x2',
|
|
6515
|
-
line.setAttribute('y2',
|
|
6927
|
+
line.setAttribute('x2', client_x - containerRect.left);
|
|
6928
|
+
line.setAttribute('y2', client_y - containerRect.top);
|
|
6516
6929
|
line.setAttribute('stroke', '#52c41a');
|
|
6517
6930
|
line.setAttribute('stroke-width', '2');
|
|
6518
6931
|
line.setAttribute('stroke-dasharray', '6 3');
|
|
@@ -6536,60 +6949,61 @@ function start_link_drag(gobj, e)
|
|
|
6536
6949
|
}
|
|
6537
6950
|
|
|
6538
6951
|
function onPointerUp(ev) {
|
|
6539
|
-
|
|
6540
|
-
|
|
6541
|
-
|
|
6542
|
-
|
|
6543
|
-
|
|
6544
|
-
// Remove drag line
|
|
6545
|
-
if(priv._link_drag_svg) {
|
|
6546
|
-
priv._link_drag_svg.remove();
|
|
6547
|
-
priv._link_drag_svg = null;
|
|
6548
|
-
}
|
|
6549
|
-
|
|
6550
|
-
if(!dragged) {
|
|
6551
|
-
// It was a click, not a drag — let the click handler deal with it
|
|
6552
|
-
return;
|
|
6553
|
-
}
|
|
6554
|
-
|
|
6555
|
-
// Check if dropped on a valid hook port
|
|
6556
|
-
let cx = ev.clientX - containerRect.left;
|
|
6557
|
-
let cy = ev.clientY - containerRect.top;
|
|
6558
|
-
let canvasPt = graph.getCanvasByViewport([cx, cy]);
|
|
6559
|
-
let target = find_hook_at_point(gobj, canvasPt[0], canvasPt[1]);
|
|
6560
|
-
|
|
6561
|
-
if(target) {
|
|
6562
|
-
try_complete_link(gobj, target.node_id, target.port_key);
|
|
6563
|
-
} else {
|
|
6564
|
-
exit_linking_mode(gobj);
|
|
6565
|
-
}
|
|
6952
|
+
gobj_send_event(gobj, "EV_LINK_DRAG_END", {
|
|
6953
|
+
client_x: ev.clientX,
|
|
6954
|
+
client_y: ev.clientY,
|
|
6955
|
+
dragged: dragged,
|
|
6956
|
+
}, gobj);
|
|
6566
6957
|
}
|
|
6567
6958
|
|
|
6568
|
-
/* A cancelled pointer (the browser took the touch) is no drop
|
|
6569
|
-
* listeners go, and what was there stays. Without it they stayed
|
|
6570
|
-
* on `document` until some later pointerup, which then dropped. */
|
|
6959
|
+
/* A cancelled pointer (the browser took the touch) is no drop. */
|
|
6571
6960
|
function onPointerCancel(ev) {
|
|
6961
|
+
gobj_send_event(gobj, "EV_LINK_DRAG_CANCEL", {}, gobj);
|
|
6962
|
+
}
|
|
6963
|
+
|
|
6964
|
+
function unwire() {
|
|
6572
6965
|
document.removeEventListener('pointermove', onPointerMove);
|
|
6573
6966
|
document.removeEventListener('pointerup', onPointerUp);
|
|
6574
6967
|
document.removeEventListener('pointercancel', onPointerCancel);
|
|
6575
|
-
priv.$container.style.cursor = '';
|
|
6576
|
-
if(priv._link_drag_svg) {
|
|
6577
|
-
priv._link_drag_svg.remove();
|
|
6578
|
-
priv._link_drag_svg = null;
|
|
6579
|
-
}
|
|
6580
|
-
exit_linking_mode(gobj);
|
|
6581
6968
|
}
|
|
6582
6969
|
|
|
6583
6970
|
document.addEventListener('pointermove', onPointerMove);
|
|
6584
6971
|
document.addEventListener('pointerup', onPointerUp);
|
|
6585
6972
|
document.addEventListener('pointercancel', onPointerCancel);
|
|
6586
|
-
|
|
6587
|
-
|
|
6588
|
-
|
|
6589
|
-
|
|
6590
|
-
|
|
6591
|
-
|
|
6973
|
+
|
|
6974
|
+
/* Where the line is measured from, for the drop, and how to take
|
|
6975
|
+
* the listeners off. A view destroyed in the middle of the drag
|
|
6976
|
+
* takes them off too (mt_destroy, through `_drag_unwire`): they
|
|
6977
|
+
* are closures over this gobj, on `document`. */
|
|
6978
|
+
priv._link_drag = {
|
|
6979
|
+
left: containerRect.left,
|
|
6980
|
+
top: containerRect.top,
|
|
6981
|
+
unwire: unwire,
|
|
6592
6982
|
};
|
|
6983
|
+
priv._drag_unwire = unwire;
|
|
6984
|
+
}
|
|
6985
|
+
|
|
6986
|
+
/************************************************************
|
|
6987
|
+
* Take a link drag down: its listeners, its line, its cursor.
|
|
6988
|
+
* What was linked, or not, is the caller's.
|
|
6989
|
+
************************************************************/
|
|
6990
|
+
function end_link_drag(gobj)
|
|
6991
|
+
{
|
|
6992
|
+
let priv = gobj.priv;
|
|
6993
|
+
let drag = priv._link_drag;
|
|
6994
|
+
|
|
6995
|
+
priv._link_drag = null;
|
|
6996
|
+
if(drag) {
|
|
6997
|
+
drag.unwire();
|
|
6998
|
+
if(priv._drag_unwire === drag.unwire) {
|
|
6999
|
+
priv._drag_unwire = null;
|
|
7000
|
+
}
|
|
7001
|
+
}
|
|
7002
|
+
priv.$container.style.cursor = '';
|
|
7003
|
+
if(priv._link_drag_svg) {
|
|
7004
|
+
priv._link_drag_svg.remove();
|
|
7005
|
+
priv._link_drag_svg = null;
|
|
7006
|
+
}
|
|
6593
7007
|
}
|
|
6594
7008
|
|
|
6595
7009
|
/************************************************************
|
|
@@ -6969,9 +7383,9 @@ function show_edge_icon(gobj)
|
|
|
6969
7383
|
}
|
|
6970
7384
|
|
|
6971
7385
|
let icons = show_dual_icons(gobj, mid.x + 4, mid.y + floating_icon_dy(),
|
|
6972
|
-
|
|
6973
|
-
|
|
6974
|
-
|
|
7386
|
+
'edge properties', "EV_TOGGLE_EDGE_POPOVER",
|
|
7387
|
+
'unlink', "EV_REQUEST_UNLINK_EDGE",
|
|
7388
|
+
{edge_id: priv._selected_edge_id}
|
|
6975
7389
|
);
|
|
6976
7390
|
priv._edge_icon_el = icons.icon_el;
|
|
6977
7391
|
priv._edge_delete_el = icons.delete_el;
|
|
@@ -7100,33 +7514,27 @@ function show_edge_popover(gobj)
|
|
|
7100
7514
|
// Apply-to scope
|
|
7101
7515
|
create_form_label(popover, 'apply to');
|
|
7102
7516
|
let scopeSelect = create_form_select(popover, [
|
|
7103
|
-
{ value: 'this',
|
|
7104
|
-
{ value: 'same_type',
|
|
7105
|
-
{ value: 'all',
|
|
7517
|
+
{ value: 'this', i18n: 'this edge' },
|
|
7518
|
+
{ value: 'same_type', i18n: 'same type edges' },
|
|
7519
|
+
{ value: 'all', i18n: 'all edges' },
|
|
7106
7520
|
]);
|
|
7107
7521
|
|
|
7108
7522
|
create_form_button_row(popover, [
|
|
7109
7523
|
{
|
|
7110
7524
|
i18n: 'cancel',
|
|
7111
7525
|
style: BTN_STYLE_CANCEL,
|
|
7112
|
-
|
|
7113
|
-
|
|
7114
|
-
},
|
|
7526
|
+
event: "EV_CANCEL_EDGE_POPOVER",
|
|
7527
|
+
kw: {edge_id: edge_id},
|
|
7115
7528
|
},
|
|
7116
7529
|
{
|
|
7117
7530
|
i18n: 'apply',
|
|
7118
7531
|
style: 'flex:1;padding:6px;background:#52c41a;color:#fff;border:none;' +
|
|
7119
7532
|
'border-radius:4px;cursor:pointer;font-size:13px;font-weight:500;',
|
|
7120
|
-
|
|
7121
|
-
|
|
7122
|
-
|
|
7123
|
-
parseInt(lwInput.value) || 2,
|
|
7124
|
-
colorInput.value,
|
|
7125
|
-
scopeSelect.value
|
|
7126
|
-
);
|
|
7127
|
-
},
|
|
7533
|
+
event: "EV_APPLY_EDGE_POPOVER",
|
|
7534
|
+
kw: {edge_id: edge_id},
|
|
7535
|
+
form: {line_width: lwInput, color: colorInput, scope: scopeSelect},
|
|
7128
7536
|
},
|
|
7129
|
-
]);
|
|
7537
|
+
], gobj);
|
|
7130
7538
|
|
|
7131
7539
|
priv.$container.appendChild(popover);
|
|
7132
7540
|
priv._edge_popover_el = popover;
|
|
@@ -7197,9 +7605,9 @@ function show_node_icon(gobj)
|
|
|
7197
7605
|
}
|
|
7198
7606
|
|
|
7199
7607
|
let icons = show_dual_icons(gobj, rect.right + 4, rect.top + floating_icon_dy(),
|
|
7200
|
-
|
|
7201
|
-
|
|
7202
|
-
|
|
7608
|
+
'node properties', "EV_TOGGLE_NODE_POPOVER",
|
|
7609
|
+
'delete node', "EV_REQUEST_DELETE_NODE",
|
|
7610
|
+
{node_id: priv._selected_node_id}
|
|
7203
7611
|
);
|
|
7204
7612
|
priv._node_icon_el = icons.icon_el;
|
|
7205
7613
|
priv._node_delete_el = icons.delete_el;
|
|
@@ -7339,7 +7747,7 @@ function show_node_popover(gobj)
|
|
|
7339
7747
|
* reshape either way. */
|
|
7340
7748
|
create_form_label(popover, 'shape');
|
|
7341
7749
|
let shapeSelect = create_form_select(popover, NODE_SHAPES.map((shape) => {
|
|
7342
|
-
return {value: shape,
|
|
7750
|
+
return {value: shape, i18n: shape};
|
|
7343
7751
|
}));
|
|
7344
7752
|
shapeSelect.value = nodeData.data && nodeData.data.desc
|
|
7345
7753
|
? node_figure_of(gobj, nodeData.data.desc, nodeData.data.graph_props || {})
|
|
@@ -7349,38 +7757,32 @@ function show_node_popover(gobj)
|
|
|
7349
7757
|
// Apply-to scope
|
|
7350
7758
|
create_form_label(popover, 'apply to');
|
|
7351
7759
|
let scopeSelect = create_form_select(popover, [
|
|
7352
|
-
{ value: 'this',
|
|
7353
|
-
{ value: 'same_topic',
|
|
7354
|
-
{ value: 'all',
|
|
7760
|
+
{ value: 'this', i18n: 'this node' },
|
|
7761
|
+
{ value: 'same_topic', i18n: 'same topic nodes' },
|
|
7762
|
+
{ value: 'all', i18n: 'all nodes' },
|
|
7355
7763
|
]);
|
|
7356
7764
|
|
|
7357
7765
|
create_form_button_row(popover, [
|
|
7358
7766
|
{
|
|
7359
7767
|
i18n: 'cancel',
|
|
7360
7768
|
style: BTN_STYLE_CANCEL,
|
|
7361
|
-
|
|
7362
|
-
|
|
7363
|
-
},
|
|
7769
|
+
event: "EV_CANCEL_NODE_POPOVER",
|
|
7770
|
+
kw: {node_id: node_id},
|
|
7364
7771
|
},
|
|
7365
7772
|
{
|
|
7366
7773
|
i18n: 'apply',
|
|
7367
7774
|
style: 'flex:1;padding:6px;background:#1890ff;color:#fff;border:none;' +
|
|
7368
7775
|
'border-radius:4px;cursor:pointer;font-size:13px;font-weight:500;',
|
|
7369
|
-
|
|
7370
|
-
|
|
7371
|
-
|
|
7372
|
-
|
|
7373
|
-
|
|
7374
|
-
|
|
7375
|
-
|
|
7376
|
-
/* Only a figure somebody CHOSE is written: the
|
|
7377
|
-
* one the select opened on is today's default,
|
|
7378
|
-
* and saving it would freeze it on the node. */
|
|
7379
|
-
(shapeSelect.value !== orig_figure)? shapeSelect.value : ""
|
|
7380
|
-
);
|
|
7776
|
+
event: "EV_APPLY_NODE_POPOVER",
|
|
7777
|
+
/* `shape_before`: the figure the select opened on, which
|
|
7778
|
+
* the action does not write (see ac_apply_node_popover). */
|
|
7779
|
+
kw: {node_id: node_id, shape_before: orig_figure},
|
|
7780
|
+
form: {
|
|
7781
|
+
fill: fillInput, stroke: strokeInput, line_width: lwInput,
|
|
7782
|
+
scope: scopeSelect, shape: shapeSelect
|
|
7381
7783
|
},
|
|
7382
7784
|
},
|
|
7383
|
-
]);
|
|
7785
|
+
], gobj);
|
|
7384
7786
|
|
|
7385
7787
|
priv.$container.appendChild(popover);
|
|
7386
7788
|
priv._node_popover_el = popover;
|
|
@@ -7408,6 +7810,49 @@ function hide_node_detail(gobj)
|
|
|
7408
7810
|
hide_overlay(gobj, '_node_detail_el');
|
|
7409
7811
|
}
|
|
7410
7812
|
|
|
7813
|
+
/************************************************************
|
|
7814
|
+
* The ✕ of the detail popover. Built as an element and not in
|
|
7815
|
+
* the popover's markup, so that it is a whole button: named
|
|
7816
|
+
* with its key (a language switch renames it), reachable by
|
|
7817
|
+
* the keyboard and pressed by Enter or Space as by the pointer.
|
|
7818
|
+
* Its click sends EV_CLOSE_NODE_DETAIL {node_id} and nothing
|
|
7819
|
+
* else; a pointer press does not take the focus.
|
|
7820
|
+
************************************************************/
|
|
7821
|
+
function create_detail_close(gobj, color, node_id)
|
|
7822
|
+
{
|
|
7823
|
+
let el = document.createElement('div');
|
|
7824
|
+
el.className = 'g6-detail-close';
|
|
7825
|
+
el.setAttribute('role', 'button');
|
|
7826
|
+
el.setAttribute('tabindex', '0');
|
|
7827
|
+
el.setAttribute('title', t('close'));
|
|
7828
|
+
el.setAttribute('data-i18n-title', 'close');
|
|
7829
|
+
el.setAttribute('aria-label', t('close'));
|
|
7830
|
+
el.setAttribute('data-i18n-aria-label', 'close');
|
|
7831
|
+
el.style.color = color;
|
|
7832
|
+
el.textContent = '×';
|
|
7833
|
+
el.addEventListener('click', (e) => {
|
|
7834
|
+
e.stopPropagation();
|
|
7835
|
+
gobj_send_event(gobj, "EV_CLOSE_NODE_DETAIL", {node_id: node_id}, gobj);
|
|
7836
|
+
});
|
|
7837
|
+
keep_focus_on_press(el);
|
|
7838
|
+
click_on_enter_or_space(el);
|
|
7839
|
+
return el;
|
|
7840
|
+
}
|
|
7841
|
+
|
|
7842
|
+
/* A `div` of the detail popover: its class, and its text as a
|
|
7843
|
+
* text node. */
|
|
7844
|
+
function detail_div(class_name, text)
|
|
7845
|
+
{
|
|
7846
|
+
let el = document.createElement('div');
|
|
7847
|
+
if(class_name) {
|
|
7848
|
+
el.className = class_name;
|
|
7849
|
+
}
|
|
7850
|
+
if(text !== undefined) {
|
|
7851
|
+
el.textContent = text;
|
|
7852
|
+
}
|
|
7853
|
+
return el;
|
|
7854
|
+
}
|
|
7855
|
+
|
|
7411
7856
|
function show_node_detail_popover(gobj, node_id)
|
|
7412
7857
|
{
|
|
7413
7858
|
let priv = gobj.priv;
|
|
@@ -7455,7 +7900,30 @@ function show_node_detail_popover(gobj, node_id)
|
|
|
7455
7900
|
"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, " +
|
|
7456
7901
|
"Helvetica, Arial, sans-serif";
|
|
7457
7902
|
|
|
7458
|
-
|
|
7903
|
+
/*
|
|
7904
|
+
* Built as elements, every value a TEXT node: an id, a topic, a
|
|
7905
|
+
* field and its value are data, never markup.
|
|
7906
|
+
*
|
|
7907
|
+
* The head is STICKY: the box scrolls, and with the head scrolling
|
|
7908
|
+
* away the ✕ left the screen -- on a phone, where the box is most
|
|
7909
|
+
* of the screen, that is a popover you cannot close without
|
|
7910
|
+
* scrolling back up for the button. Its background is the
|
|
7911
|
+
* popover's own, or the rows would show through it.
|
|
7912
|
+
*/
|
|
7913
|
+
let head = detail_div('g6-detail-head');
|
|
7914
|
+
head.style.background = bg;
|
|
7915
|
+
let names = detail_div('');
|
|
7916
|
+
names.style.flex = '1 1 auto';
|
|
7917
|
+
names.style.minWidth = '0';
|
|
7918
|
+
names.appendChild(detail_div('g6-detail-id', id));
|
|
7919
|
+
let topic_el = detail_div('g6-detail-topic', topic);
|
|
7920
|
+
topic_el.style.color = sub;
|
|
7921
|
+
names.appendChild(topic_el);
|
|
7922
|
+
head.appendChild(names);
|
|
7923
|
+
head.appendChild(create_detail_close(gobj, sub, node_id));
|
|
7924
|
+
popover.appendChild(head);
|
|
7925
|
+
|
|
7926
|
+
let n_rows = 0;
|
|
7459
7927
|
for(let key of Object.keys(record)) {
|
|
7460
7928
|
if(key.indexOf('__') === 0) { // skip internal metadata
|
|
7461
7929
|
continue;
|
|
@@ -7472,42 +7940,20 @@ function show_node_detail_popover(gobj, node_id)
|
|
|
7472
7940
|
if(vs.length > 240) {
|
|
7473
7941
|
vs = vs.slice(0, 240) + '…';
|
|
7474
7942
|
}
|
|
7475
|
-
|
|
7476
|
-
|
|
7477
|
-
|
|
7478
|
-
|
|
7479
|
-
|
|
7480
|
-
|
|
7943
|
+
let row = detail_div('g6-detail-row');
|
|
7944
|
+
row.style.borderTop = `1px solid ${rowbd}`;
|
|
7945
|
+
let key_el = detail_div('g6-detail-key', key);
|
|
7946
|
+
key_el.style.color = sub;
|
|
7947
|
+
row.appendChild(key_el);
|
|
7948
|
+
row.appendChild(detail_div('g6-detail-val', vs));
|
|
7949
|
+
popover.appendChild(row);
|
|
7950
|
+
n_rows++;
|
|
7481
7951
|
}
|
|
7482
|
-
|
|
7483
|
-
|
|
7484
|
-
|
|
7485
|
-
|
|
7486
|
-
|
|
7487
|
-
* scrolling back up for the button. Its background is the
|
|
7488
|
-
* popover's own, or the rows would show through it.
|
|
7489
|
-
*/
|
|
7490
|
-
popover.innerHTML =
|
|
7491
|
-
`<div class="g6-detail-head" style="background:${bg};">` +
|
|
7492
|
-
`<div style="flex:1 1 auto;min-width:0;">` +
|
|
7493
|
-
`<div class="g6-detail-id">${escapeHtml(id)}</div>` +
|
|
7494
|
-
`<div class="g6-detail-topic" style="color:${sub};">` +
|
|
7495
|
-
`${escapeHtml(topic)}</div>` +
|
|
7496
|
-
`</div>` +
|
|
7497
|
-
`<div class="g6-detail-close" role="button" tabindex="0"` +
|
|
7498
|
-
` aria-label="${escapeHtml(t('close'))}"` +
|
|
7499
|
-
` title="${escapeHtml(t('close'))}"` +
|
|
7500
|
-
` style="color:${sub};">×</div>` +
|
|
7501
|
-
`</div>` +
|
|
7502
|
-
(rows || `<div class="g6-detail-empty" style="color:${sub};">` +
|
|
7503
|
-
`${escapeHtml(t('no fields'))}</div>`);
|
|
7504
|
-
|
|
7505
|
-
let close_el = popover.querySelector('.g6-detail-close');
|
|
7506
|
-
if(close_el) {
|
|
7507
|
-
close_el.addEventListener('click', (e) => {
|
|
7508
|
-
e.stopPropagation();
|
|
7509
|
-
hide_node_detail(gobj);
|
|
7510
|
-
});
|
|
7952
|
+
if(!n_rows) {
|
|
7953
|
+
let empty = detail_div('g6-detail-empty', t('no fields'));
|
|
7954
|
+
empty.setAttribute('data-i18n', 'no fields');
|
|
7955
|
+
empty.style.color = sub;
|
|
7956
|
+
popover.appendChild(empty);
|
|
7511
7957
|
}
|
|
7512
7958
|
|
|
7513
7959
|
priv.$container.appendChild(popover);
|
|
@@ -8327,10 +8773,10 @@ function request_delete_selection(gobj)
|
|
|
8327
8773
|
show_confirm_popover(gobj, null,
|
|
8328
8774
|
delete_question(nodes),
|
|
8329
8775
|
'delete', '#ff4d4f',
|
|
8330
|
-
|
|
8776
|
+
{
|
|
8331
8777
|
what: "delete_selection",
|
|
8332
8778
|
ids: nodes.map((nd) => String(nd.id))
|
|
8333
|
-
},
|
|
8779
|
+
},
|
|
8334
8780
|
'_delete_confirm_el'
|
|
8335
8781
|
);
|
|
8336
8782
|
}
|
|
@@ -8367,10 +8813,10 @@ function show_delete_confirm(gobj, nodeData)
|
|
|
8367
8813
|
show_confirm_popover(gobj, priv._node_delete_el,
|
|
8368
8814
|
delete_question([nodeData]),
|
|
8369
8815
|
'delete', '#ff4d4f',
|
|
8370
|
-
|
|
8816
|
+
{
|
|
8371
8817
|
what: "delete_node",
|
|
8372
8818
|
id: String(nodeData.id)
|
|
8373
|
-
},
|
|
8819
|
+
},
|
|
8374
8820
|
'_delete_confirm_el'
|
|
8375
8821
|
);
|
|
8376
8822
|
}
|
|
@@ -8452,6 +8898,7 @@ function show_create_popover(gobj)
|
|
|
8452
8898
|
popover.appendChild(topicLabel);
|
|
8453
8899
|
|
|
8454
8900
|
let topicSelect = document.createElement('select');
|
|
8901
|
+
topicSelect.className = 'G6_CREATE_TOPIC';
|
|
8455
8902
|
topicSelect.setAttribute('title', t('topic'));
|
|
8456
8903
|
topicSelect.setAttribute('data-i18n-title', 'topic');
|
|
8457
8904
|
topicSelect.setAttribute('aria-label', t('topic'));
|
|
@@ -8493,10 +8940,15 @@ function show_create_popover(gobj)
|
|
|
8493
8940
|
// Node ID input
|
|
8494
8941
|
let idLabel = document.createElement('div');
|
|
8495
8942
|
idLabel.style.cssText = 'margin-bottom:4px;font-weight:500;';
|
|
8496
|
-
|
|
8943
|
+
let idWord = document.createElement('span');
|
|
8944
|
+
idWord.textContent = t('node id');
|
|
8945
|
+
idWord.setAttribute('data-i18n', 'node id');
|
|
8946
|
+
idLabel.appendChild(idWord);
|
|
8947
|
+
idLabel.appendChild(document.createTextNode(':'));
|
|
8497
8948
|
popover.appendChild(idLabel);
|
|
8498
8949
|
|
|
8499
8950
|
let idInput = document.createElement('input');
|
|
8951
|
+
idInput.className = 'G6_CREATE_ID';
|
|
8500
8952
|
idInput.type = 'text';
|
|
8501
8953
|
idInput.placeholder = t('node id');
|
|
8502
8954
|
idInput.setAttribute('data-i18n-placeholder', 'node id');
|
|
@@ -8512,48 +8964,39 @@ function show_create_popover(gobj)
|
|
|
8512
8964
|
|
|
8513
8965
|
// Error message area
|
|
8514
8966
|
let errorEl = document.createElement('div');
|
|
8967
|
+
errorEl.className = 'G6_CREATE_ERROR';
|
|
8515
8968
|
errorEl.style.cssText =
|
|
8516
8969
|
'color:#ff4d4f;font-size:12px;margin-bottom:8px;display:none;';
|
|
8517
8970
|
popover.appendChild(errorEl);
|
|
8518
8971
|
|
|
8519
|
-
|
|
8520
|
-
|
|
8521
|
-
create_form_button_row(popover, [
|
|
8972
|
+
/* Create sends what the form holds; an empty id is answered by
|
|
8973
|
+
* the action (ac_submit_new_node), in the popover. */
|
|
8974
|
+
let btnRow = create_form_button_row(popover, [
|
|
8522
8975
|
{
|
|
8523
8976
|
i18n: 'cancel',
|
|
8524
8977
|
style: BTN_STYLE_CANCEL,
|
|
8525
|
-
|
|
8978
|
+
event: "EV_CLOSE_CREATE_POPOVER",
|
|
8979
|
+
kw: {},
|
|
8526
8980
|
},
|
|
8527
8981
|
{
|
|
8528
8982
|
i18n: 'create',
|
|
8529
8983
|
style: 'flex:1;padding:6px;background:#1890ff;color:#fff;border:none;' +
|
|
8530
8984
|
'border-radius:4px;cursor:pointer;font-size:13px;font-weight:500;',
|
|
8531
|
-
|
|
8532
|
-
|
|
8533
|
-
|
|
8534
|
-
errorEl.textContent = t('node id required');
|
|
8535
|
-
errorEl.setAttribute('data-i18n', 'node id required');
|
|
8536
|
-
errorEl.style.display = 'block';
|
|
8537
|
-
idInput.focus();
|
|
8538
|
-
return;
|
|
8539
|
-
}
|
|
8540
|
-
gobj_send_event(gobj, "EV_SUBMIT_NEW_NODE", {
|
|
8541
|
-
topic_name: String(topicSelect.value),
|
|
8542
|
-
id: node_id
|
|
8543
|
-
}, gobj);
|
|
8544
|
-
},
|
|
8985
|
+
event: "EV_SUBMIT_NEW_NODE",
|
|
8986
|
+
kw: {},
|
|
8987
|
+
form: {topic_name: topicSelect, id: idInput},
|
|
8545
8988
|
},
|
|
8546
|
-
]);
|
|
8547
|
-
createBtn =
|
|
8989
|
+
], gobj);
|
|
8990
|
+
let createBtn = btnRow.lastElementChild; // for the Enter key
|
|
8548
8991
|
|
|
8549
|
-
// Enter key submits
|
|
8992
|
+
// Enter key submits, Escape closes: the two buttons' events
|
|
8550
8993
|
idInput.addEventListener('keydown', (e) => {
|
|
8551
8994
|
if(e.key === 'Enter') {
|
|
8552
8995
|
e.preventDefault();
|
|
8553
8996
|
createBtn.click();
|
|
8554
8997
|
} else if(e.key === 'Escape') {
|
|
8555
8998
|
e.preventDefault();
|
|
8556
|
-
|
|
8999
|
+
gobj_send_event(gobj, "EV_CLOSE_CREATE_POPOVER", {}, gobj);
|
|
8557
9000
|
}
|
|
8558
9001
|
});
|
|
8559
9002
|
|
|
@@ -8646,10 +9089,10 @@ function show_unlink_confirm(gobj, edgeData)
|
|
|
8646
9089
|
t('unlink') + ' ' + d.child_id + ' → ' + d.parent_id + '?' +
|
|
8647
9090
|
'\n' + t('neither record is deleted'),
|
|
8648
9091
|
'unlink', '#ff4d4f',
|
|
8649
|
-
|
|
9092
|
+
{
|
|
8650
9093
|
what: "unlink_edge",
|
|
8651
9094
|
id: String(edgeData.id)
|
|
8652
|
-
},
|
|
9095
|
+
},
|
|
8653
9096
|
'_unlink_confirm_el'
|
|
8654
9097
|
);
|
|
8655
9098
|
}
|
|
@@ -10646,10 +11089,7 @@ function ac_save_graph(gobj, event, kw, src)
|
|
|
10646
11089
|
let priv = gobj.priv;
|
|
10647
11090
|
|
|
10648
11091
|
if(priv.edit_mode) {
|
|
10649
|
-
|
|
10650
|
-
|
|
10651
|
-
disableElements($container, ".EV_SAVE_GRAPH");
|
|
10652
|
-
set_submit_state($container, ".EV_SAVE_GRAPH", false);
|
|
11092
|
+
light_save_button(gobj, false);
|
|
10653
11093
|
|
|
10654
11094
|
let history = graph_get_plugin(gobj, "history");
|
|
10655
11095
|
if(history) {
|
|
@@ -11111,17 +11551,27 @@ function ac_center_anchor(gobj, event, kw, src)
|
|
|
11111
11551
|
}
|
|
11112
11552
|
|
|
11113
11553
|
/************************************************************
|
|
11114
|
-
* What the anchor button says it will do next
|
|
11554
|
+
* What the anchor button says it will do next: its `title`,
|
|
11555
|
+
* and the `title_key` it is translated from.
|
|
11115
11556
|
************************************************************/
|
|
11116
11557
|
function anchor_title(state)
|
|
11117
11558
|
{
|
|
11118
11559
|
if(state === "arming") {
|
|
11119
|
-
return
|
|
11560
|
+
return {
|
|
11561
|
+
title: t('click the element to centre on'),
|
|
11562
|
+
title_key: 'click the element to centre on'
|
|
11563
|
+
};
|
|
11120
11564
|
}
|
|
11121
11565
|
if(state === "on") {
|
|
11122
|
-
return
|
|
11566
|
+
return {
|
|
11567
|
+
title: t('centred: click to release'),
|
|
11568
|
+
title_key: 'centred: click to release'
|
|
11569
|
+
};
|
|
11123
11570
|
}
|
|
11124
|
-
return
|
|
11571
|
+
return {
|
|
11572
|
+
title: t('anchor view'),
|
|
11573
|
+
title_key: 'anchor view'
|
|
11574
|
+
};
|
|
11125
11575
|
}
|
|
11126
11576
|
|
|
11127
11577
|
/************************************************************
|
|
@@ -11652,11 +12102,15 @@ function ac_set_operation_mode(gobj, event, kw, src)
|
|
|
11652
12102
|
{
|
|
11653
12103
|
gobj_write_attr(gobj, "operation_mode", kw.operation_mode);
|
|
11654
12104
|
configure_behaviour(gobj);
|
|
11655
|
-
update_toolbar(gobj);
|
|
11656
12105
|
|
|
11657
12106
|
/* `configure_behaviour` is what decides `edit_mode`, and the
|
|
11658
|
-
* history plugin belongs to that decision.
|
|
12107
|
+
* history plugin belongs to that decision. BEFORE the toolbars:
|
|
12108
|
+
* the edit toolbar entering edition reads its Save from what
|
|
12109
|
+
* update_history_buttons() says of the new history -- after
|
|
12110
|
+
* it, the toolbar was made with what the Save was told in the
|
|
12111
|
+
* LAST edition. */
|
|
11659
12112
|
sync_history_plugin(gobj);
|
|
12113
|
+
update_toolbar(gobj);
|
|
11660
12114
|
return 0;
|
|
11661
12115
|
}
|
|
11662
12116
|
|
|
@@ -11941,31 +12395,181 @@ function ac_context_menu_item(gobj, event, kw, src)
|
|
|
11941
12395
|
}
|
|
11942
12396
|
|
|
11943
12397
|
/************************************************************
|
|
11944
|
-
*
|
|
12398
|
+
* Is the identity a floating button was drawn with still what
|
|
12399
|
+
* is selected?
|
|
12400
|
+
*
|
|
12401
|
+
* The buttons are removed with their selection and drawn again
|
|
12402
|
+
* for the next one, so a click carrying another identity is a
|
|
12403
|
+
* broken invariant -- said loudly, never acted on: it would
|
|
12404
|
+
* delete, unlink or edit what the reader is not looking at.
|
|
12405
|
+
************************************************************/
|
|
12406
|
+
function floating_button_is_current(gobj, event, kw)
|
|
12407
|
+
{
|
|
12408
|
+
let priv = gobj.priv;
|
|
12409
|
+
let want = {
|
|
12410
|
+
node_id: priv._selected_node_id || "",
|
|
12411
|
+
port_key: priv._selected_port_key || "",
|
|
12412
|
+
edge_id: priv._selected_edge_id || "",
|
|
12413
|
+
};
|
|
12414
|
+
let keys = Object.keys(kw || {});
|
|
12415
|
+
let matches = keys.length > 0 && keys.every((k) => {
|
|
12416
|
+
return Object.prototype.hasOwnProperty.call(want, k) &&
|
|
12417
|
+
!!kw[k] && kw[k] === want[k];
|
|
12418
|
+
});
|
|
12419
|
+
if(!matches) {
|
|
12420
|
+
log_error(`${gobj_short_name(gobj)}: ${event} for ${JSON.stringify(kw || {})}, ` +
|
|
12421
|
+
`but the selection is ${JSON.stringify(want)}`);
|
|
12422
|
+
}
|
|
12423
|
+
return matches;
|
|
12424
|
+
}
|
|
12425
|
+
|
|
12426
|
+
/************************************************************
|
|
12427
|
+
* {node_id} -- the delete icon floating beside the selected
|
|
12428
|
+
* node.
|
|
11945
12429
|
************************************************************/
|
|
11946
12430
|
function ac_request_delete_node(gobj, event, kw, src)
|
|
11947
12431
|
{
|
|
12432
|
+
if(!floating_button_is_current(gobj, event, kw)) {
|
|
12433
|
+
return -1; // Error already logged
|
|
12434
|
+
}
|
|
11948
12435
|
request_delete_node(gobj);
|
|
11949
12436
|
return 0;
|
|
11950
12437
|
}
|
|
11951
12438
|
|
|
11952
12439
|
/************************************************************
|
|
11953
|
-
*
|
|
12440
|
+
* {edge_id} -- the unlink icon floating beside the selected
|
|
12441
|
+
* edge.
|
|
11954
12442
|
************************************************************/
|
|
11955
12443
|
function ac_request_unlink_edge(gobj, event, kw, src)
|
|
11956
12444
|
{
|
|
12445
|
+
if(!floating_button_is_current(gobj, event, kw)) {
|
|
12446
|
+
return -1; // Error already logged
|
|
12447
|
+
}
|
|
11957
12448
|
request_unlink_edge(gobj);
|
|
11958
12449
|
return 0;
|
|
11959
12450
|
}
|
|
11960
12451
|
|
|
12452
|
+
/************************************************************
|
|
12453
|
+
* {node_id} -- the gear beside the selected node: its
|
|
12454
|
+
* properties popover, opened or closed.
|
|
12455
|
+
************************************************************/
|
|
12456
|
+
function ac_toggle_node_popover(gobj, event, kw, src)
|
|
12457
|
+
{
|
|
12458
|
+
if(!floating_button_is_current(gobj, event, kw)) {
|
|
12459
|
+
return -1; // Error already logged
|
|
12460
|
+
}
|
|
12461
|
+
toggle_node_popover(gobj);
|
|
12462
|
+
return 0;
|
|
12463
|
+
}
|
|
12464
|
+
|
|
12465
|
+
/************************************************************
|
|
12466
|
+
* {edge_id} -- the gear beside the selected edge.
|
|
12467
|
+
************************************************************/
|
|
12468
|
+
function ac_toggle_edge_popover(gobj, event, kw, src)
|
|
12469
|
+
{
|
|
12470
|
+
if(!floating_button_is_current(gobj, event, kw)) {
|
|
12471
|
+
return -1; // Error already logged
|
|
12472
|
+
}
|
|
12473
|
+
toggle_edge_popover(gobj);
|
|
12474
|
+
return 0;
|
|
12475
|
+
}
|
|
12476
|
+
|
|
12477
|
+
/************************************************************
|
|
12478
|
+
* {node_id, port_key} -- the gear beside the selected port.
|
|
12479
|
+
************************************************************/
|
|
12480
|
+
function ac_toggle_port_popover(gobj, event, kw, src)
|
|
12481
|
+
{
|
|
12482
|
+
if(!floating_button_is_current(gobj, event, kw)) {
|
|
12483
|
+
return -1; // Error already logged
|
|
12484
|
+
}
|
|
12485
|
+
toggle_port_popover(gobj);
|
|
12486
|
+
return 0;
|
|
12487
|
+
}
|
|
12488
|
+
|
|
12489
|
+
/************************************************************
|
|
12490
|
+
* {node_id, port_key} -- the link icon of a selected fkey port,
|
|
12491
|
+
* CLICKED (a press that drags enters the mode on its own, from
|
|
12492
|
+
* start_link_drag): the compatible hooks light up, and the next
|
|
12493
|
+
* click on one of them links.
|
|
12494
|
+
************************************************************/
|
|
12495
|
+
function ac_enter_linking_mode(gobj, event, kw, src)
|
|
12496
|
+
{
|
|
12497
|
+
let priv = gobj.priv;
|
|
12498
|
+
|
|
12499
|
+
if(!floating_button_is_current(gobj, event, kw)) {
|
|
12500
|
+
return -1; // Error already logged
|
|
12501
|
+
}
|
|
12502
|
+
if(!priv._linking_mode) {
|
|
12503
|
+
enter_linking_mode(gobj);
|
|
12504
|
+
}
|
|
12505
|
+
return 0;
|
|
12506
|
+
}
|
|
12507
|
+
|
|
12508
|
+
/************************************************************
|
|
12509
|
+
* {node_id} -- the ✕ of the detail popover.
|
|
12510
|
+
************************************************************/
|
|
12511
|
+
function ac_close_node_detail(gobj, event, kw, src)
|
|
12512
|
+
{
|
|
12513
|
+
let priv = gobj.priv;
|
|
12514
|
+
let node_id = (kw && kw.node_id) || "";
|
|
12515
|
+
|
|
12516
|
+
if(!priv._node_detail_el || !node_id || node_id !== priv._detail_node_id) {
|
|
12517
|
+
log_error(`${gobj_short_name(gobj)}: ${event} for '${node_id}', ` +
|
|
12518
|
+
`but the detail popover shows '${priv._detail_node_id || ""}'`);
|
|
12519
|
+
return -1;
|
|
12520
|
+
}
|
|
12521
|
+
hide_node_detail(gobj);
|
|
12522
|
+
return 0;
|
|
12523
|
+
}
|
|
12524
|
+
|
|
12525
|
+
/************************************************************
|
|
12526
|
+
* {} -- the create button of the edit toolbar: the create
|
|
12527
|
+
* popover, opened or closed. Only in edition, where that
|
|
12528
|
+
* toolbar is.
|
|
12529
|
+
************************************************************/
|
|
12530
|
+
function ac_toggle_create_popover(gobj, event, kw, src)
|
|
12531
|
+
{
|
|
12532
|
+
let priv = gobj.priv;
|
|
12533
|
+
|
|
12534
|
+
if(!priv.edit_mode) {
|
|
12535
|
+
log_error(`${gobj_short_name(gobj)}: ${event} outside edition`);
|
|
12536
|
+
return -1;
|
|
12537
|
+
}
|
|
12538
|
+
toggle_create_popover(gobj);
|
|
12539
|
+
return 0;
|
|
12540
|
+
}
|
|
12541
|
+
|
|
12542
|
+
/************************************************************
|
|
12543
|
+
* {} -- the cancel of the create popover, or Escape in its id.
|
|
12544
|
+
************************************************************/
|
|
12545
|
+
function ac_close_create_popover(gobj, event, kw, src)
|
|
12546
|
+
{
|
|
12547
|
+
let priv = gobj.priv;
|
|
12548
|
+
|
|
12549
|
+
if(!priv._create_popover_el) {
|
|
12550
|
+
log_error(`${gobj_short_name(gobj)}: ${event} with no create popover open`);
|
|
12551
|
+
return -1;
|
|
12552
|
+
}
|
|
12553
|
+
hide_create_popover(gobj);
|
|
12554
|
+
return 0;
|
|
12555
|
+
}
|
|
12556
|
+
|
|
11961
12557
|
/************************************************************
|
|
11962
12558
|
* {topic_name, id} -- the create popover was filled in.
|
|
12559
|
+
*
|
|
12560
|
+
* An empty id with the popover open is the person's to fix,
|
|
12561
|
+
* not a fault: it is said IN the popover, where they typed.
|
|
11963
12562
|
************************************************************/
|
|
11964
12563
|
function ac_submit_new_node(gobj, event, kw, src)
|
|
11965
12564
|
{
|
|
11966
|
-
let
|
|
11967
|
-
let
|
|
12565
|
+
let priv = gobj.priv;
|
|
12566
|
+
let topic_name = (kw && kw.topic_name)? String(kw.topic_name) : "";
|
|
12567
|
+
let node_id = (kw && kw.id)? String(kw.id).trim() : "";
|
|
11968
12568
|
|
|
12569
|
+
if(topic_name && !node_id && priv._create_popover_el) {
|
|
12570
|
+
say_node_id_required(gobj);
|
|
12571
|
+
return 0;
|
|
12572
|
+
}
|
|
11969
12573
|
if(!topic_name || !node_id) {
|
|
11970
12574
|
log_error(`${gobj_short_name(gobj)}: ${event} without topic or id`);
|
|
11971
12575
|
return -1;
|
|
@@ -11974,6 +12578,248 @@ function ac_submit_new_node(gobj, event, kw, src)
|
|
|
11974
12578
|
return 0;
|
|
11975
12579
|
}
|
|
11976
12580
|
|
|
12581
|
+
/* "node id required", under the id of the create popover, and
|
|
12582
|
+
* the focus back on the id. */
|
|
12583
|
+
function say_node_id_required(gobj)
|
|
12584
|
+
{
|
|
12585
|
+
let $popover = gobj.priv._create_popover_el;
|
|
12586
|
+
let $error = $popover.querySelector('.G6_CREATE_ERROR');
|
|
12587
|
+
let $id = $popover.querySelector('.G6_CREATE_ID');
|
|
12588
|
+
|
|
12589
|
+
if(!$error || !$id) {
|
|
12590
|
+
log_error(`${gobj_short_name(gobj)}: the create popover has no id or no error line`);
|
|
12591
|
+
return;
|
|
12592
|
+
}
|
|
12593
|
+
$error.textContent = t('node id required');
|
|
12594
|
+
$error.setAttribute('data-i18n', 'node id required');
|
|
12595
|
+
$error.style.display = 'block';
|
|
12596
|
+
$id.focus();
|
|
12597
|
+
}
|
|
12598
|
+
|
|
12599
|
+
/************************************************************
|
|
12600
|
+
* Is `popover_key` open, and for the selection `kw` names?
|
|
12601
|
+
* `keys` are the identity in `kw` (the rest is what the form
|
|
12602
|
+
* held). A button of a popover that is not the one open is a
|
|
12603
|
+
* broken invariant -- said loudly, never acted on.
|
|
12604
|
+
************************************************************/
|
|
12605
|
+
function popover_is_current(gobj, event, kw, popover_key, keys)
|
|
12606
|
+
{
|
|
12607
|
+
let priv = gobj.priv;
|
|
12608
|
+
|
|
12609
|
+
if(!priv[popover_key]) {
|
|
12610
|
+
log_error(`${gobj_short_name(gobj)}: ${event} for ${JSON.stringify(kw || {})}, ` +
|
|
12611
|
+
`but its popover is not open`);
|
|
12612
|
+
return false;
|
|
12613
|
+
}
|
|
12614
|
+
let identity = {};
|
|
12615
|
+
for(let k of keys) {
|
|
12616
|
+
identity[k] = kw? kw[k] : undefined;
|
|
12617
|
+
}
|
|
12618
|
+
return floating_button_is_current(gobj, event, identity);
|
|
12619
|
+
}
|
|
12620
|
+
|
|
12621
|
+
/************************************************************
|
|
12622
|
+
* {node_id, shape_before, fill, stroke, line_width, scope,
|
|
12623
|
+
* shape} -- apply of the node popover.
|
|
12624
|
+
************************************************************/
|
|
12625
|
+
function ac_apply_node_popover(gobj, event, kw, src)
|
|
12626
|
+
{
|
|
12627
|
+
if(!popover_is_current(gobj, event, kw, '_node_popover_el', ['node_id'])) {
|
|
12628
|
+
return -1; // Error already logged
|
|
12629
|
+
}
|
|
12630
|
+
let shape = String(kw.shape || "");
|
|
12631
|
+
drop_preview_undo(gobj, '_node_preview_undo');
|
|
12632
|
+
apply_node_properties(gobj, kw.node_id,
|
|
12633
|
+
kw.fill,
|
|
12634
|
+
kw.stroke,
|
|
12635
|
+
parseInt(kw.line_width) || 1,
|
|
12636
|
+
kw.scope,
|
|
12637
|
+
/* Only a figure somebody CHOSE is written: the one the
|
|
12638
|
+
* select opened on is today's default, and saving it
|
|
12639
|
+
* would freeze it on the node. */
|
|
12640
|
+
(shape !== kw.shape_before)? shape : ""
|
|
12641
|
+
);
|
|
12642
|
+
return 0;
|
|
12643
|
+
}
|
|
12644
|
+
|
|
12645
|
+
/************************************************************
|
|
12646
|
+
* {node_id} -- cancel of the node popover, which undoes the
|
|
12647
|
+
* preview.
|
|
12648
|
+
************************************************************/
|
|
12649
|
+
function ac_cancel_node_popover(gobj, event, kw, src)
|
|
12650
|
+
{
|
|
12651
|
+
if(!popover_is_current(gobj, event, kw, '_node_popover_el', ['node_id'])) {
|
|
12652
|
+
return -1; // Error already logged
|
|
12653
|
+
}
|
|
12654
|
+
hide_node_popover(gobj);
|
|
12655
|
+
return 0;
|
|
12656
|
+
}
|
|
12657
|
+
|
|
12658
|
+
/************************************************************
|
|
12659
|
+
* {edge_id, line_width, color, scope} -- apply of the edge
|
|
12660
|
+
* popover.
|
|
12661
|
+
************************************************************/
|
|
12662
|
+
function ac_apply_edge_popover(gobj, event, kw, src)
|
|
12663
|
+
{
|
|
12664
|
+
if(!popover_is_current(gobj, event, kw, '_edge_popover_el', ['edge_id'])) {
|
|
12665
|
+
return -1; // Error already logged
|
|
12666
|
+
}
|
|
12667
|
+
drop_preview_undo(gobj, '_edge_preview_undo');
|
|
12668
|
+
apply_edge_properties(gobj, kw.edge_id,
|
|
12669
|
+
parseInt(kw.line_width) || 2,
|
|
12670
|
+
kw.color,
|
|
12671
|
+
kw.scope
|
|
12672
|
+
);
|
|
12673
|
+
return 0;
|
|
12674
|
+
}
|
|
12675
|
+
|
|
12676
|
+
/************************************************************
|
|
12677
|
+
* {edge_id} -- cancel of the edge popover: the edge is let go,
|
|
12678
|
+
* which undoes the preview.
|
|
12679
|
+
************************************************************/
|
|
12680
|
+
function ac_cancel_edge_popover(gobj, event, kw, src)
|
|
12681
|
+
{
|
|
12682
|
+
if(!popover_is_current(gobj, event, kw, '_edge_popover_el', ['edge_id'])) {
|
|
12683
|
+
return -1; // Error already logged
|
|
12684
|
+
}
|
|
12685
|
+
deselect_edge(gobj);
|
|
12686
|
+
return 0;
|
|
12687
|
+
}
|
|
12688
|
+
|
|
12689
|
+
/************************************************************
|
|
12690
|
+
* {node_id, port_key, r_before, shape, r, scope} -- apply of
|
|
12691
|
+
* the port popover.
|
|
12692
|
+
************************************************************/
|
|
12693
|
+
function ac_apply_port_popover(gobj, event, kw, src)
|
|
12694
|
+
{
|
|
12695
|
+
if(!popover_is_current(gobj, event, kw, '_port_popover_el', ['node_id', 'port_key'])) {
|
|
12696
|
+
return -1; // Error already logged
|
|
12697
|
+
}
|
|
12698
|
+
drop_preview_undo(gobj, '_port_preview_undo');
|
|
12699
|
+
apply_port_properties(gobj, kw.node_id, kw.port_key,
|
|
12700
|
+
kw.shape, parseInt(kw.r) || kw.r_before, kw.scope
|
|
12701
|
+
);
|
|
12702
|
+
return 0;
|
|
12703
|
+
}
|
|
12704
|
+
|
|
12705
|
+
/************************************************************
|
|
12706
|
+
* {node_id, port_key} -- cancel of the port popover, which
|
|
12707
|
+
* undoes the preview.
|
|
12708
|
+
************************************************************/
|
|
12709
|
+
function ac_cancel_port_popover(gobj, event, kw, src)
|
|
12710
|
+
{
|
|
12711
|
+
if(!popover_is_current(gobj, event, kw, '_port_popover_el', ['node_id', 'port_key'])) {
|
|
12712
|
+
return -1; // Error already logged
|
|
12713
|
+
}
|
|
12714
|
+
hide_port_popover(gobj);
|
|
12715
|
+
return 0;
|
|
12716
|
+
}
|
|
12717
|
+
|
|
12718
|
+
/* Where the confirm popover of each question is kept. */
|
|
12719
|
+
const CONFIRM_POPOVER_OF = {
|
|
12720
|
+
delete_selection: '_delete_confirm_el',
|
|
12721
|
+
delete_node: '_delete_confirm_el',
|
|
12722
|
+
unlink_edge: '_unlink_confirm_el',
|
|
12723
|
+
};
|
|
12724
|
+
|
|
12725
|
+
function same_question(a, b)
|
|
12726
|
+
{
|
|
12727
|
+
return !!a && !!b && a.what === b.what &&
|
|
12728
|
+
JSON.stringify(a.id ?? null) === JSON.stringify(b.id ?? null) &&
|
|
12729
|
+
JSON.stringify(a.ids ?? null) === JSON.stringify(b.ids ?? null);
|
|
12730
|
+
}
|
|
12731
|
+
|
|
12732
|
+
/************************************************************
|
|
12733
|
+
* {what, id|ids} -- the person said no in a confirm popover:
|
|
12734
|
+
* the question it carries must be the one open.
|
|
12735
|
+
************************************************************/
|
|
12736
|
+
function ac_cancel_confirm(gobj, event, kw, src)
|
|
12737
|
+
{
|
|
12738
|
+
let priv = gobj.priv;
|
|
12739
|
+
let key = CONFIRM_POPOVER_OF[(kw && kw.what) || ""];
|
|
12740
|
+
let asked = key? priv._confirm_questions[key] : null;
|
|
12741
|
+
|
|
12742
|
+
if(!key || !priv[key] || !same_question(asked, kw)) {
|
|
12743
|
+
log_error(`${gobj_short_name(gobj)}: ${event} for ${JSON.stringify(kw || {})}, ` +
|
|
12744
|
+
`but the question open is ${JSON.stringify((priv[key] && asked) || {})}`);
|
|
12745
|
+
return -1;
|
|
12746
|
+
}
|
|
12747
|
+
hide_overlay(gobj, key);
|
|
12748
|
+
return 0;
|
|
12749
|
+
}
|
|
12750
|
+
|
|
12751
|
+
/************************************************************
|
|
12752
|
+
* {node_id, port_key, client_x, client_y} -- a press on the
|
|
12753
|
+
* link icon of the selected fkey port, which may be a drag.
|
|
12754
|
+
************************************************************/
|
|
12755
|
+
function ac_start_link_drag(gobj, event, kw, src)
|
|
12756
|
+
{
|
|
12757
|
+
let priv = gobj.priv;
|
|
12758
|
+
|
|
12759
|
+
if(!floating_button_is_current(gobj, event,
|
|
12760
|
+
{node_id: kw && kw.node_id, port_key: kw && kw.port_key})) {
|
|
12761
|
+
return -1; // Error already logged
|
|
12762
|
+
}
|
|
12763
|
+
if(priv._link_drag) {
|
|
12764
|
+
/* The release of the last one never came: it is dropped,
|
|
12765
|
+
* and this press starts over. */
|
|
12766
|
+
log_warning(`${gobj_short_name(gobj)}: ${event} with a link drag still in flight`);
|
|
12767
|
+
end_link_drag(gobj);
|
|
12768
|
+
}
|
|
12769
|
+
start_link_drag(gobj, Number(kw.client_x) || 0, Number(kw.client_y) || 0);
|
|
12770
|
+
return 0;
|
|
12771
|
+
}
|
|
12772
|
+
|
|
12773
|
+
/************************************************************
|
|
12774
|
+
* {client_x, client_y, dragged} -- the pointer of a link drag
|
|
12775
|
+
* went up. Not dragged, it was a click: the click comes next
|
|
12776
|
+
* (EV_ENTER_LINKING_MODE). Dragged, it links to the hook it was
|
|
12777
|
+
* dropped on, or leaves the linking mode.
|
|
12778
|
+
************************************************************/
|
|
12779
|
+
function ac_link_drag_end(gobj, event, kw, src)
|
|
12780
|
+
{
|
|
12781
|
+
let priv = gobj.priv;
|
|
12782
|
+
let drag = priv._link_drag;
|
|
12783
|
+
|
|
12784
|
+
if(!drag) {
|
|
12785
|
+
log_error(`${gobj_short_name(gobj)}: ${event} with no link drag in flight`);
|
|
12786
|
+
return -1;
|
|
12787
|
+
}
|
|
12788
|
+
end_link_drag(gobj);
|
|
12789
|
+
if(!kw || !kw.dragged) {
|
|
12790
|
+
return 0;
|
|
12791
|
+
}
|
|
12792
|
+
|
|
12793
|
+
let cx = Number(kw.client_x) - drag.left;
|
|
12794
|
+
let cy = Number(kw.client_y) - drag.top;
|
|
12795
|
+
let canvasPt = priv.graph.getCanvasByViewport([cx, cy]);
|
|
12796
|
+
let target = find_hook_at_point(gobj, canvasPt[0], canvasPt[1]);
|
|
12797
|
+
|
|
12798
|
+
if(target) {
|
|
12799
|
+
try_complete_link(gobj, target.node_id, target.port_key);
|
|
12800
|
+
} else {
|
|
12801
|
+
exit_linking_mode(gobj);
|
|
12802
|
+
}
|
|
12803
|
+
return 0;
|
|
12804
|
+
}
|
|
12805
|
+
|
|
12806
|
+
/************************************************************
|
|
12807
|
+
* {} -- the browser took the pointer of a link drag: no drop,
|
|
12808
|
+
* and the linking mode goes.
|
|
12809
|
+
************************************************************/
|
|
12810
|
+
function ac_link_drag_cancel(gobj, event, kw, src)
|
|
12811
|
+
{
|
|
12812
|
+
let priv = gobj.priv;
|
|
12813
|
+
|
|
12814
|
+
if(!priv._link_drag) {
|
|
12815
|
+
log_error(`${gobj_short_name(gobj)}: ${event} with no link drag in flight`);
|
|
12816
|
+
return -1;
|
|
12817
|
+
}
|
|
12818
|
+
end_link_drag(gobj);
|
|
12819
|
+
exit_linking_mode(gobj);
|
|
12820
|
+
return 0;
|
|
12821
|
+
}
|
|
12822
|
+
|
|
11977
12823
|
/************************************************************
|
|
11978
12824
|
* {what, id|ids} -- the person said yes in a confirm popover.
|
|
11979
12825
|
*
|
|
@@ -12266,8 +13112,25 @@ function create_gclass(gclass_name)
|
|
|
12266
13112
|
["EV_CONTEXT_MENU_ITEM", ac_context_menu_item, null],
|
|
12267
13113
|
["EV_REQUEST_DELETE_NODE", ac_request_delete_node, null],
|
|
12268
13114
|
["EV_REQUEST_UNLINK_EDGE", ac_request_unlink_edge, null],
|
|
13115
|
+
["EV_TOGGLE_NODE_POPOVER", ac_toggle_node_popover, null],
|
|
13116
|
+
["EV_TOGGLE_EDGE_POPOVER", ac_toggle_edge_popover, null],
|
|
13117
|
+
["EV_TOGGLE_PORT_POPOVER", ac_toggle_port_popover, null],
|
|
13118
|
+
["EV_ENTER_LINKING_MODE", ac_enter_linking_mode, null],
|
|
13119
|
+
["EV_CLOSE_NODE_DETAIL", ac_close_node_detail, null],
|
|
13120
|
+
["EV_TOGGLE_CREATE_POPOVER", ac_toggle_create_popover, null],
|
|
13121
|
+
["EV_CLOSE_CREATE_POPOVER", ac_close_create_popover, null],
|
|
12269
13122
|
["EV_SUBMIT_NEW_NODE", ac_submit_new_node, null],
|
|
13123
|
+
["EV_APPLY_NODE_POPOVER", ac_apply_node_popover, null],
|
|
13124
|
+
["EV_CANCEL_NODE_POPOVER", ac_cancel_node_popover, null],
|
|
13125
|
+
["EV_APPLY_EDGE_POPOVER", ac_apply_edge_popover, null],
|
|
13126
|
+
["EV_CANCEL_EDGE_POPOVER", ac_cancel_edge_popover, null],
|
|
13127
|
+
["EV_APPLY_PORT_POPOVER", ac_apply_port_popover, null],
|
|
13128
|
+
["EV_CANCEL_PORT_POPOVER", ac_cancel_port_popover, null],
|
|
12270
13129
|
["EV_CONFIRMED", ac_confirmed, null],
|
|
13130
|
+
["EV_CANCEL_CONFIRM", ac_cancel_confirm, null],
|
|
13131
|
+
["EV_START_LINK_DRAG", ac_start_link_drag, null],
|
|
13132
|
+
["EV_LINK_DRAG_END", ac_link_drag_end, null],
|
|
13133
|
+
["EV_LINK_DRAG_CANCEL", ac_link_drag_cancel, null],
|
|
12271
13134
|
["EV_CANVAS_CLICK", ac_canvas_click, null],
|
|
12272
13135
|
["EV_NODE_DRAG_END", ac_node_drag_end, null],
|
|
12273
13136
|
["EV_BRUSH_SELECT", ac_brush_select, null],
|
|
@@ -12336,8 +13199,25 @@ function create_gclass(gclass_name)
|
|
|
12336
13199
|
["EV_CONTEXT_MENU_ITEM", 0],
|
|
12337
13200
|
["EV_REQUEST_DELETE_NODE", 0],
|
|
12338
13201
|
["EV_REQUEST_UNLINK_EDGE", 0],
|
|
13202
|
+
["EV_TOGGLE_NODE_POPOVER", 0],
|
|
13203
|
+
["EV_TOGGLE_EDGE_POPOVER", 0],
|
|
13204
|
+
["EV_TOGGLE_PORT_POPOVER", 0],
|
|
13205
|
+
["EV_ENTER_LINKING_MODE", 0],
|
|
13206
|
+
["EV_CLOSE_NODE_DETAIL", 0],
|
|
13207
|
+
["EV_TOGGLE_CREATE_POPOVER", 0],
|
|
13208
|
+
["EV_CLOSE_CREATE_POPOVER", 0],
|
|
12339
13209
|
["EV_SUBMIT_NEW_NODE", 0],
|
|
13210
|
+
["EV_APPLY_NODE_POPOVER", 0],
|
|
13211
|
+
["EV_CANCEL_NODE_POPOVER", 0],
|
|
13212
|
+
["EV_APPLY_EDGE_POPOVER", 0],
|
|
13213
|
+
["EV_CANCEL_EDGE_POPOVER", 0],
|
|
13214
|
+
["EV_APPLY_PORT_POPOVER", 0],
|
|
13215
|
+
["EV_CANCEL_PORT_POPOVER", 0],
|
|
12340
13216
|
["EV_CONFIRMED", 0],
|
|
13217
|
+
["EV_CANCEL_CONFIRM", 0],
|
|
13218
|
+
["EV_START_LINK_DRAG", 0],
|
|
13219
|
+
["EV_LINK_DRAG_END", 0],
|
|
13220
|
+
["EV_LINK_DRAG_CANCEL", 0],
|
|
12341
13221
|
["EV_CANVAS_CLICK", 0],
|
|
12342
13222
|
["EV_NODE_DRAG_END", 0],
|
|
12343
13223
|
["EV_BRUSH_SELECT", 0],
|
|
@@ -12461,4 +13341,7 @@ function register_c_g6_nodes_tree()
|
|
|
12461
13341
|
export {
|
|
12462
13342
|
register_c_g6_nodes_tree,
|
|
12463
13343
|
toolbar_item_html, // exported for its test
|
|
13344
|
+
YuiToolbar, // exported for its test
|
|
13345
|
+
create_floating_icon, // exported for its test
|
|
13346
|
+
create_detail_close, // exported for its test
|
|
12464
13347
|
};
|