@yuneta/gobj-ui 7.23.7 → 7.23.9
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 +53 -0
- package/dist/gobj-ui.cjs.js +588 -112
- package/dist/gobj-ui.es.js +589 -113
- package/package.json +1 -1
- package/src/c_g6_nodes_tree.js +415 -55
- package/src/c_yui_gobj_tree_js.js +2 -0
- package/src/c_yui_json.js +9 -3
- package/src/c_yui_json_graph.js +43 -23
- package/src/c_yui_treedb_graph.js +28 -18
- package/src/g6_drag_canvas_touch.js +21 -0
- package/src/g6_touch_gestures.js +413 -0
- package/src/lib_graph.css +19 -0
- package/src/lib_icons.js +14 -0
package/src/c_g6_nodes_tree.js
CHANGED
|
@@ -103,6 +103,10 @@ import i18next, {t} from "i18next";
|
|
|
103
103
|
|
|
104
104
|
import {inject_svg_icons} from "./lib_icons.js";
|
|
105
105
|
import {ensure_drag_canvas_patch} from "./g6_drag_canvas_touch.js";
|
|
106
|
+
import {
|
|
107
|
+
ensure_pinch_zoom_patch,
|
|
108
|
+
install_long_press_contextmenu,
|
|
109
|
+
} from "./g6_touch_gestures.js";
|
|
106
110
|
import {yui_theme_now, yui_watch_theme} from "./yui_theme.js";
|
|
107
111
|
|
|
108
112
|
/***************************************************************
|
|
@@ -172,6 +176,7 @@ class YuiToolbar extends Toolbar
|
|
|
172
176
|
}
|
|
173
177
|
register(ExtensionCategory.PLUGIN, 'yui-toolbar', YuiToolbar);
|
|
174
178
|
ensure_drag_canvas_patch();
|
|
179
|
+
ensure_pinch_zoom_patch();
|
|
175
180
|
|
|
176
181
|
/***************************************************************
|
|
177
182
|
* Constants
|
|
@@ -336,6 +341,9 @@ let PRIVATE_DATA = {
|
|
|
336
341
|
_focus_topic: null, // topic currently focused (EV_FOCUS_TOPIC)
|
|
337
342
|
_focus_ids: [], // node ids carrying the focus 'active' state
|
|
338
343
|
_on_pointerdown_focus: null, // listener keeping the keyboard on the canvas
|
|
344
|
+
_uninstall_long_press: null, // touch door to the context menu
|
|
345
|
+
toolbar_collapsed: true, // floating toolbars folded (narrow only)
|
|
346
|
+
_toolbars_could_collapse: null, // last answer, to notice it changed
|
|
339
347
|
_on_focusout_restore: null, // ...and putting it back when it goes nowhere
|
|
340
348
|
_selected_paint_ids: [], // node ids whose card is PAINTED selected
|
|
341
349
|
// (G6's 'selected' state is the selection
|
|
@@ -491,6 +499,11 @@ function mt_destroy(gobj)
|
|
|
491
499
|
priv._resize_raf = 0;
|
|
492
500
|
}
|
|
493
501
|
|
|
502
|
+
if(priv._uninstall_long_press) {
|
|
503
|
+
priv._uninstall_long_press();
|
|
504
|
+
priv._uninstall_long_press = null;
|
|
505
|
+
}
|
|
506
|
+
|
|
494
507
|
if(priv.graph) {
|
|
495
508
|
priv.graph.destroy();
|
|
496
509
|
priv.graph = null;
|
|
@@ -943,6 +956,19 @@ function configure_plugins(gobj)
|
|
|
943
956
|
}
|
|
944
957
|
);
|
|
945
958
|
|
|
959
|
+
/*
|
|
960
|
+
* The menu above is the way to link, unlink, delete and resize,
|
|
961
|
+
* and `contextmenu` is a RIGHT CLICK -- G6 synthesises it from
|
|
962
|
+
* `pointerdown` with `button === 2` and reads nothing from the
|
|
963
|
+
* DOM event of the same name. So on a touch screen those
|
|
964
|
+
* commands had no door at all, whatever the browser does with a
|
|
965
|
+
* long press. Give them one.
|
|
966
|
+
*/
|
|
967
|
+
if(priv._uninstall_long_press) {
|
|
968
|
+
priv._uninstall_long_press();
|
|
969
|
+
}
|
|
970
|
+
priv._uninstall_long_press = install_long_press_contextmenu(priv.graph);
|
|
971
|
+
|
|
946
972
|
if(gobj_read_bool_attr(gobj, "with_toolbar")) {
|
|
947
973
|
configure_toolbar(gobj);
|
|
948
974
|
configure_toolbar_edit(gobj);
|
|
@@ -1006,6 +1032,67 @@ function update_toolbar(gobj)
|
|
|
1006
1032
|
* flipping now; the fallbacks are the old forced-light values, so
|
|
1007
1033
|
* a host without Bulma gets exactly what it had.
|
|
1008
1034
|
************************************************************/
|
|
1035
|
+
/************************************************************
|
|
1036
|
+
* Is the canvas too narrow to give both floating toolbars a
|
|
1037
|
+
* strip of it?
|
|
1038
|
+
*
|
|
1039
|
+
* They are drawn INSIDE the canvas, one on each edge. On a
|
|
1040
|
+
* desktop that is a corner; on a 356px-wide phone canvas the
|
|
1041
|
+
* two of them take a third of the drawing area and stand on
|
|
1042
|
+
* top of the nodes -- and a node under a toolbar cannot be
|
|
1043
|
+
* read, tapped or dragged out from under it.
|
|
1044
|
+
*
|
|
1045
|
+
* Measured on the CONTAINER, not on the window: the same
|
|
1046
|
+
* graph is a full page in one app and a card in a column in
|
|
1047
|
+
* another, and it is the box it actually got that decides.
|
|
1048
|
+
************************************************************/
|
|
1049
|
+
const TOOLBAR_COLLAPSE_WIDTH = 480;
|
|
1050
|
+
|
|
1051
|
+
function toolbars_can_collapse(gobj)
|
|
1052
|
+
{
|
|
1053
|
+
let $container = gobj.priv.$container;
|
|
1054
|
+
|
|
1055
|
+
if(!$container) {
|
|
1056
|
+
return false;
|
|
1057
|
+
}
|
|
1058
|
+
let width = $container.getBoundingClientRect().width;
|
|
1059
|
+
if(!width) {
|
|
1060
|
+
return false; /* not laid out yet: decided on the first resize */
|
|
1061
|
+
}
|
|
1062
|
+
return width < TOOLBAR_COLLAPSE_WIDTH;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
/************************************************************
|
|
1066
|
+
* Are they folded RIGHT NOW? Wide enough and the question
|
|
1067
|
+
* does not arise -- the desktop toolbar never grows a button
|
|
1068
|
+
* it does not need.
|
|
1069
|
+
************************************************************/
|
|
1070
|
+
function toolbars_collapsed(gobj)
|
|
1071
|
+
{
|
|
1072
|
+
if(!toolbars_can_collapse(gobj)) {
|
|
1073
|
+
return false;
|
|
1074
|
+
}
|
|
1075
|
+
return gobj.priv.toolbar_collapsed !== false;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
/************************************************************
|
|
1079
|
+
* The one item a folded toolbar shows, and the one an
|
|
1080
|
+
* unfolded narrow toolbar adds to fold itself again.
|
|
1081
|
+
************************************************************/
|
|
1082
|
+
function toolbar_fold_item(gobj)
|
|
1083
|
+
{
|
|
1084
|
+
if(toolbars_collapsed(gobj)) {
|
|
1085
|
+
return {
|
|
1086
|
+
id: 'g6-icon-toolbar-show', value: 'toolbar-toggle',
|
|
1087
|
+
className: 'EV_TOGGLE_TOOLBARS', title: t('show toolbar')
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
return {
|
|
1091
|
+
id: 'g6-icon-toolbar-hide', value: 'toolbar-toggle',
|
|
1092
|
+
className: 'EV_TOGGLE_TOOLBARS', title: t('hide toolbar')
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1009
1096
|
function toolbar_style(extra)
|
|
1010
1097
|
{
|
|
1011
1098
|
return Object.assign({
|
|
@@ -1092,6 +1179,12 @@ function configure_toolbar(gobj)
|
|
|
1092
1179
|
marginLeft: '12px',
|
|
1093
1180
|
}),
|
|
1094
1181
|
getItems: () => {
|
|
1182
|
+
/* Folded: ONE button, and it is the one that
|
|
1183
|
+
* unfolds. Everything else is behind it. */
|
|
1184
|
+
if(toolbars_collapsed(gobj)) {
|
|
1185
|
+
return [toolbar_fold_item(gobj)];
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1095
1188
|
let items = [
|
|
1096
1189
|
{ id: 'g6-icon-zoom-in', value: 'zoom-in', className: 'EV_ZOOM_IN', title: t('zoom in') },
|
|
1097
1190
|
{ id: 'g6-icon-zoom-out', value: 'zoom-out', className: 'EV_ZOOM_OUT', title: t('zoom out') },
|
|
@@ -1143,6 +1236,14 @@ function configure_toolbar(gobj)
|
|
|
1143
1236
|
}
|
|
1144
1237
|
}
|
|
1145
1238
|
|
|
1239
|
+
/* Getting the canvas back is a window control too,
|
|
1240
|
+
* and the last thing in the strip: it is where the
|
|
1241
|
+
* single folded button will be. */
|
|
1242
|
+
if(toolbars_can_collapse(gobj)) {
|
|
1243
|
+
items.push({ separator: true });
|
|
1244
|
+
items.push(toolbar_fold_item(gobj));
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1146
1247
|
return items;
|
|
1147
1248
|
},
|
|
1148
1249
|
onClick: (value) => {
|
|
@@ -1171,6 +1272,9 @@ function configure_toolbar(gobj)
|
|
|
1171
1272
|
case 'exit-fullscreen':
|
|
1172
1273
|
gobj_send_event(gobj, "EV_EXIT_FULLSCREEN", {}, gobj);
|
|
1173
1274
|
break;
|
|
1275
|
+
case 'toolbar-toggle':
|
|
1276
|
+
gobj_send_event(gobj, "EV_TOGGLE_TOOLBARS", {}, gobj);
|
|
1277
|
+
break;
|
|
1174
1278
|
}
|
|
1175
1279
|
},
|
|
1176
1280
|
}
|
|
@@ -1185,7 +1289,11 @@ function configure_toolbar_edit(gobj)
|
|
|
1185
1289
|
{
|
|
1186
1290
|
let priv = gobj.priv;
|
|
1187
1291
|
|
|
1188
|
-
|
|
1292
|
+
/* Folded with the camera strip: the two of them are the same
|
|
1293
|
+
* wall over the canvas, and hiding one is half a fix. Removed
|
|
1294
|
+
* rather than emptied -- an empty toolbar is still a card with
|
|
1295
|
+
* a border sitting on the graph. */
|
|
1296
|
+
if(!priv.edit_mode || toolbars_collapsed(gobj)) {
|
|
1189
1297
|
// Remove edit toolbar when not in edit mode
|
|
1190
1298
|
graph_remove_plugin(gobj, 'toolbar-edit');
|
|
1191
1299
|
return;
|
|
@@ -1343,6 +1451,16 @@ function configure_behaviour(gobj)
|
|
|
1343
1451
|
break;
|
|
1344
1452
|
case "operation":
|
|
1345
1453
|
priv.edit_mode = false;
|
|
1454
|
+
/* The camera is not an edit affordance: a mode that
|
|
1455
|
+
* cannot pan or zoom is a picture, and this one was one
|
|
1456
|
+
* -- `behaviors` was left empty here while `reading` and
|
|
1457
|
+
* `writing` next door both fill it. On a desktop the
|
|
1458
|
+
* toolbar still zooms and nothing pans; on a phone,
|
|
1459
|
+
* where the gestures ARE the camera, the graph froze. */
|
|
1460
|
+
behaviors = [
|
|
1461
|
+
"drag-canvas",
|
|
1462
|
+
"zoom-canvas",
|
|
1463
|
+
];
|
|
1346
1464
|
break;
|
|
1347
1465
|
default:
|
|
1348
1466
|
log_error(`operation mode unknown: ${operation_mode}`);
|
|
@@ -2824,6 +2942,26 @@ const RESIZE_HANDLE_SIZE = 8;
|
|
|
2824
2942
|
const RESIZE_MIN_VP = 20;
|
|
2825
2943
|
const RESIZE_MIN_WORLD = 30;
|
|
2826
2944
|
|
|
2945
|
+
/*
|
|
2946
|
+
* A handle is TWO boxes: the one you see and the one you can
|
|
2947
|
+
* hit. They are the same box for a mouse, and on a touch
|
|
2948
|
+
* screen the hit box grows to a fingertip while the mark stays
|
|
2949
|
+
* a mark -- a 44px white square on each corner would hide the
|
|
2950
|
+
* node it is there to resize.
|
|
2951
|
+
*/
|
|
2952
|
+
const RESIZE_HANDLE_VISUAL_COARSE = 14;
|
|
2953
|
+
const RESIZE_HANDLE_HIT_COARSE = 44;
|
|
2954
|
+
|
|
2955
|
+
function resize_handle_visual()
|
|
2956
|
+
{
|
|
2957
|
+
return coarse_pointer()? RESIZE_HANDLE_VISUAL_COARSE: RESIZE_HANDLE_SIZE;
|
|
2958
|
+
}
|
|
2959
|
+
|
|
2960
|
+
function resize_handle_hit()
|
|
2961
|
+
{
|
|
2962
|
+
return coarse_pointer()? RESIZE_HANDLE_HIT_COARSE: RESIZE_HANDLE_SIZE;
|
|
2963
|
+
}
|
|
2964
|
+
|
|
2827
2965
|
/************************************************************
|
|
2828
2966
|
* SVG icons for floating action buttons (28×28 circles).
|
|
2829
2967
|
* Each value is a raw SVG string sized 18×18 in a 24×24 viewBox.
|
|
@@ -2871,7 +3009,68 @@ const SVG_ICONS = {
|
|
|
2871
3009
|
};
|
|
2872
3010
|
|
|
2873
3011
|
/************************************************************
|
|
2874
|
-
*
|
|
3012
|
+
* Is the primary pointer a FINGER?
|
|
3013
|
+
*
|
|
3014
|
+
* Everything this gclass draws over the canvas is sized from
|
|
3015
|
+
* this answer. A mouse is a pixel and can hit an 8px square;
|
|
3016
|
+
* a fingertip covers about 40, and the overlay it lands on
|
|
3017
|
+
* decides between "open the properties" and "delete the node".
|
|
3018
|
+
*
|
|
3019
|
+
* Asked per call, not cached: a tablet with a keyboard dock
|
|
3020
|
+
* changes the answer without reloading the page.
|
|
3021
|
+
************************************************************/
|
|
3022
|
+
function coarse_pointer()
|
|
3023
|
+
{
|
|
3024
|
+
if(typeof window.matchMedia !== "function") {
|
|
3025
|
+
return false;
|
|
3026
|
+
}
|
|
3027
|
+
try {
|
|
3028
|
+
return window.matchMedia("(pointer: coarse)").matches === true;
|
|
3029
|
+
} catch(e) {
|
|
3030
|
+
return false;
|
|
3031
|
+
}
|
|
3032
|
+
}
|
|
3033
|
+
|
|
3034
|
+
/************************************************************
|
|
3035
|
+
* The size of the round floating buttons (properties,
|
|
3036
|
+
* delete, link), and the GAP between two of them.
|
|
3037
|
+
*
|
|
3038
|
+
* The gap matters as much as the size: `node properties` and
|
|
3039
|
+
* `delete node` are drawn one under the other, and at 28px
|
|
3040
|
+
* with 4px between them a fingertip covers both -- with the
|
|
3041
|
+
* destructive one underneath.
|
|
3042
|
+
************************************************************/
|
|
3043
|
+
function floating_icon_size()
|
|
3044
|
+
{
|
|
3045
|
+
return coarse_pointer()? 44: 28;
|
|
3046
|
+
}
|
|
3047
|
+
|
|
3048
|
+
function floating_icon_gap()
|
|
3049
|
+
{
|
|
3050
|
+
return coarse_pointer()? 12: 4;
|
|
3051
|
+
}
|
|
3052
|
+
|
|
3053
|
+
/* Centre-to-top offset: the anchor names a point, the button
|
|
3054
|
+
* hangs centred on it. */
|
|
3055
|
+
function floating_icon_dy()
|
|
3056
|
+
{
|
|
3057
|
+
return -floating_icon_size() / 2;
|
|
3058
|
+
}
|
|
3059
|
+
|
|
3060
|
+
/* How far below the first button the second one starts. */
|
|
3061
|
+
function floating_icon_step()
|
|
3062
|
+
{
|
|
3063
|
+
return floating_icon_size() + floating_icon_gap();
|
|
3064
|
+
}
|
|
3065
|
+
|
|
3066
|
+
/* Where a popover opens: clear of the button column. */
|
|
3067
|
+
function floating_popover_dx()
|
|
3068
|
+
{
|
|
3069
|
+
return 4 + floating_icon_size() + 4;
|
|
3070
|
+
}
|
|
3071
|
+
|
|
3072
|
+
/************************************************************
|
|
3073
|
+
* Create a floating circular icon button.
|
|
2875
3074
|
* @param {string} svgKey - key into SVG_ICONS
|
|
2876
3075
|
* @param {string} color - border & text color (e.g. '#1890ff')
|
|
2877
3076
|
* @param {number} left - CSS left in px
|
|
@@ -2882,19 +3081,34 @@ const SVG_ICONS = {
|
|
|
2882
3081
|
************************************************************/
|
|
2883
3082
|
function create_floating_icon(svgKey, color, left, top, title, onClick)
|
|
2884
3083
|
{
|
|
3084
|
+
const size = floating_icon_size();
|
|
2885
3085
|
const el = document.createElement('div');
|
|
2886
3086
|
el.title = title;
|
|
3087
|
+
el.setAttribute('aria-label', title);
|
|
2887
3088
|
el.innerHTML = SVG_ICONS[svgKey] || '';
|
|
2888
3089
|
el.style.cssText =
|
|
2889
3090
|
'position:absolute;' +
|
|
2890
3091
|
'left:' + left + 'px;' +
|
|
2891
3092
|
'top:' + top + 'px;' +
|
|
2892
|
-
'width:
|
|
3093
|
+
'width:' + size + 'px;height:' + size + 'px;' +
|
|
2893
3094
|
'display:flex;align-items:center;justify-content:center;' +
|
|
2894
3095
|
'background:#fff;border:1px solid ' + color + ';border-radius:50%;' +
|
|
2895
3096
|
'cursor:pointer;pointer-events:all;z-index:11;' +
|
|
3097
|
+
/* The button is a drag handle in one case (the link icon)
|
|
3098
|
+
* and the browser would claim that gesture as a scroll. */
|
|
3099
|
+
'touch-action:none;' +
|
|
2896
3100
|
'box-shadow:0 2px 6px rgba(0,0,0,0.15);' +
|
|
2897
3101
|
'color:' + color + ';';
|
|
3102
|
+
|
|
3103
|
+
/* The glyphs are authored 18×18; on the bigger button that
|
|
3104
|
+
* leaves a ring of empty white, so scale with the button. */
|
|
3105
|
+
const $svg = el.querySelector('svg');
|
|
3106
|
+
if($svg) {
|
|
3107
|
+
const glyph = Math.round(size * 0.5);
|
|
3108
|
+
$svg.setAttribute('width', String(glyph));
|
|
3109
|
+
$svg.setAttribute('height', String(glyph));
|
|
3110
|
+
}
|
|
3111
|
+
|
|
2898
3112
|
el.addEventListener('click', (e) => {
|
|
2899
3113
|
e.stopPropagation();
|
|
2900
3114
|
onClick();
|
|
@@ -2954,7 +3168,7 @@ function show_dual_icons(gobj, x, y, gear_title, gear_click, trash_title, trash_
|
|
|
2954
3168
|
priv.$container.appendChild(icon_el);
|
|
2955
3169
|
|
|
2956
3170
|
const delete_el = create_floating_icon(
|
|
2957
|
-
'trash', '#ff4d4f', x, y +
|
|
3171
|
+
'trash', '#ff4d4f', x, y + floating_icon_step(), trash_title, trash_click
|
|
2958
3172
|
);
|
|
2959
3173
|
priv.$container.appendChild(delete_el);
|
|
2960
3174
|
|
|
@@ -2987,6 +3201,32 @@ function create_popover_base(left, top, className, borderColor, minWidth)
|
|
|
2987
3201
|
return popover;
|
|
2988
3202
|
}
|
|
2989
3203
|
|
|
3204
|
+
/************************************************************
|
|
3205
|
+
* Extra CSS every control inside a popover carries on a
|
|
3206
|
+
* touch screen.
|
|
3207
|
+
*
|
|
3208
|
+
* A popover is where the graph is TYPED into -- a colour, a
|
|
3209
|
+
* radius, an id -- and its controls were sized by their
|
|
3210
|
+
* padding alone, which lands them at 25-30px. A finger needs
|
|
3211
|
+
* 44, and the number is not ours: it is the floor both Apple
|
|
3212
|
+
* and Google publish, and the one every native control on the
|
|
3213
|
+
* device already meets.
|
|
3214
|
+
************************************************************/
|
|
3215
|
+
function touch_control_css()
|
|
3216
|
+
{
|
|
3217
|
+
if(!coarse_pointer()) {
|
|
3218
|
+
return '';
|
|
3219
|
+
}
|
|
3220
|
+
return 'min-height:44px;font-size:16px;';
|
|
3221
|
+
}
|
|
3222
|
+
|
|
3223
|
+
/*
|
|
3224
|
+
* 16px is not a taste: iOS Safari ZOOMS the whole page when a
|
|
3225
|
+
* text field smaller than that takes focus, and the page never
|
|
3226
|
+
* zooms back -- the graph is left half off screen after typing
|
|
3227
|
+
* one id.
|
|
3228
|
+
*/
|
|
3229
|
+
|
|
2990
3230
|
function create_form_label(parent, text)
|
|
2991
3231
|
{
|
|
2992
3232
|
let label = document.createElement('label');
|
|
@@ -3004,7 +3244,7 @@ function create_form_color_input(parent, value, onInput)
|
|
|
3004
3244
|
input.style.cssText =
|
|
3005
3245
|
'width:100%;height:30px;padding:0;' +
|
|
3006
3246
|
'border:1px solid var(--bulma-border-weak, #d9d9d9);border-radius:4px;' +
|
|
3007
|
-
'cursor:pointer;margin-bottom:10px;';
|
|
3247
|
+
'cursor:pointer;margin-bottom:10px;' + touch_control_css();
|
|
3008
3248
|
if(onInput) {
|
|
3009
3249
|
input.addEventListener('input', onInput);
|
|
3010
3250
|
}
|
|
@@ -3028,7 +3268,7 @@ function create_form_number_input(parent, value, min, max, onInput)
|
|
|
3028
3268
|
'background:var(--bulma-scheme-main-bis, #fff);' +
|
|
3029
3269
|
'color:var(--bulma-text-strong, #333);' +
|
|
3030
3270
|
'border:1px solid var(--bulma-border-weak, #d9d9d9);border-radius:4px;' +
|
|
3031
|
-
'box-sizing:border-box;margin-bottom:10px;';
|
|
3271
|
+
'box-sizing:border-box;margin-bottom:10px;' + touch_control_css();
|
|
3032
3272
|
if(onInput) {
|
|
3033
3273
|
input.addEventListener('input', onInput);
|
|
3034
3274
|
}
|
|
@@ -3044,7 +3284,8 @@ function create_form_select(parent, options, marginBottom)
|
|
|
3044
3284
|
'background:var(--bulma-scheme-main-bis, #fff);' +
|
|
3045
3285
|
'color:var(--bulma-text-strong, #333);' +
|
|
3046
3286
|
'border:1px solid var(--bulma-border-weak, #d9d9d9);border-radius:4px;' +
|
|
3047
|
-
'box-sizing:border-box;margin-bottom:' + (marginBottom || '12px') + ';'
|
|
3287
|
+
'box-sizing:border-box;margin-bottom:' + (marginBottom || '12px') + ';' +
|
|
3288
|
+
touch_control_css();
|
|
3048
3289
|
for(let opt of options) {
|
|
3049
3290
|
let o = document.createElement('option');
|
|
3050
3291
|
o.value = opt.value;
|
|
@@ -3068,7 +3309,7 @@ function create_form_button_row(parent, buttons)
|
|
|
3068
3309
|
for(let {text, style, onClick} of buttons) {
|
|
3069
3310
|
let btn = document.createElement('button');
|
|
3070
3311
|
btn.textContent = t(text);
|
|
3071
|
-
btn.style.cssText = style;
|
|
3312
|
+
btn.style.cssText = style + touch_control_css();
|
|
3072
3313
|
btn.addEventListener('click', (e) => {
|
|
3073
3314
|
e.stopPropagation();
|
|
3074
3315
|
onClick();
|
|
@@ -3448,7 +3689,7 @@ function show_resize_handles(gobj)
|
|
|
3448
3689
|
container.appendChild(selRect);
|
|
3449
3690
|
|
|
3450
3691
|
// 8 resize handles: nw, n, ne, w, e, sw, s, se
|
|
3451
|
-
|
|
3692
|
+
let handle_defs = [
|
|
3452
3693
|
{ cursor: 'nw-resize', mx: -1, my: -1 },
|
|
3453
3694
|
{ cursor: 'n-resize', mx: 0, my: -1 },
|
|
3454
3695
|
{ cursor: 'ne-resize', mx: 1, my: -1 },
|
|
@@ -3459,18 +3700,46 @@ function show_resize_handles(gobj)
|
|
|
3459
3700
|
{ cursor: 'se-resize', mx: 1, my: 1 },
|
|
3460
3701
|
];
|
|
3461
3702
|
|
|
3703
|
+
/*
|
|
3704
|
+
* On a touch screen, the CORNERS only. Eight fingertip-sized
|
|
3705
|
+
* hit boxes around a node 90px wide overlap into one blob, and
|
|
3706
|
+
* the edge handles are the ones that lose -- a corner resizes
|
|
3707
|
+
* both axes anyway, so nothing is out of reach.
|
|
3708
|
+
*/
|
|
3709
|
+
const hit = resize_handle_hit();
|
|
3710
|
+
const visual = resize_handle_visual();
|
|
3711
|
+
if(coarse_pointer()) {
|
|
3712
|
+
handle_defs = handle_defs.filter((def) => def.mx !== 0 && def.my !== 0);
|
|
3713
|
+
}
|
|
3714
|
+
|
|
3462
3715
|
const handles = [];
|
|
3463
3716
|
for(const def of handle_defs) {
|
|
3717
|
+
/* The box that catches the finger: transparent, and never
|
|
3718
|
+
* in front of the mark of a NEIGHBOURING handle. */
|
|
3464
3719
|
const el = document.createElement('div');
|
|
3465
3720
|
el.style.cssText =
|
|
3466
3721
|
'position:absolute;' +
|
|
3467
|
-
'width:' +
|
|
3468
|
-
'height:' +
|
|
3469
|
-
'
|
|
3470
|
-
'border:1px solid #1890ff;' +
|
|
3722
|
+
'width:' + hit + 'px;' +
|
|
3723
|
+
'height:' + hit + 'px;' +
|
|
3724
|
+
'display:flex;align-items:center;justify-content:center;' +
|
|
3471
3725
|
'cursor:' + def.cursor + ';' +
|
|
3472
3726
|
'pointer-events:all;' +
|
|
3727
|
+
/* Without this the browser reads the drag as a scroll
|
|
3728
|
+
* and cancels the pointer mid-resize. */
|
|
3729
|
+
'touch-action:none;' +
|
|
3730
|
+
'box-sizing:border-box;';
|
|
3731
|
+
|
|
3732
|
+
/* The box you see. */
|
|
3733
|
+
const mark = document.createElement('div');
|
|
3734
|
+
mark.style.cssText =
|
|
3735
|
+
'width:' + visual + 'px;' +
|
|
3736
|
+
'height:' + visual + 'px;' +
|
|
3737
|
+
'background:#fff;' +
|
|
3738
|
+
'border:1px solid #1890ff;' +
|
|
3739
|
+
'pointer-events:none;' +
|
|
3473
3740
|
'box-sizing:border-box;';
|
|
3741
|
+
el.appendChild(mark);
|
|
3742
|
+
|
|
3474
3743
|
el.addEventListener('pointerdown', (e) => {
|
|
3475
3744
|
start_node_resize(gobj, e, def.mx, def.my);
|
|
3476
3745
|
});
|
|
@@ -3523,7 +3792,7 @@ function update_resize_handles_position(gobj)
|
|
|
3523
3792
|
function apply_handles_to_rect(gobj, rect)
|
|
3524
3793
|
{
|
|
3525
3794
|
let priv = gobj.priv;
|
|
3526
|
-
const HALF =
|
|
3795
|
+
const HALF = resize_handle_hit() / 2;
|
|
3527
3796
|
|
|
3528
3797
|
const left = rect.left;
|
|
3529
3798
|
const top = rect.top;
|
|
@@ -3544,23 +3813,15 @@ function apply_handles_to_rect(gobj, rect)
|
|
|
3544
3813
|
'box-sizing:border-box;';
|
|
3545
3814
|
}
|
|
3546
3815
|
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
{ x: left + w, y: top + h },
|
|
3557
|
-
];
|
|
3558
|
-
|
|
3559
|
-
for(let i = 0; i < priv._resize_handles.length; i++) {
|
|
3560
|
-
const handle = priv._resize_handles[i];
|
|
3561
|
-
const p = positions[i];
|
|
3562
|
-
handle.el.style.left = (p.x - HALF) + 'px';
|
|
3563
|
-
handle.el.style.top = (p.y - HALF) + 'px';
|
|
3816
|
+
/* Each handle carries the corner it is: -1 / 0 / +1 maps to
|
|
3817
|
+
* the near edge, the middle and the far edge. Read from the
|
|
3818
|
+
* handle instead of a parallel table, because the table is
|
|
3819
|
+
* not the same length on a touch screen. */
|
|
3820
|
+
for(const handle of priv._resize_handles) {
|
|
3821
|
+
const x = left + ((handle.mx + 1) / 2) * w;
|
|
3822
|
+
const y = top + ((handle.my + 1) / 2) * h;
|
|
3823
|
+
handle.el.style.left = (x - HALF) + 'px';
|
|
3824
|
+
handle.el.style.top = (y - HALF) + 'px';
|
|
3564
3825
|
}
|
|
3565
3826
|
}
|
|
3566
3827
|
|
|
@@ -3680,6 +3941,26 @@ function start_node_resize(gobj, e, mx, my)
|
|
|
3680
3941
|
************************************************************/
|
|
3681
3942
|
const PORT_HANDLE_SIZE = 8;
|
|
3682
3943
|
|
|
3944
|
+
/*
|
|
3945
|
+
* Same two-box idea as the node handles. Kept smaller than a
|
|
3946
|
+
* full 44: these four sit on the RIM of a port that is often a
|
|
3947
|
+
* 6px dot, so a fingertip-sized box on each would cover the
|
|
3948
|
+
* neighbouring ports as well -- and picking a port is how a
|
|
3949
|
+
* link is started.
|
|
3950
|
+
*/
|
|
3951
|
+
const PORT_HANDLE_VISUAL_COARSE = 12;
|
|
3952
|
+
const PORT_HANDLE_HIT_COARSE = 32;
|
|
3953
|
+
|
|
3954
|
+
function port_handle_visual()
|
|
3955
|
+
{
|
|
3956
|
+
return coarse_pointer()? PORT_HANDLE_VISUAL_COARSE: PORT_HANDLE_SIZE;
|
|
3957
|
+
}
|
|
3958
|
+
|
|
3959
|
+
function port_handle_hit()
|
|
3960
|
+
{
|
|
3961
|
+
return coarse_pointer()? PORT_HANDLE_HIT_COARSE: PORT_HANDLE_SIZE;
|
|
3962
|
+
}
|
|
3963
|
+
|
|
3683
3964
|
/************************************************************
|
|
3684
3965
|
* Compute the canvas (world) position of a port on a node.
|
|
3685
3966
|
* Returns {x, y} in world coordinates.
|
|
@@ -3731,6 +4012,28 @@ function get_port_radius(gobj, node_id, port_key)
|
|
|
3731
4012
|
return style.portR || 6;
|
|
3732
4013
|
}
|
|
3733
4014
|
|
|
4015
|
+
/************************************************************
|
|
4016
|
+
* How much wider than the port itself its hit area is,
|
|
4017
|
+
* measured on the SCREEN and converted to world units.
|
|
4018
|
+
*
|
|
4019
|
+
* It used to be a flat `+4` in WORLD units, which is a
|
|
4020
|
+
* different target on every zoom: at the 50% a phone lands on
|
|
4021
|
+
* after fit, a 6-unit port plus 4 is a 5px radius -- one
|
|
4022
|
+
* fifth of a fingertip, and picking a port is the first step
|
|
4023
|
+
* of every link.
|
|
4024
|
+
************************************************************/
|
|
4025
|
+
function port_hit_slop_world(graph)
|
|
4026
|
+
{
|
|
4027
|
+
let slop = coarse_pointer()? 22: 6;
|
|
4028
|
+
let zoom = 1;
|
|
4029
|
+
try {
|
|
4030
|
+
zoom = graph.getZoom() || 1;
|
|
4031
|
+
} catch(e) {
|
|
4032
|
+
zoom = 1;
|
|
4033
|
+
}
|
|
4034
|
+
return slop / zoom;
|
|
4035
|
+
}
|
|
4036
|
+
|
|
3734
4037
|
/************************************************************
|
|
3735
4038
|
* Detect if a click in canvas coordinates hits a port.
|
|
3736
4039
|
* Returns the port key string, or null if no port hit.
|
|
@@ -3749,6 +4052,7 @@ function detect_port_click(gobj, node_id, canvasX, canvasY)
|
|
|
3749
4052
|
|
|
3750
4053
|
let best_key = null;
|
|
3751
4054
|
let best_dist = Infinity;
|
|
4055
|
+
let slop = port_hit_slop_world(graph);
|
|
3752
4056
|
|
|
3753
4057
|
for(let i = 0; i < ports.length; i++) {
|
|
3754
4058
|
let pl = ports[i].placement || [0.5, 0.5];
|
|
@@ -3760,8 +4064,9 @@ function detect_port_click(gobj, node_id, canvasX, canvasY)
|
|
|
3760
4064
|
let dy = canvasY - py;
|
|
3761
4065
|
let dist = Math.sqrt(dx * dx + dy * dy);
|
|
3762
4066
|
|
|
3763
|
-
|
|
3764
|
-
|
|
4067
|
+
/* Nearest wins, so the enlarged areas of two ports
|
|
4068
|
+
* overlapping is not a tie -- it is the closer one. */
|
|
4069
|
+
if(dist <= r + slop && dist < best_dist) {
|
|
3765
4070
|
best_dist = dist;
|
|
3766
4071
|
best_key = ports[i].key;
|
|
3767
4072
|
}
|
|
@@ -3838,7 +4143,8 @@ function show_link_icon_if_fkey(gobj)
|
|
|
3838
4143
|
let vp = graph.getViewportByCanvas([canvasPos.x, canvasPos.y]);
|
|
3839
4144
|
|
|
3840
4145
|
let icon = create_floating_icon(
|
|
3841
|
-
'link', '#52c41a',
|
|
4146
|
+
'link', '#52c41a',
|
|
4147
|
+
vp[0] + floating_icon_size() / 2 + 4, vp[1] + floating_icon_dy(),
|
|
3842
4148
|
t('link to hook'), () => {
|
|
3843
4149
|
// Click (no drag) toggles linking mode
|
|
3844
4150
|
if(!priv._linking_mode) {
|
|
@@ -3877,8 +4183,9 @@ function update_link_icon_position(gobj)
|
|
|
3877
4183
|
return;
|
|
3878
4184
|
}
|
|
3879
4185
|
let vp = graph.getViewportByCanvas([canvasPos.x, canvasPos.y]);
|
|
3880
|
-
priv._link_icon_el.style.left =
|
|
3881
|
-
|
|
4186
|
+
priv._link_icon_el.style.left =
|
|
4187
|
+
(vp[0] + floating_icon_size() / 2 + 4) + 'px';
|
|
4188
|
+
priv._link_icon_el.style.top = (vp[1] + floating_icon_dy()) + 'px';
|
|
3882
4189
|
} catch(e) {
|
|
3883
4190
|
hide_link_icon(gobj);
|
|
3884
4191
|
}
|
|
@@ -4262,19 +4569,33 @@ function show_port_resize_handles(gobj)
|
|
|
4262
4569
|
{ cursor: 'w-resize', dx: -1, dy: 0 },
|
|
4263
4570
|
];
|
|
4264
4571
|
|
|
4572
|
+
const hit = port_handle_hit();
|
|
4573
|
+
const visual = port_handle_visual();
|
|
4574
|
+
|
|
4265
4575
|
const handles = [];
|
|
4266
4576
|
for(const def of handle_defs) {
|
|
4267
4577
|
const el = document.createElement('div');
|
|
4268
4578
|
el.style.cssText =
|
|
4269
4579
|
'position:absolute;' +
|
|
4270
|
-
'width:' +
|
|
4271
|
-
'height:' +
|
|
4580
|
+
'width:' + hit + 'px;' +
|
|
4581
|
+
'height:' + hit + 'px;' +
|
|
4582
|
+
'display:flex;align-items:center;justify-content:center;' +
|
|
4583
|
+
'cursor:' + def.cursor + ';' +
|
|
4584
|
+
'pointer-events:all;' +
|
|
4585
|
+
'touch-action:none;' +
|
|
4586
|
+
'box-sizing:border-box;';
|
|
4587
|
+
|
|
4588
|
+
const mark = document.createElement('div');
|
|
4589
|
+
mark.style.cssText =
|
|
4590
|
+
'width:' + visual + 'px;' +
|
|
4591
|
+
'height:' + visual + 'px;' +
|
|
4272
4592
|
'background:#fff;' +
|
|
4273
4593
|
'border:1px solid #fa8c16;' +
|
|
4274
4594
|
'border-radius:50%;' +
|
|
4275
|
-
'
|
|
4276
|
-
'pointer-events:all;' +
|
|
4595
|
+
'pointer-events:none;' +
|
|
4277
4596
|
'box-sizing:border-box;';
|
|
4597
|
+
el.appendChild(mark);
|
|
4598
|
+
|
|
4278
4599
|
el.addEventListener('pointerdown', (e) => {
|
|
4279
4600
|
start_port_resize(gobj, e);
|
|
4280
4601
|
});
|
|
@@ -4342,7 +4663,7 @@ function update_port_resize_handles_position(gobj)
|
|
|
4342
4663
|
function apply_port_handles(gobj, cx, cy, vpR)
|
|
4343
4664
|
{
|
|
4344
4665
|
let priv = gobj.priv;
|
|
4345
|
-
const HALF =
|
|
4666
|
+
const HALF = port_handle_hit() / 2;
|
|
4346
4667
|
|
|
4347
4668
|
// Dashed ring
|
|
4348
4669
|
const ring = priv._port_ring;
|
|
@@ -4534,7 +4855,7 @@ function show_edge_icon(gobj)
|
|
|
4534
4855
|
return;
|
|
4535
4856
|
}
|
|
4536
4857
|
|
|
4537
|
-
let icons = show_dual_icons(gobj, mid.x + 4, mid.y
|
|
4858
|
+
let icons = show_dual_icons(gobj, mid.x + 4, mid.y + floating_icon_dy(),
|
|
4538
4859
|
t('edge properties'), () => toggle_edge_popover(gobj),
|
|
4539
4860
|
t('unlink'), () => request_unlink_edge(gobj)
|
|
4540
4861
|
);
|
|
@@ -4562,16 +4883,18 @@ function update_edge_icon_position(gobj)
|
|
|
4562
4883
|
return;
|
|
4563
4884
|
}
|
|
4564
4885
|
priv._edge_icon_el.style.left = (mid.x + 4) + 'px';
|
|
4565
|
-
priv._edge_icon_el.style.top = (mid.y
|
|
4886
|
+
priv._edge_icon_el.style.top = (mid.y + floating_icon_dy()) + 'px';
|
|
4566
4887
|
if(priv._edge_delete_el) {
|
|
4567
4888
|
priv._edge_delete_el.style.left = (mid.x + 4) + 'px';
|
|
4568
|
-
priv._edge_delete_el.style.top =
|
|
4889
|
+
priv._edge_delete_el.style.top =
|
|
4890
|
+
(mid.y + floating_icon_dy() + floating_icon_step()) + 'px';
|
|
4569
4891
|
}
|
|
4570
4892
|
|
|
4571
4893
|
// Reposition popover if open
|
|
4572
4894
|
if(priv._edge_popover_el) {
|
|
4573
|
-
priv._edge_popover_el.style.left =
|
|
4574
|
-
|
|
4895
|
+
priv._edge_popover_el.style.left =
|
|
4896
|
+
(mid.x + floating_popover_dx()) + 'px';
|
|
4897
|
+
priv._edge_popover_el.style.top = (mid.y + floating_icon_dy()) + 'px';
|
|
4575
4898
|
clamp_popover_position(gobj, priv._edge_popover_el);
|
|
4576
4899
|
}
|
|
4577
4900
|
} catch(e) {
|
|
@@ -4631,7 +4954,10 @@ function show_edge_popover(gobj)
|
|
|
4631
4954
|
return;
|
|
4632
4955
|
}
|
|
4633
4956
|
|
|
4634
|
-
const popover = create_popover_base(
|
|
4957
|
+
const popover = create_popover_base(
|
|
4958
|
+
mid.x + floating_popover_dx(), mid.y + floating_icon_dy(),
|
|
4959
|
+
'g6-edge-popover', '#d9d9d9', 180
|
|
4960
|
+
);
|
|
4635
4961
|
|
|
4636
4962
|
// Live preview: update the selected edge in real time
|
|
4637
4963
|
function preview_edge() {
|
|
@@ -4747,7 +5073,7 @@ function show_node_icon(gobj)
|
|
|
4747
5073
|
return;
|
|
4748
5074
|
}
|
|
4749
5075
|
|
|
4750
|
-
let icons = show_dual_icons(gobj, rect.right + 4, rect.top
|
|
5076
|
+
let icons = show_dual_icons(gobj, rect.right + 4, rect.top + floating_icon_dy(),
|
|
4751
5077
|
t('node properties'), () => toggle_node_popover(gobj),
|
|
4752
5078
|
t('delete node'), () => request_delete_node(gobj)
|
|
4753
5079
|
);
|
|
@@ -4781,16 +5107,18 @@ function update_node_icon_position(gobj)
|
|
|
4781
5107
|
return;
|
|
4782
5108
|
}
|
|
4783
5109
|
priv._node_icon_el.style.left = (rect.right + 4) + 'px';
|
|
4784
|
-
priv._node_icon_el.style.top = (rect.top
|
|
5110
|
+
priv._node_icon_el.style.top = (rect.top + floating_icon_dy()) + 'px';
|
|
4785
5111
|
if(priv._node_delete_el) {
|
|
4786
5112
|
priv._node_delete_el.style.left = (rect.right + 4) + 'px';
|
|
4787
|
-
priv._node_delete_el.style.top =
|
|
5113
|
+
priv._node_delete_el.style.top =
|
|
5114
|
+
(rect.top + floating_icon_dy() + floating_icon_step()) + 'px';
|
|
4788
5115
|
}
|
|
4789
5116
|
|
|
4790
5117
|
// Reposition popover if open
|
|
4791
5118
|
if(priv._node_popover_el) {
|
|
4792
|
-
priv._node_popover_el.style.left =
|
|
4793
|
-
|
|
5119
|
+
priv._node_popover_el.style.left =
|
|
5120
|
+
(rect.right + floating_popover_dx()) + 'px';
|
|
5121
|
+
priv._node_popover_el.style.top = (rect.top + floating_icon_dy()) + 'px';
|
|
4794
5122
|
clamp_popover_position(gobj, priv._node_popover_el);
|
|
4795
5123
|
}
|
|
4796
5124
|
} catch(e) {
|
|
@@ -4843,7 +5171,10 @@ function show_node_popover(gobj)
|
|
|
4843
5171
|
return;
|
|
4844
5172
|
}
|
|
4845
5173
|
|
|
4846
|
-
const popover = create_popover_base(
|
|
5174
|
+
const popover = create_popover_base(
|
|
5175
|
+
rect.right + floating_popover_dx(), rect.top + floating_icon_dy(),
|
|
5176
|
+
'g6-node-popover', '#d9d9d9', 180
|
|
5177
|
+
);
|
|
4847
5178
|
|
|
4848
5179
|
let node_graph_type = nodeData.data && nodeData.data.desc ?
|
|
4849
5180
|
nodeData.data.desc.node_treedb_type : null;
|
|
@@ -5831,7 +6162,7 @@ function show_create_popover(gobj)
|
|
|
5831
6162
|
let topicSelect = document.createElement('select');
|
|
5832
6163
|
topicSelect.style.cssText =
|
|
5833
6164
|
'width:100%;padding:6px;border:1px solid #d9d9d9;border-radius:4px;' +
|
|
5834
|
-
'font-size:13px;margin-bottom:8px;';
|
|
6165
|
+
'font-size:13px;margin-bottom:8px;' + touch_control_css();
|
|
5835
6166
|
for(let i = 0; i < topics.length; i++) {
|
|
5836
6167
|
let opt = document.createElement('option');
|
|
5837
6168
|
opt.value = topics[i].topic_name;
|
|
@@ -5874,7 +6205,8 @@ function show_create_popover(gobj)
|
|
|
5874
6205
|
idInput.placeholder = t('node id');
|
|
5875
6206
|
idInput.style.cssText =
|
|
5876
6207
|
'width:100%;padding:6px;border:1px solid #d9d9d9;border-radius:4px;' +
|
|
5877
|
-
'font-size:13px;margin-bottom:10px;box-sizing:border-box;'
|
|
6208
|
+
'font-size:13px;margin-bottom:10px;box-sizing:border-box;' +
|
|
6209
|
+
touch_control_css();
|
|
5878
6210
|
popover.appendChild(idInput);
|
|
5879
6211
|
|
|
5880
6212
|
// Error message area
|
|
@@ -6960,6 +7292,16 @@ function ac_resize(gobj, event, kw, src)
|
|
|
6960
7292
|
}
|
|
6961
7293
|
}
|
|
6962
7294
|
|
|
7295
|
+
/* Crossing the width where the toolbars start folding changes
|
|
7296
|
+
* what they contain -- a fold button appears or goes away --
|
|
7297
|
+
* and nothing else would ever rebuild them. Only on the
|
|
7298
|
+
* CROSSING: a resize fires on every frame of a drag. */
|
|
7299
|
+
let can_collapse = toolbars_can_collapse(gobj);
|
|
7300
|
+
if(can_collapse !== priv._toolbars_could_collapse) {
|
|
7301
|
+
priv._toolbars_could_collapse = can_collapse;
|
|
7302
|
+
update_toolbar(gobj);
|
|
7303
|
+
}
|
|
7304
|
+
|
|
6963
7305
|
return 0;
|
|
6964
7306
|
}
|
|
6965
7307
|
|
|
@@ -7018,6 +7360,22 @@ function ac_zoom_reset(gobj, event, kw, src)
|
|
|
7018
7360
|
return 0;
|
|
7019
7361
|
}
|
|
7020
7362
|
|
|
7363
|
+
/************************************************************
|
|
7364
|
+
* Fold / unfold the floating toolbars.
|
|
7365
|
+
*
|
|
7366
|
+
* One flag for BOTH: they are one wall over the canvas, and
|
|
7367
|
+
* the button that unfolds them has to be the same button
|
|
7368
|
+
* whichever edge the user reaches for.
|
|
7369
|
+
************************************************************/
|
|
7370
|
+
function ac_toggle_toolbars(gobj, event, kw, src)
|
|
7371
|
+
{
|
|
7372
|
+
let priv = gobj.priv;
|
|
7373
|
+
|
|
7374
|
+
priv.toolbar_collapsed = !toolbars_collapsed(gobj);
|
|
7375
|
+
update_toolbar(gobj);
|
|
7376
|
+
return 0;
|
|
7377
|
+
}
|
|
7378
|
+
|
|
7021
7379
|
function ac_auto_fit(gobj, event, kw, src)
|
|
7022
7380
|
{
|
|
7023
7381
|
graph_fitview(gobj);
|
|
@@ -7506,6 +7864,7 @@ function create_gclass(gclass_name)
|
|
|
7506
7864
|
["EV_ZOOM_IN", ac_zoom_in, null],
|
|
7507
7865
|
["EV_ZOOM_OUT", ac_zoom_out, null],
|
|
7508
7866
|
["EV_ZOOM_RESET", ac_zoom_reset, null],
|
|
7867
|
+
["EV_TOGGLE_TOOLBARS", ac_toggle_toolbars, null],
|
|
7509
7868
|
["EV_AUTO_FIT", ac_auto_fit, null],
|
|
7510
7869
|
["EV_ZOOM_SELECTION", ac_zoom_selection, null],
|
|
7511
7870
|
["EV_FOCUS_TOPIC", ac_focus_topic, null],
|
|
@@ -7553,6 +7912,7 @@ function create_gclass(gclass_name)
|
|
|
7553
7912
|
["EV_ZOOM_IN", 0],
|
|
7554
7913
|
["EV_ZOOM_OUT", 0],
|
|
7555
7914
|
["EV_ZOOM_RESET", 0],
|
|
7915
|
+
["EV_TOGGLE_TOOLBARS", 0],
|
|
7556
7916
|
["EV_AUTO_FIT", 0],
|
|
7557
7917
|
["EV_ZOOM_SELECTION", 0],
|
|
7558
7918
|
["EV_FOCUS_TOPIC", 0],
|