From 7802f0071bdfa6cc16f107cbb5ad7507e727ba52 Mon Sep 17 00:00:00 2001 From: Finn Krein Date: Mon, 31 May 2021 21:04:47 +0200 Subject: [PATCH 01/11] Make selector compatible with IE9 --- client-data/js/board.js | 6 ++-- client-data/js/intersect.js | 8 +++-- client-data/tools/hand/hand.js | 65 +++++++++++++++++----------------- 3 files changed, 40 insertions(+), 39 deletions(-) diff --git a/client-data/js/board.js b/client-data/js/board.js index ebb0d03d..c8b33a57 100644 --- a/client-data/js/board.js +++ b/client-data/js/board.js @@ -685,8 +685,8 @@ Tools.svg.height.baseVal.value = document.body.clientHeight; (function () { - let pos = {top: 0, scroll:0}; - let menu = document.getElementById("menu"); + var pos = {top: 0, scroll:0}; + var menu = document.getElementById("menu"); function menu_mousedown(evt) { pos = { top: menu.scrollTop, @@ -696,7 +696,7 @@ Tools.svg.height.baseVal.value = document.body.clientHeight; document.addEventListener("mouseup", menu_mouseup); } function menu_mousemove(evt) { - const dy = evt.clientY - pos.scroll; + var dy = evt.clientY - pos.scroll; menu.scrollTop = pos.top - dy; } function menu_mouseup(evt) { diff --git a/client-data/js/intersect.js b/client-data/js/intersect.js index 3cd09b8c..adc50068 100644 --- a/client-data/js/intersect.js +++ b/client-data/js/intersect.js @@ -28,7 +28,7 @@ if (!SVGGraphicsElement.prototype.transformedBBox || !SVGGraphicsElement.prototy [pointInTransformedBBox, transformedBBoxIntersects] = (function () { - let applyTransform = function (m,t) { + var applyTransform = function (m,t) { return [ m.a*t[0]+m.c*t[1], m.b*t[0]+m.d*t[1] @@ -60,7 +60,7 @@ if (!SVGGraphicsElement.prototype.transformedBBox || !SVGGraphicsElement.prototy } } - let pointInTransformedBBox = function ([x,y],{r,a,b}) { + var pointInTransformedBBox = function ([x,y],{r,a,b}) { var d = [x-r[0],y-r[1]]; var idet = (a[0]*b[1]-a[1]*b[0]); var c1 = (d[0]*b[1]-d[1]*b[0]) / idet; @@ -79,7 +79,9 @@ if (!SVGGraphicsElement.prototype.transformedBBox || !SVGGraphicsElement.prototy [bbox_b.r[0] + bbox_b.b[0], bbox_b.r[1] + bbox_b.b[1]], [bbox_b.r[0] + bbox_b.a[0] + bbox_b.b[0], bbox_b.r[1] + bbox_b.a[1] + bbox_b.b[1]] ] - return corners.every(corner=>pointInTransformedBBox(corner,bbox_a)) + return corners.every(function(corner) { + return pointInTransformedBBox(corner, bbox_a); + }) } SVGGraphicsElement.prototype.transformedBBoxIntersects= function (bbox) { diff --git a/client-data/tools/hand/hand.js b/client-data/tools/hand/hand.js index 8f95db67..03820a20 100644 --- a/client-data/tools/hand/hand.js +++ b/client-data/tools/hand/hand.js @@ -25,7 +25,7 @@ */ (function hand() { //Code isolation - const selectorStates = { + var selectorStates = { pointing: 0, selecting: 1, moving: 2 @@ -46,11 +46,13 @@ els.unshift(a); a = a.parentElement; } - var parentMathematics = els.find(el => el.getAttribute("class") === "MathElement"); + var parentMathematics = els.find(function(el) { + return el.getAttribute("class") === "MathElement"; + }); if ((parentMathematics) && parentMathematics.tagName === "svg") { target = parentMathematics; } - return target ?? el; + return target || el; } function createSelectorRect() { @@ -75,17 +77,15 @@ selectorState = selectorStates.moving; selected = { x: x, y: y }; // Some of the selected elements could have been deleted - selected_els = selected_els.filter(el => { - return Tools.svg.getElementById(el.id) !== null + selected_els = selected_els.filter(function(el) { + return Tools.svg.getElementById(el.id) !== null; + }); + translation_elements = selected_els.map(function(el) { + var tmatrix = get_translate_matrix(el); + return { x: tmatrix.e, y: tmatrix.f }; }); - translation_elements = selected_els.map(el => { - let tmatrix = get_translate_matrix(el); - return { x: tmatrix.e, y: tmatrix.f } - }); - { - let tmatrix = get_translate_matrix(selectionRect); - selectionRectTranslation = { x: tmatrix.e, y: tmatrix.f }; - } + var tmatrix = get_translate_matrix(selectionRect); + selectionRectTranslation = { x: tmatrix.e, y: tmatrix.f }; } function startSelector(x, y, evt) { @@ -107,33 +107,32 @@ function calculateSelection() { var scale = Tools.drawingArea.getCTM().a; var selectionTBBox = selectionRect.transformedBBox(scale); - return Array.from(Tools.drawingArea.children).filter(el => { - return transformedBBoxIntersects( - selectionTBBox, - el.transformedBBox(scale) - ) - }); + var elements = Tools.drawingArea.children; + var selected = []; + for (var i=0; i < elements.length; i++) { + if (transformedBBoxIntersects(selectionTBBox, elements[i].transformedBBox(scale))) + selected.push(Tools.drawingArea.children[i]); + } + return selected; } function moveSelection(x, y) { var dx = x - selected.x; var dy = y - selected.y; - var msgs = selected_els.map((el, i) => { - return { - type: "update", - id: el.id, - deltax: dx + translation_elements[i].x, - deltay: dy + translation_elements[i].y - } - }) + var msgs = selected_els.map(function(el, i) { + return { + type: "update", + id: el.id, + deltax: dx + translation_elements[i].x, + deltay: dy + translation_elements[i].y + }; + }) var msg = { _children: msgs }; - { - let tmatrix = get_translate_matrix(selectionRect); - tmatrix.e = dx + selectionRectTranslation.x; - tmatrix.f = dy + selectionRectTranslation.y; - } + var tmatrix = get_translate_matrix(selectionRect); + tmatrix.e = dx + selectionRectTranslation.x; + tmatrix.f = dy + selectionRectTranslation.y; var now = performance.now(); if (now - last_sent > 70) { last_sent = now; @@ -190,7 +189,7 @@ function clickSelector(x, y, evt) { var scale = Tools.drawingArea.getCTM().a - selectionRect = selectionRect ?? createSelectorRect(); + selectionRect = selectionRect || createSelectorRect(); if (pointInTransformedBBox([x, y], selectionRect.transformedBBox(scale))) { startMovingElements(x, y, evt); } else if (Tools.drawingArea.contains(evt.target)) { From 6b4ec13a0f456d4ccaaf0e94e5aa26d04c6a57c4 Mon Sep 17 00:00:00 2001 From: Finn Krein Date: Sun, 30 May 2021 23:58:30 +0200 Subject: [PATCH 02/11] Add deletion and duplication of selection --- client-data/tools/hand/delete.svg | 17 ++++++++ client-data/tools/hand/duplicate.svg | 17 ++++++++ client-data/tools/hand/hand.js | 63 ++++++++++++++++++++++++++++ server/boardData.js | 25 +++++++++++ 4 files changed, 122 insertions(+) create mode 100644 client-data/tools/hand/delete.svg create mode 100644 client-data/tools/hand/duplicate.svg diff --git a/client-data/tools/hand/delete.svg b/client-data/tools/hand/delete.svg new file mode 100644 index 00000000..c6d70e22 --- /dev/null +++ b/client-data/tools/hand/delete.svg @@ -0,0 +1,17 @@ + + + Instagram icon + + + + + + + + + + Delete icon + + + + diff --git a/client-data/tools/hand/duplicate.svg b/client-data/tools/hand/duplicate.svg new file mode 100644 index 00000000..6a410369 --- /dev/null +++ b/client-data/tools/hand/duplicate.svg @@ -0,0 +1,17 @@ + + + Instagram icon + + + + + + + + + + Duplicate icon + + + + diff --git a/client-data/tools/hand/hand.js b/client-data/tools/hand/hand.js index 03820a20..1d5ce851 100644 --- a/client-data/tools/hand/hand.js +++ b/client-data/tools/hand/hand.js @@ -55,6 +55,40 @@ return target || el; } + function deleteSelection() { + var msgs = selected_els.map(function(el) { + return ({ + "type": "delete", + "id": el.id + }); + }); + var data = { + _children: msgs + } + Tools.drawAndSend(data); + selected_els = []; + } + + function duplicateSelection() { + if (!(selectorState == selectorStates.pointing) + || (selected_els.length == 0)) return; + var msgs = []; + var newids = []; + for (var i=0; i Date: Mon, 31 May 2021 20:03:30 +0200 Subject: [PATCH 03/11] Add buttons for deleteSelection and duplicateSelection --- client-data/tools/hand/hand.js | 88 +++++++++++++++++++++++++++++++--- 1 file changed, 81 insertions(+), 7 deletions(-) diff --git a/client-data/tools/hand/hand.js b/client-data/tools/hand/hand.js index 1d5ce851..c074b591 100644 --- a/client-data/tools/hand/hand.js +++ b/client-data/tools/hand/hand.js @@ -38,9 +38,34 @@ var selectorState = selectorStates.pointing; var last_sent = 0; + var deleteButton = createButton("delete", "delete", 22, 22, + function(me, bbox, s) { + me.width.baseVal.value = me.origWidth / s; + me.height.baseVal.value = me.origHeight / s; + me.x.baseVal.value = bbox.r[0]; + me.y.baseVal.value = bbox.r[1] - (me.origHeight + 3) / s; + me.style.display = ""; + }, + deleteSelection); + + var duplicateButton = createButton("duplicate", "duplicate", 22, 22, + function(me, bbox, s) { + me.width.baseVal.value = me.origWidth / s; + me.height.baseVal.value = me.origHeight / s; + me.x.baseVal.value = bbox.r[0] + (me.origWidth + 2) / s; + me.y.baseVal.value = bbox.r[1] - (me.origHeight + 3) / s; + me.style.display = ""; + }, + duplicateSelection); + var selectionButtons = [deleteButton, duplicateButton]; + + function getScale() { + return Tools.drawingArea.getCTM().a; + } + function getParentMathematics(el) { - var target - var a = el + var target; + var a = el; var els = []; while (a) { els.unshift(a); @@ -67,6 +92,7 @@ } Tools.drawAndSend(data); selected_els = []; + hideSelectionUI(); } function duplicateSelection() { @@ -106,6 +132,43 @@ return shape; } + function createButton(name, icon , width, height, drawCallback, clickCallback) { + var shape = Tools.createSVGElement("image", { + id: name + "Icon", + href: "tools/hand/" + icon + ".svg", + width: width, + height: height, + }); + shape.style.display = "none"; + shape.origWidth = width; + shape.origHeight = height; + shape.drawCallback = drawCallback; + shape.clickCallback = clickCallback; + Tools.svg.appendChild(shape); + return shape; + } + + function showSelectionButtons() { + var scale = getScale(); + var selectionBBox = selectionRect.transformedBBox(scale); + for (var i = 0; i < selectionButtons.length; i++) { + selectionButtons[i].drawCallback(selectionButtons[i], + selectionBBox, + scale); + } + } + + function hideSelectionButtons() { + for (var i = 0; i < selectionButtons.length; i++) { + selectionButtons[i].style.display = "none"; + } + } + + function hideSelectionUI() { + hideSelectionButtons(); + selectionRect.style.display = "none"; + } + function startMovingElements(x, y, evt) { evt.preventDefault(); selectorState = selectorStates.moving; @@ -139,7 +202,7 @@ function calculateSelection() { - var scale = Tools.drawingArea.getCTM().a; + var scale = getScale(); var selectionTBBox = selectionRect.transformedBBox(scale); var elements = Tools.drawingArea.children; var selected = []; @@ -231,15 +294,25 @@ } function clickSelector(x, y, evt) { - var scale = Tools.drawingArea.getCTM().a + var scale = getScale(); selectionRect = selectionRect || createSelectorRect(); - if (pointInTransformedBBox([x, y], selectionRect.transformedBBox(scale))) { + var button = null; + for (var i=0; i Date: Tue, 1 Jun 2021 23:54:11 +0200 Subject: [PATCH 04/11] Add handle to scale selection --- client-data/js/board.js | 4 +- client-data/js/intersect.js | 25 ++-- client-data/tools/hand/hand.js | 187 +++++++++++++++++++++++------- client-data/tools/hand/handle.svg | 16 +++ 4 files changed, 183 insertions(+), 49 deletions(-) create mode 100644 client-data/tools/hand/handle.svg diff --git a/client-data/js/board.js b/client-data/js/board.js index c8b33a57..bde36010 100644 --- a/client-data/js/board.js +++ b/client-data/js/board.js @@ -363,9 +363,9 @@ function messageForTool(message) { else Tools.pendingMessages[name].push(message); } - if (message.tool !== 'Hand' && message.deltax != null && message.deltay != null) { + if (message.tool !== 'Hand' && message.transform != null) { //this message has special info for the mover - messageForTool({ tool: 'Hand', type: 'update', deltax: message.deltax || 0, deltay: message.deltay || 0, id: message.id }); + messageForTool({ tool: 'Hand', type: 'update', transform: message.transform, id: message.id}); } } diff --git a/client-data/js/intersect.js b/client-data/js/intersect.js index adc50068..705bcea2 100644 --- a/client-data/js/intersect.js +++ b/client-data/js/intersect.js @@ -28,20 +28,29 @@ if (!SVGGraphicsElement.prototype.transformedBBox || !SVGGraphicsElement.prototy [pointInTransformedBBox, transformedBBoxIntersects] = (function () { - var applyTransform = function (m,t) { + var transformRelative = function (m,t) { return [ m.a*t[0]+m.c*t[1], m.b*t[0]+m.d*t[1] ] } + var transformAbsolute = function (m,t) { + return [ + m.a*t[0]+m.c*t[1]+m.e, + m.b*t[0]+m.d*t[1]+m.f + ] + } + SVGGraphicsElement.prototype.transformedBBox = function (scale=1) { bbox = this.getBBox(); tmatrix = this.getCTM(); + tmatrix.e /= scale; + tmatrix.f /= scale; return { - r: [bbox.x + tmatrix.e/scale, bbox.y + tmatrix.f/scale], - a: applyTransform(tmatrix,[bbox.width/scale,0]), - b: applyTransform(tmatrix,[0,bbox.height/scale]) + r: transformAbsolute(tmatrix,[bbox.x/scale,bbox.y/scale]), + a: transformRelative(tmatrix,[bbox.width/scale,0]), + b: transformRelative(tmatrix,[0,bbox.height/scale]) } } @@ -53,10 +62,12 @@ if (!SVGGraphicsElement.prototype.transformedBBox || !SVGGraphicsElement.prototy height: this.height.baseVal.value }; tmatrix = this.getCTM(); + tmatrix.e /= scale; + tmatrix.f /= scale; return { - r: [bbox.x + tmatrix.e/scale, bbox.y + tmatrix.f/scale], - a: applyTransform(tmatrix,[bbox.width/scale,0]), - b: applyTransform(tmatrix,[0,bbox.height/scale]) + r: transformAbsolute(tmatrix,[bbox.x/scale,bbox.y/scale]), + a: transformRelative(tmatrix,[bbox.width/scale,0]), + b: transformRelative(tmatrix,[0,bbox.height/scale]) } } diff --git a/client-data/tools/hand/hand.js b/client-data/tools/hand/hand.js index c074b591..b2f9b198 100644 --- a/client-data/tools/hand/hand.js +++ b/client-data/tools/hand/hand.js @@ -28,13 +28,14 @@ var selectorStates = { pointing: 0, selecting: 1, - moving: 2 + transform: 2 } var selected = null; var selected_els = []; var selectionRect = createSelectorRect(); - var selectionRectTranslation; - var translation_elements = []; + var selectionRectTransform; + var currentTransform = null; + var transform_elements = []; var selectorState = selectorStates.pointing; var last_sent = 0; @@ -57,11 +58,18 @@ me.style.display = ""; }, duplicateSelection); - var selectionButtons = [deleteButton, duplicateButton]; + var scaleHandle = createButton("scaleHandle", "handle", 14, 14, + function(me, bbox, s) { + me.width.baseVal.value = me.origWidth / s; + me.height.baseVal.value = me.origHeight / s; + me.x.baseVal.value = bbox.r[0] + bbox.a[0] - me.origWidth/(2*s); + me.y.baseVal.value = bbox.r[1] + bbox.b[1] - me.origHeight/(2*s); + me.style.display = ""; + }, + startScalingTransform); + var selectionButtons = [deleteButton, duplicateButton, scaleHandle]; - function getScale() { - return Tools.drawingArea.getCTM().a; - } + var getScale = Tools.getScale; function getParentMathematics(el) { var target; @@ -171,18 +179,50 @@ function startMovingElements(x, y, evt) { evt.preventDefault(); - selectorState = selectorStates.moving; + selectorState = selectorStates.transform; + currentTransform = moveSelection; selected = { x: x, y: y }; // Some of the selected elements could have been deleted selected_els = selected_els.filter(function(el) { return Tools.svg.getElementById(el.id) !== null; }); - translation_elements = selected_els.map(function(el) { - var tmatrix = get_translate_matrix(el); - return { x: tmatrix.e, y: tmatrix.f }; + transform_elements = selected_els.map(function(el) { + var tmatrix = get_transform_matrix(el); + return { + a: tmatrix.a, b: tmatrix.b, c: tmatrix.c, + d: tmatrix.d, e: tmatrix.e, f: tmatrix.f + }; + }); + var tmatrix = get_transform_matrix(selectionRect); + selectionRectTransform = { x: tmatrix.e, y: tmatrix.f }; + } + + function startScalingTransform(x, y, evt) { + evt.preventDefault(); + hideSelectionButtons(); + selectorState = selectorStates.transform; + var scale = getScale(); + var bbox = selectionRect.transformedBBox(scale); + selected = { + x: bbox.r[0], + y: bbox.r[1], + w: bbox.a[0], + h: bbox.b[1], + s: scale + }; + transform_elements = selected_els.map(function(el) { + var tmatrix = get_transform_matrix(el); + return { + a: tmatrix.a, b: tmatrix.b, c: tmatrix.c, + d: tmatrix.d, e: tmatrix.e, f: tmatrix.f + }; }); - var tmatrix = get_translate_matrix(selectionRect); - selectionRectTranslation = { x: tmatrix.e, y: tmatrix.f }; + var tmatrix = get_transform_matrix(selectionRect); + selectionRectTransform = { + a: tmatrix.a, d: tmatrix.d, + e: tmatrix.e, f: tmatrix.f + }; + currentTransform = scaleSelection; } function startSelector(x, y, evt) { @@ -195,7 +235,7 @@ selectionRect.width.baseVal.value = 0; selectionRect.height.baseVal.value = 0; selectionRect.style.display = ""; - tmatrix = get_translate_matrix(selectionRect); + tmatrix = get_transform_matrix(selectionRect); tmatrix.e = 0; tmatrix.f = 0; } @@ -217,19 +257,73 @@ var dx = x - selected.x; var dy = y - selected.y; var msgs = selected_els.map(function(el, i) { - return { - type: "update", - id: el.id, - deltax: dx + translation_elements[i].x, - deltay: dy + translation_elements[i].y - }; - }) + var oldTransform = transform_elements[i]; + return { + type: "update", + id: el.id, + transform: { + a: oldTransform.a, + b: oldTransform.b, + c: oldTransform.c, + d: oldTransform.d, + e: dx + oldTransform.e, + f: dy + oldTransform.f + } + }; + }) + var msg = { + _children: msgs + }; + var tmatrix = get_transform_matrix(selectionRect); + tmatrix.e = dx + selectionRectTransform.x; + tmatrix.f = dy + selectionRectTransform.y; + var now = performance.now(); + if (now - last_sent > 70) { + last_sent = now; + Tools.drawAndSend(msg); + } else { + draw(msg); + } + } + + function scaleSelection(x, y) { + var rx = (x - selected.x)/(selected.w); + var ry = (y - selected.y)/(selected.h); + var scale = getScale(); + var msgs = selected_els.map(function(el, i) { + var oldTransform = transform_elements[i]; + var x = el.transformedBBox(scale).r[0]; + var y = el.transformedBBox(scale).r[1]; + var a = oldTransform.a * rx; + var d = oldTransform.d * ry; + var e = selected.x * (1 - rx) - x * a + + (x * oldTransform.a + oldTransform.e) * rx + var f = selected.y * (1 - ry) - y * d + + (y * oldTransform.d + oldTransform.f) * ry + return { + type: "update", + id: el.id, + transform: { + a: a, + b: oldTransform.b, + c: oldTransform.c, + d: d, + e: e, + f: f + } + }; + }) var msg = { _children: msgs }; - var tmatrix = get_translate_matrix(selectionRect); - tmatrix.e = dx + selectionRectTranslation.x; - tmatrix.f = dy + selectionRectTranslation.y; + + var tmatrix = get_transform_matrix(selectionRect); + tmatrix.a = rx; + tmatrix.d = ry; + tmatrix.e = selectionRectTransform.e + + selectionRect.x.baseVal.value * (selectionRectTransform.a - rx) + tmatrix.f = selectionRectTransform.f + + selectionRect.y.baseVal.value * (selectionRectTransform.d - ry) var now = performance.now(); if (now - last_sent > 70) { last_sent = now; @@ -246,23 +340,34 @@ rect.height.baseVal.value = Math.abs(y - selected.y); } - function get_translate_matrix(elem) { + function resetSelectionRect() { + var bbox = selectionRect.transformedBBox(getScale()); + var tmatrix = get_transform_matrix(selectionRect); + selectionRect.x.baseVal.value = bbox.r[0]; + selectionRect.y.baseVal.value = bbox.r[1]; + selectionRect.width.baseVal.value = bbox.a[0]; + selectionRect.height.baseVal.value = bbox.b[1]; + tmatrix.a = 1; tmatrix.b = 0; tmatrix.c = 0; + tmatrix.d = 1; tmatrix.e = 0; tmatrix.f = 0; + } + + function get_transform_matrix(elem) { // Returns the first translate or transform matrix or makes one - var translate = null; + var transform = null; for (var i = 0; i < elem.transform.baseVal.numberOfItems; ++i) { var baseVal = elem.transform.baseVal[i]; // quick tests showed that even if one changes only the fields e and f or uses createSVGTransformFromMatrix // the brower may add a SVG_TRANSFORM_MATRIX instead of a SVG_TRANSFORM_TRANSLATE - if (baseVal.type === SVGTransform.SVG_TRANSFORM_TRANSLATE || baseVal.type === SVGTransform.SVG_TRANSFORM_MATRIX) { - translate = baseVal; + if (baseVal.type === SVGTransform.SVG_TRANSFORM_MATRIX) { + transform = baseVal; break; } } - if (translate == null) { - translate = elem.transform.baseVal.createSVGTransformFromMatrix(Tools.svg.createSVGMatrix()); - elem.transform.baseVal.appendItem(translate); + if (transform == null) { + transform = elem.transform.baseVal.createSVGTransformFromMatrix(Tools.svg.createSVGMatrix()); + elem.transform.baseVal.appendItem(transform); } - return translate.matrix; + return transform.matrix; } function draw(data) { @@ -274,9 +379,10 @@ case "update": var elem = Tools.svg.getElementById(data.id); if (!elem) throw new Error("Mover: Tried to move an element that does not exist."); - var tmatrix = get_translate_matrix(elem); - tmatrix.e = data.deltax || 0; - tmatrix.f = data.deltay || 0; + var tmatrix = get_transform_matrix(elem); + for (i in data.transform) { + tmatrix[i] = data.transform[i] + } break; case "copy": var newElement = Tools.svg.getElementById(data.id).cloneNode(true); @@ -303,7 +409,7 @@ } } if (button) { - button.clickCallback(); + button.clickCallback(x, y, evt); } else if (pointInTransformedBBox([x, y], selectionRect.transformedBBox(scale))) { hideSelectionButtons(); startMovingElements(x, y, evt); @@ -323,17 +429,18 @@ if (selected_els.length == 0) { hideSelectionUI(); } - } + } else if (selectorState == selectorStates.transform) + resetSelectionRect(); if (selected_els.length != 0) showSelectionButtons(); - translation_elements = []; + transform_elements = []; selectorState = selectorStates.pointing; } function moveSelector(x, y, evt) { if (selectorState == selectorStates.selecting) { updateRect(x, y, selectionRect); - } else if (selectorState == selectorStates.moving) { - moveSelection(x, y, selectionRect); + } else if (selectorState == selectorStates.transform && currentTransform) { + currentTransform(x, y); } } diff --git a/client-data/tools/hand/handle.svg b/client-data/tools/hand/handle.svg new file mode 100644 index 00000000..f805b7c0 --- /dev/null +++ b/client-data/tools/hand/handle.svg @@ -0,0 +1,16 @@ + + + Instagram icon + + + + + + + + + Instagram icon + + + + From 3e2db5c4248d8b498bf276fed25902514e0ac6c7 Mon Sep 17 00:00:00 2001 From: Finn Krein Date: Wed, 2 Jun 2021 12:26:42 +0200 Subject: [PATCH 05/11] Add configuration option to block specific selection buttons --- client-data/tools/hand/hand.js | 18 +++++++++++++----- server/client_configuration.js | 1 + server/configuration.js | 3 +++ 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/client-data/tools/hand/hand.js b/client-data/tools/hand/hand.js index b2f9b198..6c94f114 100644 --- a/client-data/tools/hand/hand.js +++ b/client-data/tools/hand/hand.js @@ -38,8 +38,10 @@ var transform_elements = []; var selectorState = selectorStates.pointing; var last_sent = 0; + var blockedSelectionButtons = Tools.server_config.BLOCKED_SELECTION_BUTTONS; + var selectionButtons = {}; - var deleteButton = createButton("delete", "delete", 22, 22, + selectionButtons["delete"] = createButton("delete", "delete", 22, 22, function(me, bbox, s) { me.width.baseVal.value = me.origWidth / s; me.height.baseVal.value = me.origHeight / s; @@ -49,7 +51,7 @@ }, deleteSelection); - var duplicateButton = createButton("duplicate", "duplicate", 22, 22, + selectionButtons["duplicate"] = createButton("duplicate", "duplicate", 22, 22, function(me, bbox, s) { me.width.baseVal.value = me.origWidth / s; me.height.baseVal.value = me.origHeight / s; @@ -58,7 +60,7 @@ me.style.display = ""; }, duplicateSelection); - var scaleHandle = createButton("scaleHandle", "handle", 14, 14, + selectionButtons["scale"] = createButton("scaleHandle", "handle", 14, 14, function(me, bbox, s) { me.width.baseVal.value = me.origWidth / s; me.height.baseVal.value = me.origHeight / s; @@ -66,8 +68,14 @@ me.y.baseVal.value = bbox.r[1] + bbox.b[1] - me.origHeight/(2*s); me.style.display = ""; }, - startScalingTransform); - var selectionButtons = [deleteButton, duplicateButton, scaleHandle]; + startScalingTransform); + + for (i in blockedSelectionButtons) { + delete selectionButtons[blockedSelectionButtons[i]]; + } + selectionButtons = Object.keys(selectionButtons).map(function(k) { + return selectionButtons[k]; + }); var getScale = Tools.getScale; diff --git a/server/client_configuration.js b/server/client_configuration.js index e623400e..e8957a1d 100644 --- a/server/client_configuration.js +++ b/server/client_configuration.js @@ -6,5 +6,6 @@ module.exports = { MAX_EMIT_COUNT: config.MAX_EMIT_COUNT, MAX_EMIT_COUNT_PERIOD: config.MAX_EMIT_COUNT_PERIOD, BLOCKED_TOOLS: config.BLOCKED_TOOLS, + BLOCKED_SELECTION_BUTTONS: config.BLOCKED_SELECTION_BUTTONS, AUTO_FINGER_WHITEOUT: config.AUTO_FINGER_WHITEOUT, }; diff --git a/server/configuration.js b/server/configuration.js index 6010134c..334663c0 100644 --- a/server/configuration.js +++ b/server/configuration.js @@ -42,6 +42,9 @@ module.exports = { /** Blocked Tools. A comma-separated list of tools that should not appear on boards. */ BLOCKED_TOOLS: (process.env["WBO_BLOCKED_TOOLS"] || "").split(","), + /** Selection Buttons. A comma-separated list of selection buttons that should not be available. */ + BLOCKED_SELECTION_BUTTONS: (process.env["WBO_BLOCKED_SELECTION_BUTTONS"] || "").split(","), + /** Automatically switch to White-out on finger touch after drawing with Pencil using a stylus. Only supported on iPad with Apple Pencil. */ AUTO_FINGER_WHITEOUT: process.env['AUTO_FINGER_WHITEOUT'] !== "disabled", From d7b341d86eb9698a32bc9f39d0645dc0d1c48409 Mon Sep 17 00:00:00 2001 From: Finn Krein Date: Wed, 2 Jun 2021 15:05:57 +0200 Subject: [PATCH 06/11] Fix elements disappearing when scaling them near 0. element.getCTM() returns `null` if one of the transformation matrices is zero. This fix uses get_transform_matrix instead. --- client-data/js/intersect.js | 23 +++++++++++++++++++++-- client-data/tools/hand/hand.js | 20 ++++++++------------ 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/client-data/js/intersect.js b/client-data/js/intersect.js index 705bcea2..13c37a37 100644 --- a/client-data/js/intersect.js +++ b/client-data/js/intersect.js @@ -28,6 +28,25 @@ if (!SVGGraphicsElement.prototype.transformedBBox || !SVGGraphicsElement.prototy [pointInTransformedBBox, transformedBBoxIntersects] = (function () { + var get_transform_matrix = function (elem) { + // Returns the first translate or transform matrix or makes one + var transform = null; + for (var i = 0; i < elem.transform.baseVal.numberOfItems; ++i) { + var baseVal = elem.transform.baseVal[i]; + // quick tests showed that even if one changes only the fields e and f or uses createSVGTransformFromMatrix + // the brower may add a SVG_TRANSFORM_MATRIX instead of a SVG_TRANSFORM_TRANSLATE + if (baseVal.type === SVGTransform.SVG_TRANSFORM_MATRIX) { + transform = baseVal; + break; + } + } + if (transform == null) { + transform = elem.transform.baseVal.createSVGTransformFromMatrix(Tools.svg.createSVGMatrix()); + elem.transform.baseVal.appendItem(transform); + } + return transform.matrix; + } + var transformRelative = function (m,t) { return [ m.a*t[0]+m.c*t[1], @@ -44,7 +63,7 @@ if (!SVGGraphicsElement.prototype.transformedBBox || !SVGGraphicsElement.prototy SVGGraphicsElement.prototype.transformedBBox = function (scale=1) { bbox = this.getBBox(); - tmatrix = this.getCTM(); + tmatrix = get_transform_matrix(this); tmatrix.e /= scale; tmatrix.f /= scale; return { @@ -61,7 +80,7 @@ if (!SVGGraphicsElement.prototype.transformedBBox || !SVGGraphicsElement.prototy width: this.width.baseVal.value, height: this.height.baseVal.value }; - tmatrix = this.getCTM(); + tmatrix = get_transform_matrix(this); tmatrix.e /= scale; tmatrix.f /= scale; return { diff --git a/client-data/tools/hand/hand.js b/client-data/tools/hand/hand.js index 6c94f114..dcb000dd 100644 --- a/client-data/tools/hand/hand.js +++ b/client-data/tools/hand/hand.js @@ -166,7 +166,7 @@ function showSelectionButtons() { var scale = getScale(); - var selectionBBox = selectionRect.transformedBBox(scale); + var selectionBBox = selectionRect.transformedBBox(); for (var i = 0; i < selectionButtons.length; i++) { selectionButtons[i].drawCallback(selectionButtons[i], selectionBBox, @@ -209,14 +209,12 @@ evt.preventDefault(); hideSelectionButtons(); selectorState = selectorStates.transform; - var scale = getScale(); - var bbox = selectionRect.transformedBBox(scale); + var bbox = selectionRect.transformedBBox(); selected = { x: bbox.r[0], y: bbox.r[1], w: bbox.a[0], h: bbox.b[1], - s: scale }; transform_elements = selected_els.map(function(el) { var tmatrix = get_transform_matrix(el); @@ -250,12 +248,11 @@ function calculateSelection() { - var scale = getScale(); - var selectionTBBox = selectionRect.transformedBBox(scale); + var selectionTBBox = selectionRect.transformedBBox(); var elements = Tools.drawingArea.children; var selected = []; for (var i=0; i < elements.length; i++) { - if (transformedBBoxIntersects(selectionTBBox, elements[i].transformedBBox(scale))) + if (transformedBBoxIntersects(selectionTBBox, elements[i].transformedBBox())) selected.push(Tools.drawingArea.children[i]); } return selected; @@ -297,11 +294,10 @@ function scaleSelection(x, y) { var rx = (x - selected.x)/(selected.w); var ry = (y - selected.y)/(selected.h); - var scale = getScale(); var msgs = selected_els.map(function(el, i) { var oldTransform = transform_elements[i]; - var x = el.transformedBBox(scale).r[0]; - var y = el.transformedBBox(scale).r[1]; + var x = el.transformedBBox().r[0]; + var y = el.transformedBBox().r[1]; var a = oldTransform.a * rx; var d = oldTransform.d * ry; var e = selected.x * (1 - rx) - x * a + @@ -349,7 +345,7 @@ } function resetSelectionRect() { - var bbox = selectionRect.transformedBBox(getScale()); + var bbox = selectionRect.transformedBBox(); var tmatrix = get_transform_matrix(selectionRect); selectionRect.x.baseVal.value = bbox.r[0]; selectionRect.y.baseVal.value = bbox.r[1]; @@ -418,7 +414,7 @@ } if (button) { button.clickCallback(x, y, evt); - } else if (pointInTransformedBBox([x, y], selectionRect.transformedBBox(scale))) { + } else if (pointInTransformedBBox([x, y], selectionRect.transformedBBox())) { hideSelectionButtons(); startMovingElements(x, y, evt); } else if (Tools.drawingArea.contains(evt.target)) { From 4887f539de8087893070d3b0d3a35cfcf2c4cdfc Mon Sep 17 00:00:00 2001 From: Ophir LOJKINE Date: Sat, 5 Jun 2021 00:58:26 +0200 Subject: [PATCH 07/11] remove unused variable --- client-data/tools/hand/hand.js | 1 - 1 file changed, 1 deletion(-) diff --git a/client-data/tools/hand/hand.js b/client-data/tools/hand/hand.js index dcb000dd..b8d18540 100644 --- a/client-data/tools/hand/hand.js +++ b/client-data/tools/hand/hand.js @@ -404,7 +404,6 @@ } function clickSelector(x, y, evt) { - var scale = getScale(); selectionRect = selectionRect || createSelectorRect(); var button = null; for (var i=0; i Date: Sat, 5 Jun 2021 01:04:22 +0200 Subject: [PATCH 08/11] Fix incoherent formatting --- client-data/tools/hand/hand.js | 76 +++++++++++++++++----------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/client-data/tools/hand/hand.js b/client-data/tools/hand/hand.js index b8d18540..2c49e9ca 100644 --- a/client-data/tools/hand/hand.js +++ b/client-data/tools/hand/hand.js @@ -42,40 +42,40 @@ var selectionButtons = {}; selectionButtons["delete"] = createButton("delete", "delete", 22, 22, - function(me, bbox, s) { + function (me, bbox, s) { me.width.baseVal.value = me.origWidth / s; me.height.baseVal.value = me.origHeight / s; me.x.baseVal.value = bbox.r[0]; me.y.baseVal.value = bbox.r[1] - (me.origHeight + 3) / s; me.style.display = ""; }, - deleteSelection); + deleteSelection); selectionButtons["duplicate"] = createButton("duplicate", "duplicate", 22, 22, - function(me, bbox, s) { + function (me, bbox, s) { me.width.baseVal.value = me.origWidth / s; me.height.baseVal.value = me.origHeight / s; me.x.baseVal.value = bbox.r[0] + (me.origWidth + 2) / s; me.y.baseVal.value = bbox.r[1] - (me.origHeight + 3) / s; me.style.display = ""; }, - duplicateSelection); + duplicateSelection); selectionButtons["scale"] = createButton("scaleHandle", "handle", 14, 14, - function(me, bbox, s) { + function (me, bbox, s) { me.width.baseVal.value = me.origWidth / s; me.height.baseVal.value = me.origHeight / s; - me.x.baseVal.value = bbox.r[0] + bbox.a[0] - me.origWidth/(2*s); - me.y.baseVal.value = bbox.r[1] + bbox.b[1] - me.origHeight/(2*s); + me.x.baseVal.value = bbox.r[0] + bbox.a[0] - me.origWidth / (2 * s); + me.y.baseVal.value = bbox.r[1] + bbox.b[1] - me.origHeight / (2 * s); me.style.display = ""; }, - startScalingTransform); + startScalingTransform); for (i in blockedSelectionButtons) { delete selectionButtons[blockedSelectionButtons[i]]; } - selectionButtons = Object.keys(selectionButtons).map(function(k) { - return selectionButtons[k]; - }); + selectionButtons = Object.keys(selectionButtons).map(function (k) { + return selectionButtons[k]; + }); var getScale = Tools.getScale; @@ -87,9 +87,9 @@ els.unshift(a); a = a.parentElement; } - var parentMathematics = els.find(function(el) { - return el.getAttribute("class") === "MathElement"; - }); + var parentMathematics = els.find(function (el) { + return el.getAttribute("class") === "MathElement"; + }); if ((parentMathematics) && parentMathematics.tagName === "svg") { target = parentMathematics; } @@ -97,12 +97,12 @@ } function deleteSelection() { - var msgs = selected_els.map(function(el) { - return ({ - "type": "delete", - "id": el.id - }); - }); + var msgs = selected_els.map(function (el) { + return ({ + "type": "delete", + "id": el.id + }); + }); var data = { _children: msgs } @@ -116,7 +116,7 @@ || (selected_els.length == 0)) return; var msgs = []; var newids = []; - for (var i=0; i Date: Sat, 5 Jun 2021 01:14:30 +0200 Subject: [PATCH 09/11] Fix selection UI remaining visible but inactive when switching tools --- client-data/tools/hand/hand.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/client-data/tools/hand/hand.js b/client-data/tools/hand/hand.js index 2c49e9ca..048681c5 100644 --- a/client-data/tools/hand/hand.js +++ b/client-data/tools/hand/hand.js @@ -491,15 +491,18 @@ } function switchTool() { - selected = null; + onquit(); if (handTool.secondary.active) { window.addEventListener("keydown", deleteShortcut); window.addEventListener("keydown", duplicateShortcut); } - else { - window.removeEventListener("keydown", deleteShortcut); - window.removeEventListener("keydown", duplicateShortcut); - } + } + + function onquit() { + selected = null; + hideSelectionUI(); + window.removeEventListener("keydown", deleteShortcut); + window.removeEventListener("keydown", duplicateShortcut); } var handTool = { //The new tool @@ -510,6 +513,7 @@ "move": move, "release": release, }, + "onquit": onquit, "secondary": { "name": "Selector", "icon": "tools/hand/selector.svg", From 516ff8f214893417f448c0a012336634b772d833 Mon Sep 17 00:00:00 2001 From: Ophir LOJKINE Date: Sat, 5 Jun 2021 02:00:36 +0200 Subject: [PATCH 10/11] small svg and js optimizations --- client-data/tools/hand/delete.svg | 18 ++----- client-data/tools/hand/duplicate.svg | 21 +++----- client-data/tools/hand/hand.js | 74 +++++++++++++--------------- 3 files changed, 42 insertions(+), 71 deletions(-) diff --git a/client-data/tools/hand/delete.svg b/client-data/tools/hand/delete.svg index c6d70e22..5b90f8ff 100644 --- a/client-data/tools/hand/delete.svg +++ b/client-data/tools/hand/delete.svg @@ -1,17 +1,5 @@ - - Instagram icon - - - - - - - - - - Delete icon - - - + + Delete + diff --git a/client-data/tools/hand/duplicate.svg b/client-data/tools/hand/duplicate.svg index 6a410369..5285eab4 100644 --- a/client-data/tools/hand/duplicate.svg +++ b/client-data/tools/hand/duplicate.svg @@ -1,17 +1,8 @@ - - Instagram icon - - - - - - - - - - Duplicate icon - - - + + Duplicate + + + + diff --git a/client-data/tools/hand/hand.js b/client-data/tools/hand/hand.js index 048681c5..8f5c5425 100644 --- a/client-data/tools/hand/hand.js +++ b/client-data/tools/hand/hand.js @@ -39,43 +39,41 @@ var selectorState = selectorStates.pointing; var last_sent = 0; var blockedSelectionButtons = Tools.server_config.BLOCKED_SELECTION_BUTTONS; - var selectionButtons = {}; - - selectionButtons["delete"] = createButton("delete", "delete", 22, 22, - function (me, bbox, s) { - me.width.baseVal.value = me.origWidth / s; - me.height.baseVal.value = me.origHeight / s; - me.x.baseVal.value = bbox.r[0]; - me.y.baseVal.value = bbox.r[1] - (me.origHeight + 3) / s; - me.style.display = ""; - }, - deleteSelection); - - selectionButtons["duplicate"] = createButton("duplicate", "duplicate", 22, 22, - function (me, bbox, s) { - me.width.baseVal.value = me.origWidth / s; - me.height.baseVal.value = me.origHeight / s; - me.x.baseVal.value = bbox.r[0] + (me.origWidth + 2) / s; - me.y.baseVal.value = bbox.r[1] - (me.origHeight + 3) / s; - me.style.display = ""; - }, - duplicateSelection); - selectionButtons["scale"] = createButton("scaleHandle", "handle", 14, 14, - function (me, bbox, s) { - me.width.baseVal.value = me.origWidth / s; - me.height.baseVal.value = me.origHeight / s; - me.x.baseVal.value = bbox.r[0] + bbox.a[0] - me.origWidth / (2 * s); - me.y.baseVal.value = bbox.r[1] + bbox.b[1] - me.origHeight / (2 * s); - me.style.display = ""; - }, - startScalingTransform); + var selectionButtons = [ + createButton("delete", "delete", 24, 24, + function (me, bbox, s) { + me.width.baseVal.value = me.origWidth / s; + me.height.baseVal.value = me.origHeight / s; + me.x.baseVal.value = bbox.r[0]; + me.y.baseVal.value = bbox.r[1] - (me.origHeight + 3) / s; + me.style.display = ""; + }, + deleteSelection), + + createButton("duplicate", "duplicate", 24, 24, + function (me, bbox, s) { + me.width.baseVal.value = me.origWidth / s; + me.height.baseVal.value = me.origHeight / s; + me.x.baseVal.value = bbox.r[0] + (me.origWidth + 2) / s; + me.y.baseVal.value = bbox.r[1] - (me.origHeight + 3) / s; + me.style.display = ""; + }, + duplicateSelection), + + createButton("scaleHandle", "handle", 14, 14, + function (me, bbox, s) { + me.width.baseVal.value = me.origWidth / s; + me.height.baseVal.value = me.origHeight / s; + me.x.baseVal.value = bbox.r[0] + bbox.a[0] - me.origWidth / (2 * s); + me.y.baseVal.value = bbox.r[1] + bbox.b[1] - me.origHeight / (2 * s); + me.style.display = ""; + }, + startScalingTransform) + ]; for (i in blockedSelectionButtons) { delete selectionButtons[blockedSelectionButtons[i]]; } - selectionButtons = Object.keys(selectionButtons).map(function (k) { - return selectionButtons[k]; - }); var getScale = Tools.getScale; @@ -149,12 +147,7 @@ } function createButton(name, icon, width, height, drawCallback, clickCallback) { - var shape = Tools.createSVGElement("image", { - id: name + "Icon", - href: "tools/hand/" + icon + ".svg", - width: width, - height: height, - }); + var shape = Tools.createSVGElement("use", {href: "tools/hand/" + icon + ".svg#root"}); shape.style.display = "none"; shape.origWidth = width; shape.origHeight = height; @@ -405,10 +398,9 @@ function clickSelector(x, y, evt) { selectionRect = selectionRect || createSelectorRect(); - var button = null; for (var i = 0; i < selectionButtons.length; i++) { if (selectionButtons[i].contains(evt.target)) { - button = selectionButtons[i]; + var button = selectionButtons[i]; } } if (button) { From 37e556e490e4059380fea27fe2a1ab37373010e7 Mon Sep 17 00:00:00 2001 From: Ophir LOJKINE Date: Sat, 5 Jun 2021 02:01:42 +0200 Subject: [PATCH 11/11] version bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3a8ae026..7bbabeb7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "whitebophir", "description": "Online collaborative whiteboard", - "version": "1.11.0", + "version": "1.12.0", "keywords": [ "collaborative", "whiteboard"