diff --git a/client-data/js/board.js b/client-data/js/board.js
index ebb0d03d..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});
}
}
@@ -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..13c37a37 100644
--- a/client-data/js/intersect.js
+++ b/client-data/js/intersect.js
@@ -28,20 +28,48 @@ if (!SVGGraphicsElement.prototype.transformedBBox || !SVGGraphicsElement.prototy
[pointInTransformedBBox,
transformedBBoxIntersects] = (function () {
- let applyTransform = function (m,t) {
+ 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],
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 = get_transform_matrix(this);
+ 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])
}
}
@@ -52,15 +80,17 @@ 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 {
- 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])
}
}
- 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 +109,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/delete.svg b/client-data/tools/hand/delete.svg
new file mode 100644
index 00000000..5b90f8ff
--- /dev/null
+++ b/client-data/tools/hand/delete.svg
@@ -0,0 +1,5 @@
+
+
diff --git a/client-data/tools/hand/duplicate.svg b/client-data/tools/hand/duplicate.svg
new file mode 100644
index 00000000..5285eab4
--- /dev/null
+++ b/client-data/tools/hand/duplicate.svg
@@ -0,0 +1,8 @@
+
+
diff --git a/client-data/tools/hand/hand.js b/client-data/tools/hand/hand.js
index 8f95db67..8f5c5425 100644
--- a/client-data/tools/hand/hand.js
+++ b/client-data/tools/hand/hand.js
@@ -25,32 +25,108 @@
*/
(function hand() { //Code isolation
- const selectorStates = {
+ 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;
+ var blockedSelectionButtons = Tools.server_config.BLOCKED_SELECTION_BUTTONS;
+ 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]];
+ }
+
+ var getScale = Tools.getScale;
function getParentMathematics(el) {
- var target
- var a = 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");
+ 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 deleteSelection() {
+ var msgs = selected_els.map(function (el) {
+ return ({
+ "type": "delete",
+ "id": el.id
+ });
+ });
+ var data = {
+ _children: msgs
+ }
+ Tools.drawAndSend(data);
+ selected_els = [];
+ hideSelectionUI();
+ }
+
+ function duplicateSelection() {
+ if (!(selectorState == selectorStates.pointing)
+ || (selected_els.length == 0)) return;
+ var msgs = [];
+ var newids = [];
+ for (var i = 0; i < selected_els.length; i++) {
+ var id = selected_els[i].id;
+ msgs[i] = {
+ type: "copy",
+ id: id,
+ newid: Tools.generateUID(id[0])
+ };
+ newids[i] = id;
+ }
+ Tools.drawAndSend({ _children: msgs });
+ selected_els = newids.map(function (id) {
+ return Tools.svg.getElementById(id);
+ });
}
function createSelectorRect() {
@@ -70,22 +146,82 @@
return shape;
}
+ function createButton(name, icon, width, height, drawCallback, clickCallback) {
+ var shape = Tools.createSVGElement("use", {href: "tools/hand/" + icon + ".svg#root"});
+ 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();
+ 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;
+ selectorState = selectorStates.transform;
+ currentTransform = moveSelection;
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(el => {
- let 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
+ };
});
- {
- let tmatrix = get_translate_matrix(selectionRect);
- selectionRectTranslation = { x: tmatrix.e, y: 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 bbox = selectionRect.transformedBBox();
+ selected = {
+ x: bbox.r[0],
+ y: bbox.r[1],
+ w: bbox.a[0],
+ h: bbox.b[1],
+ };
+ 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 = {
+ a: tmatrix.a, d: tmatrix.d,
+ e: tmatrix.e, f: tmatrix.f
+ };
+ currentTransform = scaleSelection;
}
function startSelector(x, y, evt) {
@@ -98,42 +234,93 @@
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;
}
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 selectionTBBox = selectionRect.transformedBBox();
+ var elements = Tools.drawingArea.children;
+ var selected = [];
+ for (var i = 0; i < elements.length; i++) {
+ if (transformedBBoxIntersects(selectionTBBox, elements[i].transformedBBox()))
+ 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) => {
+ var msgs = selected_els.map(function (el, i) {
+ var oldTransform = transform_elements[i];
return {
type: "update",
id: el.id,
- deltax: dx + translation_elements[i].x,
- deltay: dy + translation_elements[i].y
- }
+ 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
};
- {
- let tmatrix = get_translate_matrix(selectionRect);
- tmatrix.e = dx + selectionRectTranslation.x;
- tmatrix.f = dy + selectionRectTranslation.y;
+ 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 msgs = selected_els.map(function (el, i) {
+ var oldTransform = transform_elements[i];
+ 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 +
+ (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_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;
@@ -150,23 +337,34 @@
rect.height.baseVal.value = Math.abs(y - selected.y);
}
- function get_translate_matrix(elem) {
+ function resetSelectionRect() {
+ var bbox = selectionRect.transformedBBox();
+ 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) {
@@ -178,9 +376,19 @@
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);
+ newElement.id = data.newid;
+ Tools.drawingArea.appendChild(newElement);
+ break;
+ case "delete":
+ data.tool = "Eraser";
+ messageForTool(data);
break;
default:
throw new Error("Mover: 'move' instruction with unknown type. ", data);
@@ -189,15 +397,23 @@
}
function clickSelector(x, y, evt) {
- var scale = Tools.drawingArea.getCTM().a
- selectionRect = selectionRect ?? createSelectorRect();
- if (pointInTransformedBBox([x, y], selectionRect.transformedBBox(scale))) {
+ selectionRect = selectionRect || createSelectorRect();
+ for (var i = 0; i < selectionButtons.length; i++) {
+ if (selectionButtons[i].contains(evt.target)) {
+ var button = selectionButtons[i];
+ }
+ }
+ if (button) {
+ button.clickCallback(x, y, evt);
+ } else if (pointInTransformedBBox([x, y], selectionRect.transformedBBox())) {
+ hideSelectionButtons();
startMovingElements(x, y, evt);
} else if (Tools.drawingArea.contains(evt.target)) {
- selectionRect.style.display = "none";
+ hideSelectionUI();
selected_els = [getParentMathematics(evt.target)];
startMovingElements(x, y, evt);
} else {
+ hideSelectionButtons();
startSelector(x, y, evt);
}
}
@@ -206,18 +422,20 @@
if (selectorState == selectorStates.selecting) {
selected_els = calculateSelection();
if (selected_els.length == 0) {
- selectionRect.style.display = "none";
+ hideSelectionUI();
}
- }
- translation_elements = [];
+ } else if (selectorState == selectorStates.transform)
+ resetSelectionRect();
+ if (selected_els.length != 0) showSelectionButtons();
+ 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);
}
}
@@ -252,8 +470,31 @@
selected = null;
}
+ function deleteShortcut(e) {
+ if (e.key == "Delete" &&
+ !e.target.matches("input[type=text], textarea"))
+ deleteSelection();
+ }
+
+ function duplicateShortcut(e) {
+ if (e.key == "d" &&
+ !e.target.matches("input[type=text], textarea"))
+ duplicateSelection();
+ }
+
function switchTool() {
+ onquit();
+ if (handTool.secondary.active) {
+ window.addEventListener("keydown", deleteShortcut);
+ window.addEventListener("keydown", duplicateShortcut);
+ }
+ }
+
+ function onquit() {
selected = null;
+ hideSelectionUI();
+ window.removeEventListener("keydown", deleteShortcut);
+ window.removeEventListener("keydown", duplicateShortcut);
}
var handTool = { //The new tool
@@ -264,6 +505,7 @@
"move": move,
"release": release,
},
+ "onquit": onquit,
"secondary": {
"name": "Selector",
"icon": "tools/hand/selector.svg",
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 @@
+
+
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"
diff --git a/server/boardData.js b/server/boardData.js
index 54efb087..d7867ccb 100644
--- a/server/boardData.js
+++ b/server/boardData.js
@@ -100,6 +100,28 @@ class BoardData {
this.delaySave();
}
+ /** Copy elements in the board
+ * @param {string} id - Identifier of the data to copy.
+ * @param {BoardElem} data - Object containing the id of the new copied element.
+ */
+ copy(id, data) {
+ var obj = this.board[id];
+ var newid = data.newid;
+ if (obj) {
+ var newobj = JSON.parse(JSON.stringify(obj));
+ newobj.id = newid;
+ if (newobj._children) {
+ for (var child of newobj._children) {
+ child.parent = newid;
+ }
+ }
+ this.board[newid] = newobj;
+ } else {
+ log("Copied object does not exist in board.", {object: id});
+ }
+ this.delaySave();
+ }
+
/** Removes data from the board
* @param {string} id - Identifier of the data to delete.
*/
@@ -137,6 +159,9 @@ class BoardData {
case "update":
if (id) this.update(id, message);
break;
+ case "copy":
+ if (id) this.copy(id, message);
+ break;
case "child":
this.addChild(message.parent, message);
break;
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",