author | Edouard Tisserant |
Thu, 17 Dec 2020 19:31:00 +0100 | |
branch | svghmi |
changeset 3092 | 96ffd8b1b016 |
parent 3047 | c113904f0e62 |
child 3102 | abb487b56911 |
permissions | -rw-r--r-- |
2908 | 1 |
// widget_keypad.ysl2 |
2 |
||
2943
304e88bae115
SVGHMI: added more meaningful namespaces to emit javascript code from.
Edouard Tisserant <edouard.tisserant@gmail.com>
parents:
2941
diff
changeset
|
3 |
emit "declarations:keypad" { |
2941
ef13a4007538
SVGHMI: spread JS code from svghmi/scripts.ysl2 in other .ysl2 files, using dedicated preamble and epilogue namespaces
Edouard Tisserant <edouard.tisserant@gmail.com>
parents:
2920
diff
changeset
|
4 |
| |
ef13a4007538
SVGHMI: spread JS code from svghmi/scripts.ysl2 in other .ysl2 files, using dedicated preamble and epilogue namespaces
Edouard Tisserant <edouard.tisserant@gmail.com>
parents:
2920
diff
changeset
|
5 |
| var keypads = { |
ef13a4007538
SVGHMI: spread JS code from svghmi/scripts.ysl2 in other .ysl2 files, using dedicated preamble and epilogue namespaces
Edouard Tisserant <edouard.tisserant@gmail.com>
parents:
2920
diff
changeset
|
6 |
foreach "$keypads_descs"{ |
ef13a4007538
SVGHMI: spread JS code from svghmi/scripts.ysl2 in other .ysl2 files, using dedicated preamble and epilogue namespaces
Edouard Tisserant <edouard.tisserant@gmail.com>
parents:
2920
diff
changeset
|
7 |
const "keypad_id","@id"; |
ef13a4007538
SVGHMI: spread JS code from svghmi/scripts.ysl2 in other .ysl2 files, using dedicated preamble and epilogue namespaces
Edouard Tisserant <edouard.tisserant@gmail.com>
parents:
2920
diff
changeset
|
8 |
foreach "arg"{ |
ef13a4007538
SVGHMI: spread JS code from svghmi/scripts.ysl2 in other .ysl2 files, using dedicated preamble and epilogue namespaces
Edouard Tisserant <edouard.tisserant@gmail.com>
parents:
2920
diff
changeset
|
9 |
const "g", "$geometry[@Id = $keypad_id]"; |
ef13a4007538
SVGHMI: spread JS code from svghmi/scripts.ysl2 in other .ysl2 files, using dedicated preamble and epilogue namespaces
Edouard Tisserant <edouard.tisserant@gmail.com>
parents:
2920
diff
changeset
|
10 |
| "«@value»":["«$keypad_id»", «$g/@x», «$g/@y»], |
ef13a4007538
SVGHMI: spread JS code from svghmi/scripts.ysl2 in other .ysl2 files, using dedicated preamble and epilogue namespaces
Edouard Tisserant <edouard.tisserant@gmail.com>
parents:
2920
diff
changeset
|
11 |
} |
ef13a4007538
SVGHMI: spread JS code from svghmi/scripts.ysl2 in other .ysl2 files, using dedicated preamble and epilogue namespaces
Edouard Tisserant <edouard.tisserant@gmail.com>
parents:
2920
diff
changeset
|
12 |
} |
ef13a4007538
SVGHMI: spread JS code from svghmi/scripts.ysl2 in other .ysl2 files, using dedicated preamble and epilogue namespaces
Edouard Tisserant <edouard.tisserant@gmail.com>
parents:
2920
diff
changeset
|
13 |
| } |
ef13a4007538
SVGHMI: spread JS code from svghmi/scripts.ysl2 in other .ysl2 files, using dedicated preamble and epilogue namespaces
Edouard Tisserant <edouard.tisserant@gmail.com>
parents:
2920
diff
changeset
|
14 |
} |
ef13a4007538
SVGHMI: spread JS code from svghmi/scripts.ysl2 in other .ysl2 files, using dedicated preamble and epilogue namespaces
Edouard Tisserant <edouard.tisserant@gmail.com>
parents:
2920
diff
changeset
|
15 |
|
3010 | 16 |
template "widget[@type='Keypad']", mode="widget_class" |
17 |
|| |
|
18 |
class KeypadWidget extends Widget{ |
|
19 |
moving = undefined; |
|
3045
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
20 |
click = undefined; |
3010 | 21 |
offset = undefined; |
22 |
||
23 |
on_position_click(evt) { |
|
24 |
this.moving = true; |
|
3045
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
25 |
|
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
26 |
// chatch window events |
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
27 |
window.addEventListener("touchmove", this.bound_on_drag, true); |
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
28 |
window.addEventListener("mousemove", this.bound_on_drag, true); |
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
29 |
|
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
30 |
window.addEventListener("mouseup", this.bound_on_release, true) |
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
31 |
window.addEventListener("touchend", this.bound_on_release, true); |
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
32 |
window.addEventListener("touchcancel", this.bound_on_release, true); |
3010 | 33 |
|
34 |
// get click position offset from widget x,y and save it to variable |
|
35 |
var keypad_borders = this.position_elt.getBoundingClientRect(); |
|
36 |
var clickX = undefined; |
|
37 |
var clickY = undefined; |
|
38 |
if (evt.type == "touchstart"){ |
|
39 |
clickX = Math.ceil(evt.touches[0].clientX); |
|
40 |
clickY = Math.ceil(evt.touches[0].clientY); |
|
41 |
} |
|
42 |
else{ |
|
43 |
clickX = evt.pageX; |
|
44 |
clickY = evt.pageY; |
|
45 |
} |
|
46 |
this.offset=[clickX-keypad_borders.left,clickY-keypad_borders.top] |
|
47 |
} |
|
48 |
||
3045
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
49 |
on_release(evt) { |
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
50 |
//relase binds |
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
51 |
window.removeEventListener("touchmove", this.bound_on_drag, true); |
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
52 |
window.removeEventListener("mousemove", this.bound_on_drag, true); |
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
53 |
|
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
54 |
window.removeEventListener("mouseup", this.bound_on_release, true) |
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
55 |
window.removeEventListener("touchend", this.bound_on_release, true); |
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
56 |
window.removeEventListener("touchcancel", this.bound_on_release, true); |
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
57 |
|
3010 | 58 |
if(this.moving) |
59 |
this.moving = false; |
|
60 |
} |
|
61 |
||
3045
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
62 |
on_drag(evt) { |
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
63 |
if(this.moving) |
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
64 |
//get mouse coordinates |
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
65 |
var clickX = undefined; |
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
66 |
var clickY = undefined; |
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
67 |
if (evt.type == "touchmove"){ |
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
68 |
clickX = Math.ceil(evt.touches[0].clientX); |
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
69 |
clickY = Math.ceil(evt.touches[0].clientY); |
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
70 |
} |
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
71 |
else{ |
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
72 |
clickX = evt.pageX; |
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
73 |
clickY = evt.pageY; |
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
74 |
} |
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
75 |
this.click = [clickX,clickY] |
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
76 |
|
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
77 |
//requeset redraw |
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
78 |
this.request_animate(); |
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
79 |
} |
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
80 |
|
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
81 |
animate(){ |
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
82 |
//get keyboard pos in html |
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
83 |
let [eltid, tmpgrp] = current_modal; |
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
84 |
let [xcoord,ycoord] = this.coordinates; |
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
85 |
let [clickX,clickY] = this.click; |
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
86 |
let [xdest,ydest,svgWidth,svgHeight] = page_desc[current_visible_page].bbox; |
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
87 |
|
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
88 |
//translate keyboard position |
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
89 |
let mouseX = ((clickX-this.offset[0])/window.innerWidth)*svgWidth; |
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
90 |
let mouseY = ((clickY-this.offset[1])/window.innerHeight)*svgHeight; |
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
91 |
tmpgrp.setAttribute("transform","translate("+String(xdest-xcoord+mouseX)+","+String(ydest-ycoord+mouseY)+")"); |
3010 | 92 |
} |
93 |
||
94 |
on_key_click(symbols) { |
|
95 |
var syms = symbols.split(" "); |
|
96 |
this.shift |= this.caps; |
|
97 |
this.editstr += syms[this.shift?syms.length-1:0]; |
|
98 |
this.shift = false; |
|
99 |
this.update(); |
|
100 |
} |
|
101 |
||
102 |
on_Esc_click() { |
|
103 |
end_modal.call(this); |
|
104 |
} |
|
105 |
||
106 |
on_Enter_click() { |
|
3033
52f6548982d4
SVGHMI: Keypad is now keeping Javascript type constency. In other words, if a number was given as initial value, input value will have to convert to number in order to be valid. In case invalid value is entered, initial value is restored.
Edouard Tisserant
parents:
3010
diff
changeset
|
107 |
let coercedval = (typeof this.initial) == "number" ? Number(this.editstr) : this.editstr; |
3042
ed43facc7137
SVGHMI: Fix keypad : non-number input was always considered invalid because of missing type checking.
Edouard Tisserant
parents:
3033
diff
changeset
|
108 |
if(typeof coercedval == 'number' && isNaN(coercedval)){ |
ed43facc7137
SVGHMI: Fix keypad : non-number input was always considered invalid because of missing type checking.
Edouard Tisserant
parents:
3033
diff
changeset
|
109 |
// revert to initial so it explicitely shows input was ignored |
3033
52f6548982d4
SVGHMI: Keypad is now keeping Javascript type constency. In other words, if a number was given as initial value, input value will have to convert to number in order to be valid. In case invalid value is entered, initial value is restored.
Edouard Tisserant
parents:
3010
diff
changeset
|
110 |
this.editstr = String(this.initial); |
52f6548982d4
SVGHMI: Keypad is now keeping Javascript type constency. In other words, if a number was given as initial value, input value will have to convert to number in order to be valid. In case invalid value is entered, initial value is restored.
Edouard Tisserant
parents:
3010
diff
changeset
|
111 |
this.update(); |
3042
ed43facc7137
SVGHMI: Fix keypad : non-number input was always considered invalid because of missing type checking.
Edouard Tisserant
parents:
3033
diff
changeset
|
112 |
} else { |
3033
52f6548982d4
SVGHMI: Keypad is now keeping Javascript type constency. In other words, if a number was given as initial value, input value will have to convert to number in order to be valid. In case invalid value is entered, initial value is restored.
Edouard Tisserant
parents:
3010
diff
changeset
|
113 |
let callback_obj = this.result_callback_obj; |
52f6548982d4
SVGHMI: Keypad is now keeping Javascript type constency. In other words, if a number was given as initial value, input value will have to convert to number in order to be valid. In case invalid value is entered, initial value is restored.
Edouard Tisserant
parents:
3010
diff
changeset
|
114 |
end_modal.call(this); |
52f6548982d4
SVGHMI: Keypad is now keeping Javascript type constency. In other words, if a number was given as initial value, input value will have to convert to number in order to be valid. In case invalid value is entered, initial value is restored.
Edouard Tisserant
parents:
3010
diff
changeset
|
115 |
callback_obj.edit_callback(coercedval); |
52f6548982d4
SVGHMI: Keypad is now keeping Javascript type constency. In other words, if a number was given as initial value, input value will have to convert to number in order to be valid. In case invalid value is entered, initial value is restored.
Edouard Tisserant
parents:
3010
diff
changeset
|
116 |
} |
3010 | 117 |
} |
118 |
||
119 |
on_BackSpace_click() { |
|
120 |
this.editstr = this.editstr.slice(0,this.editstr.length-1); |
|
121 |
this.update(); |
|
122 |
} |
|
123 |
||
124 |
on_Sign_click() { |
|
125 |
if(this.editstr[0] == "-") |
|
126 |
this.editstr = this.editstr.slice(1,this.editstr.length); |
|
127 |
else |
|
128 |
this.editstr = "-" + this.editstr; |
|
129 |
this.update(); |
|
130 |
} |
|
131 |
||
132 |
on_NumDot_click() { |
|
133 |
if(this.editstr.indexOf(".") == "-1"){ |
|
134 |
this.editstr += "."; |
|
135 |
this.update(); |
|
136 |
} |
|
137 |
} |
|
138 |
||
139 |
on_Space_click() { |
|
140 |
this.editstr += " "; |
|
141 |
this.update(); |
|
142 |
} |
|
143 |
||
144 |
caps = false; |
|
145 |
_caps = undefined; |
|
146 |
on_CapsLock_click() { |
|
147 |
this.caps = !this.caps; |
|
148 |
this.update(); |
|
149 |
} |
|
150 |
||
151 |
shift = false; |
|
152 |
_shift = undefined; |
|
153 |
on_Shift_click() { |
|
154 |
this.shift = !this.shift; |
|
155 |
this.caps = false; |
|
156 |
this.update(); |
|
157 |
} |
|
158 |
editstr = ""; |
|
159 |
_editstr = undefined; |
|
160 |
result_callback_obj = undefined; |
|
161 |
start_edit(info, valuetype, callback_obj, initial,size) { |
|
162 |
show_modal.call(this,size); |
|
3033
52f6548982d4
SVGHMI: Keypad is now keeping Javascript type constency. In other words, if a number was given as initial value, input value will have to convert to number in order to be valid. In case invalid value is entered, initial value is restored.
Edouard Tisserant
parents:
3010
diff
changeset
|
163 |
this.editstr = String(initial); |
3010 | 164 |
this.result_callback_obj = callback_obj; |
165 |
this.Info_elt.textContent = info; |
|
166 |
this.shift = false; |
|
167 |
this.caps = false; |
|
3033
52f6548982d4
SVGHMI: Keypad is now keeping Javascript type constency. In other words, if a number was given as initial value, input value will have to convert to number in order to be valid. In case invalid value is entered, initial value is restored.
Edouard Tisserant
parents:
3010
diff
changeset
|
168 |
this.initial = initial; |
52f6548982d4
SVGHMI: Keypad is now keeping Javascript type constency. In other words, if a number was given as initial value, input value will have to convert to number in order to be valid. In case invalid value is entered, initial value is restored.
Edouard Tisserant
parents:
3010
diff
changeset
|
169 |
|
3010 | 170 |
this.update(); |
171 |
} |
|
172 |
||
173 |
update() { |
|
174 |
if(this.editstr != this._editstr){ |
|
175 |
this._editstr = this.editstr; |
|
176 |
this.Value_elt.textContent = this.editstr; |
|
177 |
} |
|
178 |
if(this.shift != this._shift){ |
|
179 |
this._shift = this.shift; |
|
180 |
(this.shift?widget_active_activable:widget_inactive_activable)(this.Shift_sub); |
|
181 |
} |
|
182 |
if(this.caps != this._caps){ |
|
183 |
this._caps = this.caps; |
|
184 |
(this.caps?widget_active_activable:widget_inactive_activable)(this.CapsLock_sub); |
|
185 |
} |
|
186 |
} |
|
187 |
} |
|
188 |
|| |
|
189 |
||
2908 | 190 |
template "widget[@type='Keypad']", mode="widget_defs" { |
2917
c8d923dd707f
SVGHMI: Keypad working for HMI_STRING, still Shift/CapsLock not finished.
Edouard Tisserant
parents:
2911
diff
changeset
|
191 |
param "hmi_element"; |
c8d923dd707f
SVGHMI: Keypad working for HMI_STRING, still Shift/CapsLock not finished.
Edouard Tisserant
parents:
2911
diff
changeset
|
192 |
labels("Esc Enter BackSpace Keys Info Value"); |
3010 | 193 |
optional_labels("Sign Space NumDot position"); |
2920
3ee337c8c769
SVGHMI: finished shift and capslock support n keypad widget. Added a helper in widgets_common to collect subelements likle active/inactive/disabled...
Edouard Tisserant <edouard.tisserant@gmail.com>
parents:
2919
diff
changeset
|
194 |
activable_labels("CapsLock Shift"); |
2911
211d6a185e31
SVGHMI: More infrastructure for editing values with a keypad.
Edouard Tisserant
parents:
2908
diff
changeset
|
195 |
| init: function() { |
2917
c8d923dd707f
SVGHMI: Keypad working for HMI_STRING, still Shift/CapsLock not finished.
Edouard Tisserant
parents:
2911
diff
changeset
|
196 |
foreach "$hmi_element/*[@inkscape:label = 'Keys']/*" { |
c8d923dd707f
SVGHMI: Keypad working for HMI_STRING, still Shift/CapsLock not finished.
Edouard Tisserant
parents:
2911
diff
changeset
|
197 |
| id("«@id»").setAttribute("onclick", "hmi_widgets['«$hmi_element/@id»'].on_key_click('«func:escape_quotes(@inkscape:label)»')"); |
c8d923dd707f
SVGHMI: Keypad working for HMI_STRING, still Shift/CapsLock not finished.
Edouard Tisserant
parents:
2911
diff
changeset
|
198 |
} |
2920
3ee337c8c769
SVGHMI: finished shift and capslock support n keypad widget. Added a helper in widgets_common to collect subelements likle active/inactive/disabled...
Edouard Tisserant <edouard.tisserant@gmail.com>
parents:
2919
diff
changeset
|
199 |
foreach "str:split('Esc Enter BackSpace Sign Space NumDot CapsLock Shift')" { |
2917
c8d923dd707f
SVGHMI: Keypad working for HMI_STRING, still Shift/CapsLock not finished.
Edouard Tisserant
parents:
2911
diff
changeset
|
200 |
| if(this.«.»_elt) |
c8d923dd707f
SVGHMI: Keypad working for HMI_STRING, still Shift/CapsLock not finished.
Edouard Tisserant
parents:
2911
diff
changeset
|
201 |
| this.«.»_elt.setAttribute("onclick", "hmi_widgets['«$hmi_element/@id»'].on_«.»_click()"); |
c8d923dd707f
SVGHMI: Keypad working for HMI_STRING, still Shift/CapsLock not finished.
Edouard Tisserant
parents:
2911
diff
changeset
|
202 |
} |
3010 | 203 |
| if(this.position_elt){ |
3045
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
204 |
| this.bound_on_release = this.on_release.bind(this); |
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
205 |
| this.bound_on_drag = this.on_drag.bind(this); |
f6d428330e04
All widgets reworked to use widget class and animate function if needed
usveticic
parents:
3010
diff
changeset
|
206 |
| |
3010 | 207 |
| this.position_elt.setAttribute("onmousedown", "hmi_widgets['"+this.element_id+"'].on_position_click(evt)"); |
208 |
| this.position_elt.setAttribute("ontouchstart", "hmi_widgets['"+this.element_id+"'].on_position_click(evt)"); |
|
209 |
| } |
|
2917
c8d923dd707f
SVGHMI: Keypad working for HMI_STRING, still Shift/CapsLock not finished.
Edouard Tisserant
parents:
2911
diff
changeset
|
210 |
| }, |
3010 | 211 |
| |
2917
c8d923dd707f
SVGHMI: Keypad working for HMI_STRING, still Shift/CapsLock not finished.
Edouard Tisserant
parents:
2911
diff
changeset
|
212 |
const "g", "$geometry[@Id = $hmi_element/@id]"; |
c8d923dd707f
SVGHMI: Keypad working for HMI_STRING, still Shift/CapsLock not finished.
Edouard Tisserant
parents:
2911
diff
changeset
|
213 |
| coordinates: [«$g/@x», «$g/@y»], |
2908 | 214 |
} |