Browse Source

Merge pull request #200 from sents/selector_buttons

Selector buttons
dev_h
Ophir LOJKINE 4 years ago
parent
commit
2aee70e26d
No account linked to committer's email address

+ 5
- 5
client-data/js/board.js View File

363
 		else Tools.pendingMessages[name].push(message);
363
 		else Tools.pendingMessages[name].push(message);
364
 	}
364
 	}
365
 
365
 
366
-	if (message.tool !== 'Hand' && message.deltax != null && message.deltay != null) {
366
+	if (message.tool !== 'Hand' && message.transform != null) {
367
 		//this message has special info for the mover
367
 		//this message has special info for the mover
368
-		messageForTool({ tool: 'Hand', type: 'update', deltax: message.deltax || 0, deltay: message.deltay || 0, id: message.id });
368
+	    messageForTool({ tool: 'Hand', type: 'update', transform: message.transform, id: message.id});
369
 	}
369
 	}
370
 }
370
 }
371
 
371
 
685
 
685
 
686
 
686
 
687
 (function () {
687
 (function () {
688
-    let pos = {top: 0, scroll:0};
689
-    let menu = document.getElementById("menu");
688
+    var pos = {top: 0, scroll:0};
689
+    var menu = document.getElementById("menu");
690
     function menu_mousedown(evt) {
690
     function menu_mousedown(evt) {
691
 	pos = {
691
 	pos = {
692
 	    top: menu.scrollTop,
692
 	    top: menu.scrollTop,
696
 	document.addEventListener("mouseup", menu_mouseup);
696
 	document.addEventListener("mouseup", menu_mouseup);
697
     }
697
     }
698
     function menu_mousemove(evt) {
698
     function menu_mousemove(evt) {
699
-	const dy = evt.clientY - pos.scroll;
699
+	var dy = evt.clientY - pos.scroll;
700
 	menu.scrollTop = pos.top - dy;
700
 	menu.scrollTop = pos.top - dy;
701
     }
701
     }
702
     function menu_mouseup(evt) {
702
     function menu_mouseup(evt) {

+ 43
- 11
client-data/js/intersect.js View File

28
     [pointInTransformedBBox,
28
     [pointInTransformedBBox,
29
      transformedBBoxIntersects] = (function () {
29
      transformedBBoxIntersects] = (function () {
30
 
30
 
31
-	let applyTransform = function (m,t) {
31
+	 var get_transform_matrix = function (elem) {
32
+	     // Returns the first translate or transform matrix or makes one
33
+	     var transform = null;
34
+	     for (var i = 0; i < elem.transform.baseVal.numberOfItems; ++i) {
35
+		 var baseVal = elem.transform.baseVal[i];
36
+		 // quick tests showed that even if one changes only the fields e and f or uses createSVGTransformFromMatrix
37
+		 // the brower may add a SVG_TRANSFORM_MATRIX instead of a SVG_TRANSFORM_TRANSLATE
38
+		 if (baseVal.type === SVGTransform.SVG_TRANSFORM_MATRIX) {
39
+		     transform = baseVal;
40
+		     break;
41
+		 }
42
+	     }
43
+	     if (transform == null) {
44
+		 transform = elem.transform.baseVal.createSVGTransformFromMatrix(Tools.svg.createSVGMatrix());
45
+		 elem.transform.baseVal.appendItem(transform);
46
+	     }
47
+	     return transform.matrix;
48
+	 }
49
+
50
+	var transformRelative = function (m,t) {
32
 	    return [
51
 	    return [
33
 		m.a*t[0]+m.c*t[1],
52
 		m.a*t[0]+m.c*t[1],
34
 		m.b*t[0]+m.d*t[1]
53
 		m.b*t[0]+m.d*t[1]
35
 	    ]
54
 	    ]
36
 	}
55
 	}
37
 
56
 
57
+	var transformAbsolute = function (m,t) {
58
+	    return [
59
+		m.a*t[0]+m.c*t[1]+m.e,
60
+		m.b*t[0]+m.d*t[1]+m.f
61
+	    ]
62
+	}
63
+
38
 	SVGGraphicsElement.prototype.transformedBBox = function (scale=1) {
64
 	SVGGraphicsElement.prototype.transformedBBox = function (scale=1) {
39
 	    bbox = this.getBBox();
65
 	    bbox = this.getBBox();
40
-	    tmatrix = this.getCTM();
66
+	    tmatrix = get_transform_matrix(this);
67
+	    tmatrix.e /= scale;
68
+	    tmatrix.f /= scale;
41
 	    return {
69
 	    return {
42
-		r: [bbox.x + tmatrix.e/scale, bbox.y + tmatrix.f/scale],
43
-		a: applyTransform(tmatrix,[bbox.width/scale,0]),
44
-		b: applyTransform(tmatrix,[0,bbox.height/scale])
70
+		r: transformAbsolute(tmatrix,[bbox.x/scale,bbox.y/scale]),
71
+		a: transformRelative(tmatrix,[bbox.width/scale,0]),
72
+		b: transformRelative(tmatrix,[0,bbox.height/scale])
45
 	    }
73
 	    }
46
 	}
74
 	}
47
 
75
 
52
 		width: this.width.baseVal.value,
80
 		width: this.width.baseVal.value,
53
 		height: this.height.baseVal.value
81
 		height: this.height.baseVal.value
54
 	    };
82
 	    };
55
-	    tmatrix = this.getCTM();
83
+	    tmatrix = get_transform_matrix(this);
84
+	    tmatrix.e /= scale;
85
+	    tmatrix.f /= scale;
56
 	    return {
86
 	    return {
57
-		r: [bbox.x + tmatrix.e/scale, bbox.y + tmatrix.f/scale],
58
-		a: applyTransform(tmatrix,[bbox.width/scale,0]),
59
-		b: applyTransform(tmatrix,[0,bbox.height/scale])
87
+		r: transformAbsolute(tmatrix,[bbox.x/scale,bbox.y/scale]),
88
+		a: transformRelative(tmatrix,[bbox.width/scale,0]),
89
+		b: transformRelative(tmatrix,[0,bbox.height/scale])
60
 	    }
90
 	    }
61
 	}
91
 	}
62
 
92
 
63
-	let pointInTransformedBBox = function ([x,y],{r,a,b}) {
93
+	var pointInTransformedBBox = function ([x,y],{r,a,b}) {
64
 	    var d = [x-r[0],y-r[1]];
94
 	    var d = [x-r[0],y-r[1]];
65
 	    var idet = (a[0]*b[1]-a[1]*b[0]);
95
 	    var idet = (a[0]*b[1]-a[1]*b[0]);
66
 	    var c1 = (d[0]*b[1]-d[1]*b[0]) / idet;
96
 	    var c1 = (d[0]*b[1]-d[1]*b[0]) / idet;
79
 		[bbox_b.r[0] + bbox_b.b[0], bbox_b.r[1] + bbox_b.b[1]],
109
 		[bbox_b.r[0] + bbox_b.b[0], bbox_b.r[1] + bbox_b.b[1]],
80
 		[bbox_b.r[0] + bbox_b.a[0] + bbox_b.b[0], bbox_b.r[1] + bbox_b.a[1] + bbox_b.b[1]]
110
 		[bbox_b.r[0] + bbox_b.a[0] + bbox_b.b[0], bbox_b.r[1] + bbox_b.a[1] + bbox_b.b[1]]
81
 	    ]
111
 	    ]
82
-	    return corners.every(corner=>pointInTransformedBBox(corner,bbox_a))
112
+	    return corners.every(function(corner) {
113
+				return pointInTransformedBBox(corner, bbox_a);
114
+			})
83
 	}
115
 	}
84
 
116
 
85
 	SVGGraphicsElement.prototype.transformedBBoxIntersects= function (bbox) {
117
 	SVGGraphicsElement.prototype.transformedBBoxIntersects= function (bbox) {

+ 5
- 0
client-data/tools/hand/delete.svg View File

1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<svg role="img" version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" id="root">
3
+    <title>Delete</title>
4
+    <path stroke="red" stroke-width="2" d="M 2 2 L 22 22 M 2 22 L 22 2"></path>
5
+</svg>

+ 8
- 0
client-data/tools/hand/duplicate.svg View File

1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<svg role="img" version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" id="root">
3
+    <title>Duplicate</title>
4
+    <path d="m7.1549 7.1542v-3.5315c0-0.65725 0.52912-1.1864 1.1864-1.1864h11.991c0.65725 0 1.1864 0.52912 1.1864 1.1864v12.036c0 0.65725-0.52912 1.1864-1.1864 1.1864h-3.4867" fill="none" stroke="#000" stroke-linejoin="round" stroke-width=".3" />
5
+    <rect x="2.481" y="7.155" width="14.364" height="14.409" ry="1.1864" fill="none" stroke="#000" stroke-linejoin="round" stroke-width=".3" />
6
+    <rect transform="translate(-20.734 -16.126) scale(1,-1)" x="30.056" y="-33.755" width=".6819" height="6.5387" ry=".34186" />
7
+    <rect transform="translate(-20.734 -16.126) matrix(0,1,1,0,0,0)" x="30.145" y="27.128" width=".6819" height="6.5387" ry=".34186" />
8
+</svg>

+ 297
- 55
client-data/tools/hand/hand.js View File

25
  */
25
  */
26
 
26
 
27
 (function hand() { //Code isolation
27
 (function hand() { //Code isolation
28
-	const selectorStates = {
28
+	var selectorStates = {
29
 		pointing: 0,
29
 		pointing: 0,
30
 		selecting: 1,
30
 		selecting: 1,
31
-		moving: 2
31
+		transform: 2
32
 	}
32
 	}
33
 	var selected = null;
33
 	var selected = null;
34
 	var selected_els = [];
34
 	var selected_els = [];
35
 	var selectionRect = createSelectorRect();
35
 	var selectionRect = createSelectorRect();
36
-	var selectionRectTranslation;
37
-	var translation_elements = [];
36
+	var selectionRectTransform;
37
+	var currentTransform = null;
38
+	var transform_elements = [];
38
 	var selectorState = selectorStates.pointing;
39
 	var selectorState = selectorStates.pointing;
39
 	var last_sent = 0;
40
 	var last_sent = 0;
41
+	var blockedSelectionButtons = Tools.server_config.BLOCKED_SELECTION_BUTTONS;
42
+	var selectionButtons = [
43
+		createButton("delete", "delete", 24, 24,
44
+			function (me, bbox, s) {
45
+				me.width.baseVal.value = me.origWidth / s;
46
+				me.height.baseVal.value = me.origHeight / s;
47
+				me.x.baseVal.value = bbox.r[0];
48
+				me.y.baseVal.value = bbox.r[1] - (me.origHeight + 3) / s;
49
+				me.style.display = "";
50
+			},
51
+			deleteSelection),
52
+
53
+		createButton("duplicate", "duplicate", 24, 24,
54
+			function (me, bbox, s) {
55
+				me.width.baseVal.value = me.origWidth / s;
56
+				me.height.baseVal.value = me.origHeight / s;
57
+				me.x.baseVal.value = bbox.r[0] + (me.origWidth + 2) / s;
58
+				me.y.baseVal.value = bbox.r[1] - (me.origHeight + 3) / s;
59
+				me.style.display = "";
60
+			},
61
+			duplicateSelection),
62
+
63
+		createButton("scaleHandle", "handle", 14, 14,
64
+			function (me, bbox, s) {
65
+				me.width.baseVal.value = me.origWidth / s;
66
+				me.height.baseVal.value = me.origHeight / s;
67
+				me.x.baseVal.value = bbox.r[0] + bbox.a[0] - me.origWidth / (2 * s);
68
+				me.y.baseVal.value = bbox.r[1] + bbox.b[1] - me.origHeight / (2 * s);
69
+				me.style.display = "";
70
+			},
71
+			startScalingTransform)
72
+	];
73
+
74
+	for (i in blockedSelectionButtons) {
75
+		delete selectionButtons[blockedSelectionButtons[i]];
76
+	}
77
+
78
+	var getScale = Tools.getScale;
40
 
79
 
41
 	function getParentMathematics(el) {
80
 	function getParentMathematics(el) {
42
-		var target
43
-		var a = el
81
+		var target;
82
+		var a = el;
44
 		var els = [];
83
 		var els = [];
45
 		while (a) {
84
 		while (a) {
46
 			els.unshift(a);
85
 			els.unshift(a);
47
 			a = a.parentElement;
86
 			a = a.parentElement;
48
 		}
87
 		}
49
-		var parentMathematics = els.find(el => el.getAttribute("class") === "MathElement");
88
+		var parentMathematics = els.find(function (el) {
89
+			return el.getAttribute("class") === "MathElement";
90
+		});
50
 		if ((parentMathematics) && parentMathematics.tagName === "svg") {
91
 		if ((parentMathematics) && parentMathematics.tagName === "svg") {
51
 			target = parentMathematics;
92
 			target = parentMathematics;
52
 		}
93
 		}
53
-		return target ?? el;
94
+		return target || el;
95
+	}
96
+
97
+	function deleteSelection() {
98
+		var msgs = selected_els.map(function (el) {
99
+			return ({
100
+				"type": "delete",
101
+				"id": el.id
102
+			});
103
+		});
104
+		var data = {
105
+			_children: msgs
106
+		}
107
+		Tools.drawAndSend(data);
108
+		selected_els = [];
109
+		hideSelectionUI();
110
+	}
111
+
112
+	function duplicateSelection() {
113
+		if (!(selectorState == selectorStates.pointing)
114
+			|| (selected_els.length == 0)) return;
115
+		var msgs = [];
116
+		var newids = [];
117
+		for (var i = 0; i < selected_els.length; i++) {
118
+			var id = selected_els[i].id;
119
+			msgs[i] = {
120
+				type: "copy",
121
+				id: id,
122
+				newid: Tools.generateUID(id[0])
123
+			};
124
+			newids[i] = id;
125
+		}
126
+		Tools.drawAndSend({ _children: msgs });
127
+		selected_els = newids.map(function (id) {
128
+			return Tools.svg.getElementById(id);
129
+		});
54
 	}
130
 	}
55
 
131
 
56
 	function createSelectorRect() {
132
 	function createSelectorRect() {
70
 		return shape;
146
 		return shape;
71
 	}
147
 	}
72
 
148
 
149
+	function createButton(name, icon, width, height, drawCallback, clickCallback) {
150
+		var shape = Tools.createSVGElement("use", {href: "tools/hand/" + icon + ".svg#root"});
151
+		shape.style.display = "none";
152
+		shape.origWidth = width;
153
+		shape.origHeight = height;
154
+		shape.drawCallback = drawCallback;
155
+		shape.clickCallback = clickCallback;
156
+		Tools.svg.appendChild(shape);
157
+		return shape;
158
+	}
159
+
160
+	function showSelectionButtons() {
161
+		var scale = getScale();
162
+		var selectionBBox = selectionRect.transformedBBox();
163
+		for (var i = 0; i < selectionButtons.length; i++) {
164
+			selectionButtons[i].drawCallback(selectionButtons[i],
165
+				selectionBBox,
166
+				scale);
167
+		}
168
+	}
169
+
170
+	function hideSelectionButtons() {
171
+		for (var i = 0; i < selectionButtons.length; i++) {
172
+			selectionButtons[i].style.display = "none";
173
+		}
174
+	}
175
+
176
+	function hideSelectionUI() {
177
+		hideSelectionButtons();
178
+		selectionRect.style.display = "none";
179
+	}
180
+
73
 	function startMovingElements(x, y, evt) {
181
 	function startMovingElements(x, y, evt) {
74
 		evt.preventDefault();
182
 		evt.preventDefault();
75
-		selectorState = selectorStates.moving;
183
+		selectorState = selectorStates.transform;
184
+		currentTransform = moveSelection;
76
 		selected = { x: x, y: y };
185
 		selected = { x: x, y: y };
77
 		// Some of the selected elements could have been deleted
186
 		// Some of the selected elements could have been deleted
78
-		selected_els = selected_els.filter(el => {
79
-			return Tools.svg.getElementById(el.id) !== null
187
+		selected_els = selected_els.filter(function (el) {
188
+			return Tools.svg.getElementById(el.id) !== null;
80
 		});
189
 		});
81
-		translation_elements = selected_els.map(el => {
82
-			let tmatrix = get_translate_matrix(el);
83
-			return { x: tmatrix.e, y: tmatrix.f }
190
+		transform_elements = selected_els.map(function (el) {
191
+			var tmatrix = get_transform_matrix(el);
192
+			return {
193
+				a: tmatrix.a, b: tmatrix.b, c: tmatrix.c,
194
+				d: tmatrix.d, e: tmatrix.e, f: tmatrix.f
195
+			};
84
 		});
196
 		});
85
-		{
86
-			let tmatrix = get_translate_matrix(selectionRect);
87
-			selectionRectTranslation = { x: tmatrix.e, y: tmatrix.f };
88
-		}
197
+		var tmatrix = get_transform_matrix(selectionRect);
198
+		selectionRectTransform = { x: tmatrix.e, y: tmatrix.f };
199
+	}
200
+
201
+	function startScalingTransform(x, y, evt) {
202
+		evt.preventDefault();
203
+		hideSelectionButtons();
204
+		selectorState = selectorStates.transform;
205
+		var bbox = selectionRect.transformedBBox();
206
+		selected = {
207
+			x: bbox.r[0],
208
+			y: bbox.r[1],
209
+			w: bbox.a[0],
210
+			h: bbox.b[1],
211
+		};
212
+		transform_elements = selected_els.map(function (el) {
213
+			var tmatrix = get_transform_matrix(el);
214
+			return {
215
+				a: tmatrix.a, b: tmatrix.b, c: tmatrix.c,
216
+				d: tmatrix.d, e: tmatrix.e, f: tmatrix.f
217
+			};
218
+		});
219
+		var tmatrix = get_transform_matrix(selectionRect);
220
+		selectionRectTransform = {
221
+			a: tmatrix.a, d: tmatrix.d,
222
+			e: tmatrix.e, f: tmatrix.f
223
+		};
224
+		currentTransform = scaleSelection;
89
 	}
225
 	}
90
 
226
 
91
 	function startSelector(x, y, evt) {
227
 	function startSelector(x, y, evt) {
98
 		selectionRect.width.baseVal.value = 0;
234
 		selectionRect.width.baseVal.value = 0;
99
 		selectionRect.height.baseVal.value = 0;
235
 		selectionRect.height.baseVal.value = 0;
100
 		selectionRect.style.display = "";
236
 		selectionRect.style.display = "";
101
-		tmatrix = get_translate_matrix(selectionRect);
237
+		tmatrix = get_transform_matrix(selectionRect);
102
 		tmatrix.e = 0;
238
 		tmatrix.e = 0;
103
 		tmatrix.f = 0;
239
 		tmatrix.f = 0;
104
 	}
240
 	}
105
 
241
 
106
 
242
 
107
 	function calculateSelection() {
243
 	function calculateSelection() {
108
-		var scale = Tools.drawingArea.getCTM().a;
109
-		var selectionTBBox = selectionRect.transformedBBox(scale);
110
-		return Array.from(Tools.drawingArea.children).filter(el => {
111
-			return transformedBBoxIntersects(
112
-				selectionTBBox,
113
-				el.transformedBBox(scale)
114
-			)
115
-		});
244
+		var selectionTBBox = selectionRect.transformedBBox();
245
+		var elements = Tools.drawingArea.children;
246
+		var selected = [];
247
+		for (var i = 0; i < elements.length; i++) {
248
+			if (transformedBBoxIntersects(selectionTBBox, elements[i].transformedBBox()))
249
+				selected.push(Tools.drawingArea.children[i]);
250
+		}
251
+		return selected;
116
 	}
252
 	}
117
 
253
 
118
 	function moveSelection(x, y) {
254
 	function moveSelection(x, y) {
119
 		var dx = x - selected.x;
255
 		var dx = x - selected.x;
120
 		var dy = y - selected.y;
256
 		var dy = y - selected.y;
121
-		var msgs = selected_els.map((el, i) => {
257
+		var msgs = selected_els.map(function (el, i) {
258
+			var oldTransform = transform_elements[i];
122
 			return {
259
 			return {
123
 				type: "update",
260
 				type: "update",
124
 				id: el.id,
261
 				id: el.id,
125
-				deltax: dx + translation_elements[i].x,
126
-				deltay: dy + translation_elements[i].y
127
-			}
262
+				transform: {
263
+					a: oldTransform.a,
264
+					b: oldTransform.b,
265
+					c: oldTransform.c,
266
+					d: oldTransform.d,
267
+					e: dx + oldTransform.e,
268
+					f: dy + oldTransform.f
269
+				}
270
+			};
128
 		})
271
 		})
129
 		var msg = {
272
 		var msg = {
130
 			_children: msgs
273
 			_children: msgs
131
 		};
274
 		};
132
-		{
133
-			let tmatrix = get_translate_matrix(selectionRect);
134
-			tmatrix.e = dx + selectionRectTranslation.x;
135
-			tmatrix.f = dy + selectionRectTranslation.y;
275
+		var tmatrix = get_transform_matrix(selectionRect);
276
+		tmatrix.e = dx + selectionRectTransform.x;
277
+		tmatrix.f = dy + selectionRectTransform.y;
278
+		var now = performance.now();
279
+		if (now - last_sent > 70) {
280
+			last_sent = now;
281
+			Tools.drawAndSend(msg);
282
+		} else {
283
+			draw(msg);
136
 		}
284
 		}
285
+	}
286
+
287
+	function scaleSelection(x, y) {
288
+		var rx = (x - selected.x) / (selected.w);
289
+		var ry = (y - selected.y) / (selected.h);
290
+		var msgs = selected_els.map(function (el, i) {
291
+			var oldTransform = transform_elements[i];
292
+			var x = el.transformedBBox().r[0];
293
+			var y = el.transformedBBox().r[1];
294
+			var a = oldTransform.a * rx;
295
+			var d = oldTransform.d * ry;
296
+			var e = selected.x * (1 - rx) - x * a +
297
+				(x * oldTransform.a + oldTransform.e) * rx
298
+			var f = selected.y * (1 - ry) - y * d +
299
+				(y * oldTransform.d + oldTransform.f) * ry
300
+			return {
301
+				type: "update",
302
+				id: el.id,
303
+				transform: {
304
+					a: a,
305
+					b: oldTransform.b,
306
+					c: oldTransform.c,
307
+					d: d,
308
+					e: e,
309
+					f: f
310
+				}
311
+			};
312
+		})
313
+		var msg = {
314
+			_children: msgs
315
+		};
316
+
317
+		var tmatrix = get_transform_matrix(selectionRect);
318
+		tmatrix.a = rx;
319
+		tmatrix.d = ry;
320
+		tmatrix.e = selectionRectTransform.e +
321
+			selectionRect.x.baseVal.value * (selectionRectTransform.a - rx)
322
+		tmatrix.f = selectionRectTransform.f +
323
+			selectionRect.y.baseVal.value * (selectionRectTransform.d - ry)
137
 		var now = performance.now();
324
 		var now = performance.now();
138
 		if (now - last_sent > 70) {
325
 		if (now - last_sent > 70) {
139
 			last_sent = now;
326
 			last_sent = now;
150
 		rect.height.baseVal.value = Math.abs(y - selected.y);
337
 		rect.height.baseVal.value = Math.abs(y - selected.y);
151
 	}
338
 	}
152
 
339
 
153
-	function get_translate_matrix(elem) {
340
+	function resetSelectionRect() {
341
+		var bbox = selectionRect.transformedBBox();
342
+		var tmatrix = get_transform_matrix(selectionRect);
343
+		selectionRect.x.baseVal.value = bbox.r[0];
344
+		selectionRect.y.baseVal.value = bbox.r[1];
345
+		selectionRect.width.baseVal.value = bbox.a[0];
346
+		selectionRect.height.baseVal.value = bbox.b[1];
347
+		tmatrix.a = 1; tmatrix.b = 0; tmatrix.c = 0;
348
+		tmatrix.d = 1; tmatrix.e = 0; tmatrix.f = 0;
349
+	}
350
+
351
+	function get_transform_matrix(elem) {
154
 		// Returns the first translate or transform matrix or makes one
352
 		// Returns the first translate or transform matrix or makes one
155
-		var translate = null;
353
+		var transform = null;
156
 		for (var i = 0; i < elem.transform.baseVal.numberOfItems; ++i) {
354
 		for (var i = 0; i < elem.transform.baseVal.numberOfItems; ++i) {
157
 			var baseVal = elem.transform.baseVal[i];
355
 			var baseVal = elem.transform.baseVal[i];
158
 			// quick tests showed that even if one changes only the fields e and f or uses createSVGTransformFromMatrix
356
 			// quick tests showed that even if one changes only the fields e and f or uses createSVGTransformFromMatrix
159
 			// the brower may add a SVG_TRANSFORM_MATRIX instead of a SVG_TRANSFORM_TRANSLATE
357
 			// the brower may add a SVG_TRANSFORM_MATRIX instead of a SVG_TRANSFORM_TRANSLATE
160
-			if (baseVal.type === SVGTransform.SVG_TRANSFORM_TRANSLATE || baseVal.type === SVGTransform.SVG_TRANSFORM_MATRIX) {
161
-				translate = baseVal;
358
+			if (baseVal.type === SVGTransform.SVG_TRANSFORM_MATRIX) {
359
+				transform = baseVal;
162
 				break;
360
 				break;
163
 			}
361
 			}
164
 		}
362
 		}
165
-		if (translate == null) {
166
-			translate = elem.transform.baseVal.createSVGTransformFromMatrix(Tools.svg.createSVGMatrix());
167
-			elem.transform.baseVal.appendItem(translate);
363
+		if (transform == null) {
364
+			transform = elem.transform.baseVal.createSVGTransformFromMatrix(Tools.svg.createSVGMatrix());
365
+			elem.transform.baseVal.appendItem(transform);
168
 		}
366
 		}
169
-		return translate.matrix;
367
+		return transform.matrix;
170
 	}
368
 	}
171
 
369
 
172
 	function draw(data) {
370
 	function draw(data) {
178
 				case "update":
376
 				case "update":
179
 					var elem = Tools.svg.getElementById(data.id);
377
 					var elem = Tools.svg.getElementById(data.id);
180
 					if (!elem) throw new Error("Mover: Tried to move an element that does not exist.");
378
 					if (!elem) throw new Error("Mover: Tried to move an element that does not exist.");
181
-					var tmatrix = get_translate_matrix(elem);
182
-					tmatrix.e = data.deltax || 0;
183
-					tmatrix.f = data.deltay || 0;
379
+					var tmatrix = get_transform_matrix(elem);
380
+					for (i in data.transform) {
381
+						tmatrix[i] = data.transform[i]
382
+					}
383
+					break;
384
+				case "copy":
385
+					var newElement = Tools.svg.getElementById(data.id).cloneNode(true);
386
+					newElement.id = data.newid;
387
+					Tools.drawingArea.appendChild(newElement);
388
+					break;
389
+				case "delete":
390
+					data.tool = "Eraser";
391
+					messageForTool(data);
184
 					break;
392
 					break;
185
 				default:
393
 				default:
186
 					throw new Error("Mover: 'move' instruction with unknown type. ", data);
394
 					throw new Error("Mover: 'move' instruction with unknown type. ", data);
189
 	}
397
 	}
190
 
398
 
191
 	function clickSelector(x, y, evt) {
399
 	function clickSelector(x, y, evt) {
192
-		var scale = Tools.drawingArea.getCTM().a
193
-		selectionRect = selectionRect ?? createSelectorRect();
194
-		if (pointInTransformedBBox([x, y], selectionRect.transformedBBox(scale))) {
400
+		selectionRect = selectionRect || createSelectorRect();
401
+		for (var i = 0; i < selectionButtons.length; i++) {
402
+			if (selectionButtons[i].contains(evt.target)) {
403
+				var button = selectionButtons[i];
404
+			}
405
+		}
406
+		if (button) {
407
+			button.clickCallback(x, y, evt);
408
+		} else if (pointInTransformedBBox([x, y], selectionRect.transformedBBox())) {
409
+			hideSelectionButtons();
195
 			startMovingElements(x, y, evt);
410
 			startMovingElements(x, y, evt);
196
 		} else if (Tools.drawingArea.contains(evt.target)) {
411
 		} else if (Tools.drawingArea.contains(evt.target)) {
197
-			selectionRect.style.display = "none";
412
+			hideSelectionUI();
198
 			selected_els = [getParentMathematics(evt.target)];
413
 			selected_els = [getParentMathematics(evt.target)];
199
 			startMovingElements(x, y, evt);
414
 			startMovingElements(x, y, evt);
200
 		} else {
415
 		} else {
416
+			hideSelectionButtons();
201
 			startSelector(x, y, evt);
417
 			startSelector(x, y, evt);
202
 		}
418
 		}
203
 	}
419
 	}
206
 		if (selectorState == selectorStates.selecting) {
422
 		if (selectorState == selectorStates.selecting) {
207
 			selected_els = calculateSelection();
423
 			selected_els = calculateSelection();
208
 			if (selected_els.length == 0) {
424
 			if (selected_els.length == 0) {
209
-				selectionRect.style.display = "none";
425
+				hideSelectionUI();
210
 			}
426
 			}
211
-		}
212
-		translation_elements = [];
427
+		} else if (selectorState == selectorStates.transform)
428
+			resetSelectionRect();
429
+		if (selected_els.length != 0) showSelectionButtons();
430
+		transform_elements = [];
213
 		selectorState = selectorStates.pointing;
431
 		selectorState = selectorStates.pointing;
214
 	}
432
 	}
215
 
433
 
216
 	function moveSelector(x, y, evt) {
434
 	function moveSelector(x, y, evt) {
217
 		if (selectorState == selectorStates.selecting) {
435
 		if (selectorState == selectorStates.selecting) {
218
 			updateRect(x, y, selectionRect);
436
 			updateRect(x, y, selectionRect);
219
-		} else if (selectorState == selectorStates.moving) {
220
-			moveSelection(x, y, selectionRect);
437
+		} else if (selectorState == selectorStates.transform && currentTransform) {
438
+			currentTransform(x, y);
221
 		}
439
 		}
222
 	}
440
 	}
223
 
441
 
252
 		selected = null;
470
 		selected = null;
253
 	}
471
 	}
254
 
472
 
473
+	function deleteShortcut(e) {
474
+		if (e.key == "Delete" &&
475
+			!e.target.matches("input[type=text], textarea"))
476
+			deleteSelection();
477
+	}
478
+
479
+	function duplicateShortcut(e) {
480
+		if (e.key == "d" &&
481
+			!e.target.matches("input[type=text], textarea"))
482
+			duplicateSelection();
483
+	}
484
+
255
 	function switchTool() {
485
 	function switchTool() {
486
+		onquit();
487
+		if (handTool.secondary.active) {
488
+			window.addEventListener("keydown", deleteShortcut);
489
+			window.addEventListener("keydown", duplicateShortcut);
490
+		}
491
+	}
492
+
493
+	function onquit() {
256
 		selected = null;
494
 		selected = null;
495
+		hideSelectionUI();
496
+		window.removeEventListener("keydown", deleteShortcut);
497
+		window.removeEventListener("keydown", duplicateShortcut);
257
 	}
498
 	}
258
 
499
 
259
 	var handTool = { //The new tool
500
 	var handTool = { //The new tool
264
 			"move": move,
505
 			"move": move,
265
 			"release": release,
506
 			"release": release,
266
 		},
507
 		},
508
+		"onquit": onquit,
267
 		"secondary": {
509
 		"secondary": {
268
 			"name": "Selector",
510
 			"name": "Selector",
269
 			"icon": "tools/hand/selector.svg",
511
 			"icon": "tools/hand/selector.svg",

+ 16
- 0
client-data/tools/hand/handle.svg View File

1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<svg role="img" version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
3
+ <title>Instagram icon</title>
4
+ <g transform="matrix(2.3668 2.3668 -2.3668 2.3668 13.98 -131.11)" fill="#ff002d">
5
+  <g transform="translate(-.5821 .16648)" fill="#f00">
6
+   <rect transform="rotate(-45)" x="-2.9234" y="40.131" width="5.7222" height="5.8388" ry=".34186" fill="#bdaddf" fill-opacity=".99078" stroke="#000" stroke-linejoin="round" stroke-width=".23901"/>
7
+  </g>
8
+ </g>
9
+ <metadata>
10
+  <rdf:RDF>
11
+   <cc:Work rdf:about="">
12
+    <dc:title>Instagram icon</dc:title>
13
+   </cc:Work>
14
+  </rdf:RDF>
15
+ </metadata>
16
+</svg>

+ 25
- 0
server/boardData.js View File

100
     this.delaySave();
100
     this.delaySave();
101
   }
101
   }
102
 
102
 
103
+  /** Copy elements in the board
104
+   * @param {string} id - Identifier of the data to copy.
105
+   * @param {BoardElem} data - Object containing the id of the new copied element.
106
+   */
107
+  copy(id, data) {
108
+    var obj = this.board[id];
109
+    var newid = data.newid;
110
+    if (obj) {
111
+      var newobj = JSON.parse(JSON.stringify(obj));
112
+      newobj.id = newid;
113
+      if (newobj._children) {
114
+	for (var child of newobj._children) {
115
+	    child.parent = newid;
116
+	}
117
+      }
118
+      this.board[newid] = newobj;
119
+    } else  {
120
+      log("Copied object does not exist in board.", {object: id});
121
+    }
122
+    this.delaySave();
123
+  }
124
+
103
   /** Removes data from the board
125
   /** Removes data from the board
104
    * @param {string} id - Identifier of the data to delete.
126
    * @param {string} id - Identifier of the data to delete.
105
    */
127
    */
137
       case "update":
159
       case "update":
138
         if (id) this.update(id, message);
160
         if (id) this.update(id, message);
139
         break;
161
         break;
162
+      case "copy":
163
+        if (id) this.copy(id, message);
164
+        break;
140
       case "child":
165
       case "child":
141
         this.addChild(message.parent, message);
166
         this.addChild(message.parent, message);
142
         break;
167
         break;

+ 1
- 0
server/client_configuration.js View File

6
   MAX_EMIT_COUNT: config.MAX_EMIT_COUNT,
6
   MAX_EMIT_COUNT: config.MAX_EMIT_COUNT,
7
   MAX_EMIT_COUNT_PERIOD: config.MAX_EMIT_COUNT_PERIOD,
7
   MAX_EMIT_COUNT_PERIOD: config.MAX_EMIT_COUNT_PERIOD,
8
   BLOCKED_TOOLS: config.BLOCKED_TOOLS,
8
   BLOCKED_TOOLS: config.BLOCKED_TOOLS,
9
+  BLOCKED_SELECTION_BUTTONS: config.BLOCKED_SELECTION_BUTTONS,
9
   AUTO_FINGER_WHITEOUT: config.AUTO_FINGER_WHITEOUT,
10
   AUTO_FINGER_WHITEOUT: config.AUTO_FINGER_WHITEOUT,
10
 };
11
 };

+ 3
- 0
server/configuration.js View File

42
   /** Blocked Tools. A comma-separated list of tools that should not appear on boards. */
42
   /** Blocked Tools. A comma-separated list of tools that should not appear on boards. */
43
   BLOCKED_TOOLS: (process.env["WBO_BLOCKED_TOOLS"] || "").split(","),
43
   BLOCKED_TOOLS: (process.env["WBO_BLOCKED_TOOLS"] || "").split(","),
44
 
44
 
45
+  /** Selection Buttons. A comma-separated list of selection buttons that should not be available. */
46
+  BLOCKED_SELECTION_BUTTONS: (process.env["WBO_BLOCKED_SELECTION_BUTTONS"] || "").split(","),
47
+
45
   /** Automatically switch to White-out on finger touch after drawing
48
   /** Automatically switch to White-out on finger touch after drawing
46
       with Pencil using a stylus. Only supported on iPad with Apple Pencil. */
49
       with Pencil using a stylus. Only supported on iPad with Apple Pencil. */
47
   AUTO_FINGER_WHITEOUT: process.env['AUTO_FINGER_WHITEOUT'] !== "disabled",
50
   AUTO_FINGER_WHITEOUT: process.env['AUTO_FINGER_WHITEOUT'] !== "disabled",

Loading…
Cancel
Save