From 6bbb8c8d60afaca7b13f6aba79e40761b3032320 Mon Sep 17 00:00:00 2001 From: Finn Krein Date: Thu, 20 May 2021 16:04:10 +0200 Subject: [PATCH 1/8] Add selector tool --- client-data/board.css | 3 + client-data/board.html | 1 + client-data/js/intersect.js | 92 +++++++++++ client-data/tools/hand/hand.js | 233 +++++++++++++++++++++++----- client-data/tools/hand/selector.svg | 19 +++ server/boardData.js | 26 ++++ server/sockets.js | 3 + 7 files changed, 340 insertions(+), 37 deletions(-) create mode 100644 client-data/js/intersect.js create mode 100644 client-data/tools/hand/selector.svg diff --git a/client-data/board.css b/client-data/board.css index cdab39cb..f83dbb6c 100644 --- a/client-data/board.css +++ b/client-data/board.css @@ -276,6 +276,9 @@ circle.opcursor { transition: 0s; } +#board #selectionRect { + fill: none; +} /* Internet Explorer specific CSS */ @media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) { diff --git a/client-data/board.html b/client-data/board.html index b5f65940..c894bfa6 100644 --- a/client-data/board.html +++ b/client-data/board.html @@ -88,6 +88,7 @@ + diff --git a/client-data/js/intersect.js b/client-data/js/intersect.js new file mode 100644 index 00000000..3cd09b8c --- /dev/null +++ b/client-data/js/intersect.js @@ -0,0 +1,92 @@ +/** + * INTERSEC + ********************************************************* + * @licstart The following is the entire license notice for the + * JavaScript code in this page. + * + * Copyright (C) 2021 Ophir LOJKINE + * + * + * The JavaScript code in this page is free software: you can + * redistribute it and/or modify it under the terms of the GNU + * General Public License (GNU GPL) as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) + * any later version. The code is distributed WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU GPL for more details. + * + * As additional permission under GNU GPL version 3 section 7, you + * may distribute non-source (e.g., minimized or compacted) forms of + * that code without the copy of the GNU GPL normally required by + * section 4, provided you include this license notice and a URL + * through which recipients can access the Corresponding Source. + * + * @licend + */ + +if (!SVGGraphicsElement.prototype.transformedBBox || !SVGGraphicsElement.prototype.transformedBBoxContains) { + [pointInTransformedBBox, + transformedBBoxIntersects] = (function () { + + let applyTransform = function (m,t) { + return [ + m.a*t[0]+m.c*t[1], + m.b*t[0]+m.d*t[1] + ] + } + + SVGGraphicsElement.prototype.transformedBBox = function (scale=1) { + bbox = this.getBBox(); + tmatrix = this.getCTM(); + 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]) + } + } + + SVGSVGElement.prototype.transformedBBox = function (scale=1) { + bbox = { + x: this.x.baseVal.value, + y: this.y.baseVal.value, + width: this.width.baseVal.value, + height: this.height.baseVal.value + }; + tmatrix = this.getCTM(); + 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]) + } + } + + let 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; + var c2 = (d[1]*a[0]-d[0]*a[1]) / idet; + return (c1>=0 && c1<=1 && c2>=0 && c2<=1) + } + + SVGGraphicsElement.prototype.transformedBBoxContains = function (x,y) { + return pointInTransformedBBox([x, y], this.transformedBBox()) + } + + function transformedBBoxIntersects(bbox_a,bbox_b) { + var corners = [ + bbox_b.r, + [bbox_b.r[0] + bbox_b.a[0], bbox_b.r[1] + bbox_b.a[1]], + [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)) + } + + SVGGraphicsElement.prototype.transformedBBoxIntersects= function (bbox) { + return transformedBBoxIntersects(this.transformedBBox(),bbox) + } + + return [pointInTransformedBBox, + transformedBBoxIntersects] + })(); +} diff --git a/client-data/tools/hand/hand.js b/client-data/tools/hand/hand.js index ffba5ee7..743d63b9 100644 --- a/client-data/tools/hand/hand.js +++ b/client-data/tools/hand/hand.js @@ -25,31 +25,144 @@ */ (function hand() { //Code isolation - var selected = null; + const selectorStates = { + pointing: 0, + selecting: 1, + moving: 2 + } + var selected = null; + var selected_els = []; + var selectionRect = createSelectorRect(); + var selectionRectTranslation; + var translation_elements = []; + var selectorState = selectorStates.pointing; var last_sent = 0; + function inRect(x ,y , rect) { + return (x>=rect.x && x<=rect.x+rect.width) && + (y>=rect.y && y>=rect.w+rect.height) + } - function startMovingElement(x, y, evt) { - //Prevent the press from being interpreted by the browser - evt.preventDefault(); - if (!evt.target || !Tools.drawingArea.contains(evt.target)) return; - var tmatrix = get_translate_matrix(evt.target); - selected = { x: x - tmatrix.e, y: y - tmatrix.f, elem: evt.target }; - } - - function moveElement(x, y) { - if (!selected) return; - var deltax = x - selected.x; - var deltay = y - selected.y; - var msg = { type: "update", id: selected.elem.id, deltax: deltax, deltay: deltay }; - var now = performance.now(); - if (now - last_sent > 70) { - last_sent = now; - Tools.drawAndSend(msg); - } else { - draw(msg); - } + function intersectRect(rect1 , rect2) { + return !( + (rect1.x+rect1.width<=rect2.x) || + (rect2.x+rect2.width<=rect1.x) || + (rect1.y+rect1.height<=rect2.y) || + (rect2.y+rect2.height<=rect1.y) + ) + } + + function getParentMathematics(el) { + var target + var a = el + var els = []; + while (a) { + els.unshift(a); + a = a.parentElement; + } + var parentMathematics = els.find(el => el.getAttribute("class") === "MathElement"); + if ((parentMathematics) && parentMathematics.tagName === "svg") { + target = parentMathematics; + } + return target ?? el; + } + + function createSelectorRect() { + var shape = Tools.createSVGElement("rect"); + shape.id = "selectionRect"; + shape.x.baseVal.value = 0; + shape.y.baseVal.value = 0; + shape.width.baseVal.value = 0; + shape.height.baseVal.value = 0; + shape.setAttribute("stroke", "black"); + shape.setAttribute("stroke-width", 3); + shape.setAttribute("fill", "none"); + shape.setAttribute("stroke-dasharray", "5 5"); + shape.setAttribute("opacity", 1); + Tools.svg.appendChild(shape); + return shape; + } + + function startMovingElements(x, y, evt) { + evt.preventDefault(); + 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 + }); + 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}; } + } + + function startSelector(x, y , evt) { + evt.preventDefault(); + selected = {x: x, y: y}; + selected_els= []; + selectorState = selectorStates.selecting; + selectionRect.x.baseVal.value = x; + selectionRect.y.baseVal.value = y; + selectionRect.width.baseVal.value = 0; + selectionRect.height.baseVal.value = 0; + selectionRect.style.display = ""; + tmatrix = get_translate_matrix(selectionRect); + tmatrix.e = 0; + tmatrix.f = 0; + } + + + 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) + ) + }); + } + + 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 msg = { + type: "batch", + msgs: msgs + }; + { + let 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; + Tools.drawAndSend(msg); + } else { + draw(msg); + } + } + + function updateRect(x,y, rect) { + rect.x.baseVal.value = Math.min(x,selected.x); + rect.y.baseVal.value = Math.min(y,selected.y); + rect.width.baseVal.value = Math.abs(x-selected.x); + rect.height.baseVal.value = Math.abs(y-selected.y); + } function get_translate_matrix(elem) { // Returns the first translate or transform matrix or makes one @@ -70,21 +183,66 @@ return translate.matrix; } - function draw(data) { - switch (data.type) { - 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; - break; - - default: - throw new Error("Mover: 'move' instruction with unknown type. ", data); + function draw(data) { + switch (data.type) { + case "batch": + for ([i,msg] of data.msgs.entries()) { + switch (msg.type) { + case "update": + let tmatrix = get_translate_matrix(Tools.svg.getElementById(msg.id)); + tmatrix.e = msg.deltax || 0; + tmatrix.f = msg.deltay || 0; + break; + // Eventually also "delete"? + } + } + break; + 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; + break; + default: + throw new Error("Mover: 'move' instruction with unknown type. ", data); } } + function clickSelector(x ,y , evt) { + var scale = Tools.drawingArea.getCTM().a + selectionRect = selectionRect ?? createSelectorRect(); + if (pointInTransformedBBox([x,y],selectionRect.transformedBBox(scale))) { + startMovingElements(x, y, evt); + } else if (Tools.drawingArea.contains(evt.target)) + { + selectionRect.style.display = "none"; + selected_els = [getParentMathematics(evt.target)]; + startMovingElements(x, y, evt); + } else { + startSelector(x, y, evt); + } + } + + function releaseSelector(x ,y , evt) { + if (selectorState == selectorStates.selecting) { + selected_els = calculateSelection(); + if (selected_els.length == 0) { + selectionRect.style.display = "none"; + } + } + translation_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); + } + } + function startHand(x, y, evt, isTouchEvent) { if (!isTouchEvent) { selected = { @@ -101,17 +259,18 @@ function press(x, y, evt, isTouchEvent) { if (!handTool.secondary.active) startHand(x, y, evt, isTouchEvent); - else startMovingElement(x, y, evt, isTouchEvent); + else clickSelector(x, y, evt, isTouchEvent); } function move(x, y, evt, isTouchEvent) { if (!handTool.secondary.active) moveHand(x, y, evt, isTouchEvent); - else moveElement(x, y, evt, isTouchEvent); + else moveSelector(x, y, evt, isTouchEvent); } function release(x, y, evt, isTouchEvent) { move(x, y, evt, isTouchEvent); + if (handTool.secondary.active) releaseSelector(x, y, evt, isTouchEvent); selected = null; } @@ -128,8 +287,8 @@ "release": release, }, "secondary": { - "name": "Mover", - "icon": "tools/hand/mover.svg", + "name": "Selector", + "icon": "tools/hand/selector.svg", "active": false, "switch": switchTool, }, diff --git a/client-data/tools/hand/selector.svg b/client-data/tools/hand/selector.svg new file mode 100644 index 00000000..20ac9419 --- /dev/null +++ b/client-data/tools/hand/selector.svg @@ -0,0 +1,19 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/server/boardData.js b/server/boardData.js index 2c4f27f9..f99f4554 100644 --- a/server/boardData.js +++ b/server/boardData.js @@ -109,6 +109,32 @@ class BoardData { this.delaySave(); } + /** Process a batch of messages + * @param {envelope} array of messages to be delegated to the other methods + */ + batch(envelope) { + for (const message of envelope.msgs) { + let id = message.id; + switch (message.type) { + case "delete": + if (id) this.delete(id); + break; + case "update": + if (id) this.update(id, message); + break; + case "child": + this.addChild(message.parent, message); + break; + case "batch": + throw new Error("Nested batch message: ", message); + default: + //Add data + if (!id) throw new Error("Invalid message: ", message); + this.set(id, message); + } + } + } + /** Reads data from the board * @param {string} id - Identifier of the element to get. * @returns {BoardElem} The element with the given id, or undefined if no element has this id diff --git a/server/sockets.js b/server/sockets.js index c524efc6..5fcd7c8d 100644 --- a/server/sockets.js +++ b/server/sockets.js @@ -171,6 +171,9 @@ async function saveHistory(boardName, message) { case "child": board.addChild(message.parent, message); break; + case "batch": + board.batch(message); + break; default: //Add data if (!id) throw new Error("Invalid message: ", message); From e5e43b87c639e16fb2f46e27ce329bfb75b28ffe Mon Sep 17 00:00:00 2001 From: Finn Krein Date: Fri, 21 May 2021 18:18:27 +0200 Subject: [PATCH 2/8] Make batch messages consistent. --- client-data/tools/hand/hand.js | 20 ++++++----------- server/boardData.js | 4 +--- server/sockets.js | 39 +++++++++++++++++++--------------- 3 files changed, 29 insertions(+), 34 deletions(-) diff --git a/client-data/tools/hand/hand.js b/client-data/tools/hand/hand.js index 743d63b9..57158937 100644 --- a/client-data/tools/hand/hand.js +++ b/client-data/tools/hand/hand.js @@ -140,8 +140,7 @@ } }) var msg = { - type: "batch", - msgs: msgs + _children: msgs }; { let tmatrix = get_translate_matrix(selectionRect); @@ -184,19 +183,11 @@ } function draw(data) { + if (data._children) { + batchCall(draw, data._children); + } + else { switch (data.type) { - case "batch": - for ([i,msg] of data.msgs.entries()) { - switch (msg.type) { - case "update": - let tmatrix = get_translate_matrix(Tools.svg.getElementById(msg.id)); - tmatrix.e = msg.deltax || 0; - tmatrix.f = msg.deltay || 0; - break; - // Eventually also "delete"? - } - } - break; case "update": var elem = Tools.svg.getElementById(data.id); if (!elem) throw new Error("Mover: Tried to move an element that does not exist."); @@ -208,6 +199,7 @@ throw new Error("Mover: 'move' instruction with unknown type. ", data); } } + } function clickSelector(x ,y , evt) { var scale = Tools.drawingArea.getCTM().a diff --git a/server/boardData.js b/server/boardData.js index f99f4554..8e5ead31 100644 --- a/server/boardData.js +++ b/server/boardData.js @@ -113,7 +113,7 @@ class BoardData { * @param {envelope} array of messages to be delegated to the other methods */ batch(envelope) { - for (const message of envelope.msgs) { + for (const message of envelope._children) { let id = message.id; switch (message.type) { case "delete": @@ -125,8 +125,6 @@ class BoardData { case "child": this.addChild(message.parent, message); break; - case "batch": - throw new Error("Nested batch message: ", message); default: //Add data if (!id) throw new Error("Invalid message: ", message); diff --git a/server/sockets.js b/server/sockets.js index 5fcd7c8d..0e4391e8 100644 --- a/server/sockets.js +++ b/server/sockets.js @@ -160,24 +160,29 @@ function handleMessage(boardName, message, socket) { async function saveHistory(boardName, message) { var id = message.id; + if (!message.tool && !message._children) { + console.error("Received a badly formatted message (no tool). ", message); + } var board = await getBoard(boardName); - switch (message.type) { - case "delete": - if (id) board.delete(id); - break; - case "update": - if (id) board.update(id, message); - break; - case "child": - board.addChild(message.parent, message); - break; - case "batch": - board.batch(message); - break; - default: - //Add data - if (!id) throw new Error("Invalid message: ", message); - board.set(id, message); + if (message._children) { + board.batch(message); + } + else { + switch (message.type) { + case "delete": + if (id) board.delete(id); + break; + case "update": + if (id) board.update(id, message); + break; + case "child": + board.addChild(message.parent, message); + break; + default: + //Add data + if (!id) throw new Error("Invalid message: ", message); + board.set(id, message); + } } } From 9e1bfffa8051f0bc15acadcf5d559d4ac729f432 Mon Sep 17 00:00:00 2001 From: Finn Krein Date: Fri, 21 May 2021 23:55:22 +0200 Subject: [PATCH 3/8] Make selector rect stroke width non-scaling --- client-data/tools/hand/hand.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client-data/tools/hand/hand.js b/client-data/tools/hand/hand.js index 57158937..4f5823b3 100644 --- a/client-data/tools/hand/hand.js +++ b/client-data/tools/hand/hand.js @@ -75,7 +75,8 @@ shape.width.baseVal.value = 0; shape.height.baseVal.value = 0; shape.setAttribute("stroke", "black"); - shape.setAttribute("stroke-width", 3); + shape.setAttribute("stroke-width", 1); + shape.setAttribute("vector-effect", "non-scaling-stroke"); shape.setAttribute("fill", "none"); shape.setAttribute("stroke-dasharray", "5 5"); shape.setAttribute("opacity", 1); From 296dec16bc16281410647da9dbf46318ab6924ac Mon Sep 17 00:00:00 2001 From: Ophir LOJKINE Date: Sun, 23 May 2021 23:46:01 +0200 Subject: [PATCH 4/8] Fix duplicated code on the server (and add type annotations) --- server/boardData.js | 52 ++++++++++++++++++++++++++++----------------- server/sockets.js | 24 ++------------------- 2 files changed, 35 insertions(+), 41 deletions(-) diff --git a/server/boardData.js b/server/boardData.js index 8e5ead31..54efb087 100644 --- a/server/boardData.js +++ b/server/boardData.js @@ -110,26 +110,40 @@ class BoardData { } /** Process a batch of messages - * @param {envelope} array of messages to be delegated to the other methods + * @typedef {{ + * id:string, + * type: "delete" | "update" | "child", + * parent?: string, + * _children?: BoardMessage[], + * } & BoardElem } BoardMessage + * @param {BoardMessage[]} children array of messages to be delegated to the other methods */ - batch(envelope) { - for (const message of envelope._children) { - let id = message.id; - switch (message.type) { - case "delete": - if (id) this.delete(id); - break; - case "update": - if (id) this.update(id, message); - break; - case "child": - this.addChild(message.parent, message); - break; - default: - //Add data - if (!id) throw new Error("Invalid message: ", message); - this.set(id, message); - } + processMessageBatch(children) { + for (const message of children) { + this.processMessage(message); + } + } + + /** Process a single message + * @param {BoardMessage} message instruction to apply to the board + */ + processMessage(message) { + if (message._children) return this.processMessageBatch(message._children); + let id = message.id; + switch (message.type) { + case "delete": + if (id) this.delete(id); + break; + case "update": + if (id) this.update(id, message); + break; + case "child": + this.addChild(message.parent, message); + break; + default: + //Add data + if (!id) throw new Error("Invalid message: ", message); + this.set(id, message); } } diff --git a/server/sockets.js b/server/sockets.js index 0e4391e8..be12ab85 100644 --- a/server/sockets.js +++ b/server/sockets.js @@ -4,7 +4,7 @@ var iolib = require("socket.io"), config = require("./configuration"); /** Map from name to *promises* of BoardData - @type {Object>} + @type {Object>} */ var boards = {}; @@ -159,31 +159,11 @@ function handleMessage(boardName, message, socket) { } async function saveHistory(boardName, message) { - var id = message.id; if (!message.tool && !message._children) { console.error("Received a badly formatted message (no tool). ", message); } var board = await getBoard(boardName); - if (message._children) { - board.batch(message); - } - else { - switch (message.type) { - case "delete": - if (id) board.delete(id); - break; - case "update": - if (id) board.update(id, message); - break; - case "child": - board.addChild(message.parent, message); - break; - default: - //Add data - if (!id) throw new Error("Invalid message: ", message); - board.set(id, message); - } - } + board.processMessage(message); } function generateUID(prefix, suffix) { From d279041aec518664f9d0cc0c98f17cd865e105ea Mon Sep 17 00:00:00 2001 From: Ophir LOJKINE Date: Sun, 23 May 2021 23:51:24 +0200 Subject: [PATCH 5/8] fix indentation --- client-data/tools/hand/hand.js | 337 ++++++++++++++++----------------- 1 file changed, 168 insertions(+), 169 deletions(-) diff --git a/client-data/tools/hand/hand.js b/client-data/tools/hand/hand.js index 4f5823b3..efa478a1 100644 --- a/client-data/tools/hand/hand.js +++ b/client-data/tools/hand/hand.js @@ -25,144 +25,144 @@ */ (function hand() { //Code isolation - const selectorStates = { - pointing: 0, - selecting: 1, - moving: 2 - } - var selected = null; - var selected_els = []; - var selectionRect = createSelectorRect(); - var selectionRectTranslation; - var translation_elements = []; - var selectorState = selectorStates.pointing; + const selectorStates = { + pointing: 0, + selecting: 1, + moving: 2 + } + var selected = null; + var selected_els = []; + var selectionRect = createSelectorRect(); + var selectionRectTranslation; + var translation_elements = []; + var selectorState = selectorStates.pointing; var last_sent = 0; - function inRect(x ,y , rect) { - return (x>=rect.x && x<=rect.x+rect.width) && - (y>=rect.y && y>=rect.w+rect.height) - } - - function intersectRect(rect1 , rect2) { - return !( - (rect1.x+rect1.width<=rect2.x) || - (rect2.x+rect2.width<=rect1.x) || - (rect1.y+rect1.height<=rect2.y) || - (rect2.y+rect2.height<=rect1.y) - ) - } - - function getParentMathematics(el) { - var target - var a = el - var els = []; - while (a) { - els.unshift(a); - a = a.parentElement; + function inRect(x, y, rect) { + return (x >= rect.x && x <= rect.x + rect.width) && + (y >= rect.y && y >= rect.w + rect.height) } - var parentMathematics = els.find(el => el.getAttribute("class") === "MathElement"); - if ((parentMathematics) && parentMathematics.tagName === "svg") { - target = parentMathematics; + + function intersectRect(rect1, rect2) { + return !( + (rect1.x + rect1.width <= rect2.x) || + (rect2.x + rect2.width <= rect1.x) || + (rect1.y + rect1.height <= rect2.y) || + (rect2.y + rect2.height <= rect1.y) + ) } - return target ?? el; - } - function createSelectorRect() { - var shape = Tools.createSVGElement("rect"); - shape.id = "selectionRect"; - shape.x.baseVal.value = 0; - shape.y.baseVal.value = 0; - shape.width.baseVal.value = 0; - shape.height.baseVal.value = 0; - shape.setAttribute("stroke", "black"); - shape.setAttribute("stroke-width", 1); - shape.setAttribute("vector-effect", "non-scaling-stroke"); - shape.setAttribute("fill", "none"); - shape.setAttribute("stroke-dasharray", "5 5"); - shape.setAttribute("opacity", 1); - Tools.svg.appendChild(shape); - return shape; - } + function getParentMathematics(el) { + var target + var a = el + var els = []; + while (a) { + els.unshift(a); + a = a.parentElement; + } + var parentMathematics = els.find(el => el.getAttribute("class") === "MathElement"); + if ((parentMathematics) && parentMathematics.tagName === "svg") { + target = parentMathematics; + } + return target ?? el; + } - function startMovingElements(x, y, evt) { - evt.preventDefault(); - 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 - }); - 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}; + function createSelectorRect() { + var shape = Tools.createSVGElement("rect"); + shape.id = "selectionRect"; + shape.x.baseVal.value = 0; + shape.y.baseVal.value = 0; + shape.width.baseVal.value = 0; + shape.height.baseVal.value = 0; + shape.setAttribute("stroke", "black"); + shape.setAttribute("stroke-width", 1); + shape.setAttribute("vector-effect", "non-scaling-stroke"); + shape.setAttribute("fill", "none"); + shape.setAttribute("stroke-dasharray", "5 5"); + shape.setAttribute("opacity", 1); + Tools.svg.appendChild(shape); + return shape; } - } - function startSelector(x, y , evt) { - evt.preventDefault(); - selected = {x: x, y: y}; - selected_els= []; - selectorState = selectorStates.selecting; - selectionRect.x.baseVal.value = x; - selectionRect.y.baseVal.value = y; - selectionRect.width.baseVal.value = 0; - selectionRect.height.baseVal.value = 0; - selectionRect.style.display = ""; - tmatrix = get_translate_matrix(selectionRect); - tmatrix.e = 0; - tmatrix.f = 0; - } + function startMovingElements(x, y, evt) { + evt.preventDefault(); + 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 + }); + 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 }; + } + } + function startSelector(x, y, evt) { + evt.preventDefault(); + selected = { x: x, y: y }; + selected_els = []; + selectorState = selectorStates.selecting; + selectionRect.x.baseVal.value = x; + selectionRect.y.baseVal.value = y; + selectionRect.width.baseVal.value = 0; + selectionRect.height.baseVal.value = 0; + selectionRect.style.display = ""; + tmatrix = get_translate_matrix(selectionRect); + tmatrix.e = 0; + tmatrix.f = 0; + } - 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) - ) - }); - } - 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 msg = { - _children: msgs - }; - { - let tmatrix = get_translate_matrix(selectionRect); - tmatrix.e = dx + selectionRectTranslation.x; - tmatrix.f = dy + selectionRectTranslation.y; + 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 now = performance.now(); - if (now - last_sent > 70) { - last_sent = now; - Tools.drawAndSend(msg); - } else { - draw(msg); + + 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 msg = { + _children: msgs + }; + { + let 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; + Tools.drawAndSend(msg); + } else { + draw(msg); + } } - } - function updateRect(x,y, rect) { - rect.x.baseVal.value = Math.min(x,selected.x); - rect.y.baseVal.value = Math.min(y,selected.y); - rect.width.baseVal.value = Math.abs(x-selected.x); - rect.height.baseVal.value = Math.abs(y-selected.y); - } + function updateRect(x, y, rect) { + rect.x.baseVal.value = Math.min(x, selected.x); + rect.y.baseVal.value = Math.min(y, selected.y); + rect.width.baseVal.value = Math.abs(x - selected.x); + rect.height.baseVal.value = Math.abs(y - selected.y); + } function get_translate_matrix(elem) { // Returns the first translate or transform matrix or makes one @@ -183,58 +183,57 @@ return translate.matrix; } - function draw(data) { - if (data._children) { - batchCall(draw, data._children); - } - else { - switch (data.type) { - 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; - break; - default: - throw new Error("Mover: 'move' instruction with unknown type. ", data); + function draw(data) { + if (data._children) { + batchCall(draw, data._children); + } + else { + switch (data.type) { + 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; + break; + default: + throw new Error("Mover: 'move' instruction with unknown type. ", data); + } } } - } - function clickSelector(x ,y , evt) { - var scale = Tools.drawingArea.getCTM().a - selectionRect = selectionRect ?? createSelectorRect(); - if (pointInTransformedBBox([x,y],selectionRect.transformedBBox(scale))) { - startMovingElements(x, y, evt); - } else if (Tools.drawingArea.contains(evt.target)) - { - selectionRect.style.display = "none"; - selected_els = [getParentMathematics(evt.target)]; - startMovingElements(x, y, evt); - } else { - startSelector(x, y, evt); + function clickSelector(x, y, evt) { + var scale = Tools.drawingArea.getCTM().a + selectionRect = selectionRect ?? createSelectorRect(); + if (pointInTransformedBBox([x, y], selectionRect.transformedBBox(scale))) { + startMovingElements(x, y, evt); + } else if (Tools.drawingArea.contains(evt.target)) { + selectionRect.style.display = "none"; + selected_els = [getParentMathematics(evt.target)]; + startMovingElements(x, y, evt); + } else { + startSelector(x, y, evt); + } } - } - function releaseSelector(x ,y , evt) { - if (selectorState == selectorStates.selecting) { - selected_els = calculateSelection(); - if (selected_els.length == 0) { - selectionRect.style.display = "none"; - } + function releaseSelector(x, y, evt) { + if (selectorState == selectorStates.selecting) { + selected_els = calculateSelection(); + if (selected_els.length == 0) { + selectionRect.style.display = "none"; + } + } + translation_elements = []; + selectorState = selectorStates.pointing; } - translation_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); + function moveSelector(x, y, evt) { + if (selectorState == selectorStates.selecting) { + updateRect(x, y, selectionRect); + } else if (selectorState == selectorStates.moving) { + moveSelection(x, y, selectionRect); + } } - } function startHand(x, y, evt, isTouchEvent) { if (!isTouchEvent) { @@ -263,7 +262,7 @@ function release(x, y, evt, isTouchEvent) { move(x, y, evt, isTouchEvent); - if (handTool.secondary.active) releaseSelector(x, y, evt, isTouchEvent); + if (handTool.secondary.active) releaseSelector(x, y, evt, isTouchEvent); selected = null; } From 22e866b8aa9a2a7a11c5a98e656ced93e4cc7338 Mon Sep 17 00:00:00 2001 From: Ophir LOJKINE Date: Sun, 23 May 2021 23:53:14 +0200 Subject: [PATCH 6/8] Remove unused functions --- client-data/tools/hand/hand.js | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/client-data/tools/hand/hand.js b/client-data/tools/hand/hand.js index efa478a1..8f95db67 100644 --- a/client-data/tools/hand/hand.js +++ b/client-data/tools/hand/hand.js @@ -38,20 +38,6 @@ var selectorState = selectorStates.pointing; var last_sent = 0; - function inRect(x, y, rect) { - return (x >= rect.x && x <= rect.x + rect.width) && - (y >= rect.y && y >= rect.w + rect.height) - } - - function intersectRect(rect1, rect2) { - return !( - (rect1.x + rect1.width <= rect2.x) || - (rect2.x + rect2.width <= rect1.x) || - (rect1.y + rect1.height <= rect2.y) || - (rect2.y + rect2.height <= rect1.y) - ) - } - function getParentMathematics(el) { var target var a = el From 9209ba2864093277bd086d86e07385342abc8e75 Mon Sep 17 00:00:00 2001 From: Ophir LOJKINE Date: Mon, 24 May 2021 00:06:41 +0200 Subject: [PATCH 7/8] Bump version number --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7717f3fa..3a8ae026 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "whitebophir", "description": "Online collaborative whiteboard", - "version": "1.10.2", + "version": "1.11.0", "keywords": [ "collaborative", "whiteboard" From 16d1140a0d94465fce01cd5ea215c34096f7e1d4 Mon Sep 17 00:00:00 2001 From: Finn Krein Date: Mon, 24 May 2021 09:27:49 +0200 Subject: [PATCH 8/8] Remove unneeded css for selectionRect --- client-data/board.css | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/client-data/board.css b/client-data/board.css index f83dbb6c..7f3e5b8e 100644 --- a/client-data/board.css +++ b/client-data/board.css @@ -276,10 +276,6 @@ circle.opcursor { transition: 0s; } -#board #selectionRect { - fill: none; -} - /* Internet Explorer specific CSS */ @media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) { #chooseColor { @@ -288,4 +284,4 @@ circle.opcursor { label.tool-name[for=chooseColor] { line-height: 10px; } -} \ No newline at end of file +}