Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added document tool to upload images #42

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions client-data/board.html
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
<script src="../tools/text/text.js"></script>
<script src="../tools/eraser/eraser.js"></script>
<script src="../tools/hand/hand.js"></script>
<script src="../tools/document/document.js"></script>
<script src="../tools/grid/grid.js"></script>
<script src="../tools/zoom/zoom.js"></script>
<script src="../js/canvascolor/canvascolor.min.js"></script>
Expand Down
103 changes: 103 additions & 0 deletions client-data/tools/document/document.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
(function documents() { //Code isolation


var xlinkNS = "http://www.w3.org/1999/xlink";
var imgCount = 1;

function assert_count() {
if (Tools.svg.querySelectorAll("image").length >= Tools.server_config.MAX_DOCUMENT_COUNT) {
alert("Too many documents exist already");
throw new Error("Too many documents exist already");
}
}

function onstart() {
var fileInput = document.createElement("input");
fileInput.type = "file";
fileInput.accept = "image/*";
fileInput.click();
fileInput.addEventListener("change", () => {
finnboeger marked this conversation as resolved.
Show resolved Hide resolved
assert_count();

var reader = new FileReader();
reader.readAsDataURL(fileInput.files[0]);

reader.onload = function (e) {
// use canvas to compress image
var image = new Image();
image.src = e.target.result;
image.onload = function () {

assert_count();

var uid = Tools.generateUID("doc"); // doc for document

var ctx, size;
var scale = 1;

do {
// Todo give feedback of processing effort

ctx = document.createElement("canvas").getContext("2d");
ctx.canvas.width = image.width * scale;
ctx.canvas.height = image.height * scale;
ctx.drawImage(image, 0, 0, image.width * scale, image.height * scale);
var dataURL = ctx.canvas.toDataURL("image/webp", 0.7);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably use jpeg by default here... We can make it configurable, though.

https://caniuse.com/#feat=webp

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jpeg does not support transparency, which would require us to check what kind of image was uploaded and if it made use of the alpha channel.
If it is a png with transparency we would not be able to compress it other than by shrinking the dimensions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest using a webp polyfill, e.g. https://github.com/chase-moskal/webp-hero

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's maybe start with JPEG, and then add support for webp, with a polyfill, in a second pull request ? webp-hero won't work as-is with data urls...


// Compressed file size as data url, approximately 1/3 larger than as bytestream
size = dataURL.length;

// attempt again with an image that is at least 10% smaller
scale = scale * Math.sqrt(Math.min(
0.9,
Tools.server_config.MAX_DOCUMENT_SIZE / size
));
} while (size > Tools.server_config.MAX_DOCUMENT_SIZE);

var msg = {
id: uid,
type: "doc",
data: dataURL,
size: size,
w: this.width * scale || 300,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This uses the newly reduced value for scale. I think it was meant to use the previous value, which is the value used with drawImage()

h: this.height * scale || 300,
x: (100 + document.documentElement.scrollLeft) / Tools.scale + 10 * imgCount,
y: (100 + document.documentElement.scrollTop) / Tools.scale + 10 * imgCount
//fileType: fileInput.files[0].type
};

assert_count();

draw(msg);
Tools.send(msg,"Document");
imgCount++;
};
};
// Tools.change(Tools.prevToolName);
});
}

function draw(msg) {
var aspect = msg.w/msg.h;
var img = Tools.createSVGElement("image");
img.id=msg.id;
img.setAttribute("class", "layer-"+Tools.layer);
img.setAttributeNS(xlinkNS, "href", msg.data);
img.x.baseVal.value = msg['x'];
img.y.baseVal.value = msg['y'];
img.setAttribute("width", 400*aspect);
img.setAttribute("height", 400);
Comment on lines +88 to +89
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably have a maximum and a minimum image size, and scale only images that do not fit.

Tools.drawingArea.appendChild(img);

}

Tools.add({
"name": "Document",
//"shortcut": "",
"draw": draw,
"onstart": onstart,
"oneTouch":true,
"icon": "/tools/document/icon.svg",
});

})(); //End of code isolation
17 changes: 17 additions & 0 deletions client-data/tools/document/icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 5 additions & 1 deletion server/boardData.js
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,11 @@ BoardData.load = async function loadBoard(name) {
try {
data = await fs.promises.readFile(boardData.file);
boardData.board = JSON.parse(data);
for (id in boardData.board) boardData.validate(boardData.board[id]);
boardData.existingDocuments = 0;
for (id in boardData.board) {
boardData.validate(boardData.board[id]);
if (boardData.board[id].type === "doc") existingDocuments += 1;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be boardData.existingDocuments?

}
log('disk load', { 'board': boardData.name });
} catch (e) {
log('empty board creation', {
Expand Down
2 changes: 2 additions & 0 deletions server/client_configuration.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ const config = require("./configuration");

/** Settings that should be handed through to the clients */
module.exports = {
"MAX_DOCUMENT_SIZE": config.MAX_DOCUMENT_SIZE,
"MAX_DOCUMENT_COUNT": config.MAX_DOCUMENT_COUNT,
"MAX_BOARD_SIZE": config.MAX_BOARD_SIZE,
"MAX_EMIT_COUNT": config.MAX_EMIT_COUNT,
"MAX_EMIT_COUNT_PERIOD": config.MAX_EMIT_COUNT_PERIOD,
Expand Down
6 changes: 6 additions & 0 deletions server/configuration.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ module.exports = {
/** Maximum value for any x or y on the board */
MAX_BOARD_SIZE: parseInt(process.env['WBO_MAX_BOARD_SIZE']) || 65536,

/** Maximum size of uploaded documents default 1MB */
MAX_DOCUMENT_SIZE: parseInt(process.env['WBO_MAX_DOCUMENT_SIZE']) || 1048576,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Large images failed for me, causing a transport error. The socket.io default maxHttpBufferSize is 1e6, so I changed this to 1e6 - 500.


/** Maximum number of documents allowed */
MAX_DOCUMENT_COUNT: parseInt(process.env['WBO_MAX_DOCUMENT_COUNT']) || 5,

/** Maximum messages per user over the given time period before banning them */
MAX_EMIT_COUNT: parseInt(process.env['WBO_MAX_EMIT_COUNT']) || 128,

Expand Down
25 changes: 24 additions & 1 deletion server/sockets.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ function socketConnection(socket) {
var board = await getBoard(name);
board.users.add(socket.id);
log('board joined', { 'board': board.name, 'users': board.users.size });

return board;
}

Expand All @@ -66,7 +67,7 @@ function socketConnection(socket) {

var lastEmitSecond = Date.now() / config.MAX_EMIT_COUNT_PERIOD | 0;
var emitCount = 0;
socket.on('broadcast', noFail(function onBroadcast(message) {
socket.on('broadcast', noFail(async function onBroadcast(message) {
var currentSecond = Date.now() / config.MAX_EMIT_COUNT_PERIOD | 0;
if (currentSecond === lastEmitSecond) {
emitCount++;
Expand Down Expand Up @@ -96,6 +97,28 @@ function socketConnection(socket) {
return;
}

var boardData;
if (message.data.type === "doc") {
boardData = await getBoard(boardName);

if (boardData.existingDocuments >= config.MAX_DOCUMENT_COUNT) {
console.warn("Received too many documents");
return;
}

if (message.data.data.length > config.MAX_DOCUMENT_SIZE) {
console.warn("Received too large file");
return;
}

boardData.existingDocuments += 1;
} else if (message.data.type === "delete") {
boardData = await getBoard(boardName);

if (boardData.board[message.data.id].type === "doc") {
boardData.existingDocuments -= 1;
}
}
Comment on lines +106 to +126
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be handled in the BoardData class

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, we should also verify the provided URL, to check that it's a data URL.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we would still broadcast the large files to all users in that case

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you are right, I hadn't thought about that. I still think the board-specific code should happen in BoardData, but we should probably set the httpBufferSize option of socket.io in addition, to avoid having to handle and dispatch large messages.


// Save the message in the board
handleMessage(boardName, data, socket);
Expand Down