1441 lines
56 KiB
JavaScript
1441 lines
56 KiB
JavaScript
import { dom } from "@fortawesome/fontawesome-svg-core";
|
|
import Point from "./classes/Point";
|
|
import ReadOnlyService from "./services/ReadOnlyService";
|
|
import InfoService from "./services/InfoService";
|
|
import ThrottlingService from "./services/ThrottlingService";
|
|
import ConfigService from "./services/ConfigService";
|
|
import html2canvas from "html2canvas";
|
|
|
|
const RAD_TO_DEG = 180.0 / Math.PI;
|
|
const DEG_TO_RAD = Math.PI / 180.0;
|
|
const _45_DEG_IN_RAD = 45 * DEG_TO_RAD;
|
|
|
|
const whiteboard = {
|
|
canvas: null,
|
|
ctx: null,
|
|
drawcolor: "black",
|
|
previousToolHtmlElem: null, // useful for handling read-only mode
|
|
tool: "mouse",
|
|
thickness: 4,
|
|
/**
|
|
* @type Point
|
|
*/
|
|
prevPos: new Point(0, 0),
|
|
/**
|
|
* @type Point
|
|
*/
|
|
startCoords: new Point(0, 0),
|
|
drawFlag: false,
|
|
oldGCO: null,
|
|
mouseover: false,
|
|
lineCap: "round", //butt, square
|
|
backgroundGrid: null,
|
|
canvasElement: null,
|
|
cursorContainer: null,
|
|
imgContainer: null,
|
|
svgContainer: null, //For draw prev
|
|
mouseOverlay: null,
|
|
ownCursor: null,
|
|
penSmoothLastCoords: [],
|
|
svgLine: null,
|
|
svgRect: null,
|
|
svgCirle: null,
|
|
drawBuffer: [],
|
|
undoBuffer: [],
|
|
drawId: 0, //Used for undo/redo functions
|
|
imgDragActive: false,
|
|
latestActiveTextBoxId: false, //The id of the latest clicked Textbox (for font and color change)
|
|
pressedKeys: {},
|
|
settings: {
|
|
whiteboardId: "0",
|
|
username: "unknown",
|
|
sendFunction: null,
|
|
backgroundGridUrl: "./images/gb_grid.png",
|
|
},
|
|
lastPointerSentTime: 0,
|
|
/**
|
|
* @type Point
|
|
*/
|
|
lastPointerPosition: new Point(0, 0),
|
|
loadWhiteboard: function (whiteboardContainer, newSettings) {
|
|
const svgns = "http://www.w3.org/2000/svg";
|
|
const _this = this;
|
|
for (const i in newSettings) {
|
|
this.settings[i] = newSettings[i];
|
|
}
|
|
this.settings["username"] = this.settings["username"].replace(/[^0-9a-z]/gi, "");
|
|
|
|
//background grid (repeating image) and smallest screen indication
|
|
_this.backgroundGrid = $(
|
|
`<div style="position: absolute; left:0px; top:0; opacity: 0.2; background-image:url('${_this.settings["backgroundGridUrl"]}'); height: 100%; width: 100%;"></div>`
|
|
);
|
|
// container for background images
|
|
_this.imgContainer = $(
|
|
'<div style="position: absolute; left:0px; top:0; height: 100%; width: 100%;"></div>'
|
|
);
|
|
// whiteboard canvas
|
|
_this.canvasElement = $(
|
|
'<canvas id="whiteboardCanvas" style="position: absolute; left:0px; top:0; cursor:crosshair;"></canvas>'
|
|
);
|
|
// SVG container holding drawing or moving previews
|
|
_this.svgContainer = $(
|
|
'<svg style="position: absolute; top:0px; left:0px;" width="100%" height="100%"></svg>'
|
|
);
|
|
// drag and drop indicator, hidden by default
|
|
_this.dropIndicator = $(
|
|
'<div style="position:absolute; height: 100%; width: 100%; border: 7px dashed gray; text-align: center; top: 0px; left: 0px; color: gray; font-size: 23em; display: none;"><i class="far fa-plus-square" aria-hidden="true"></i></div>'
|
|
);
|
|
// container for other users cursors
|
|
_this.cursorContainer = $(
|
|
'<div style="position: absolute; left:0px; top:0; height: 100%; width: 100%;"></div>'
|
|
);
|
|
// container for texts by users
|
|
_this.textContainer = $(
|
|
'<div class="textcontainer" style="position: absolute; left:0px; top:0; height: 100%; width: 100%; cursor:text;"></div>'
|
|
);
|
|
// mouse overlay for draw callbacks
|
|
_this.mouseOverlay = $(
|
|
'<div id="mouseOverlay" style="cursor:none; position: absolute; left:0px; top:0; height: 100%; width: 100%;"></div>'
|
|
);
|
|
|
|
$(whiteboardContainer)
|
|
.append(_this.backgroundGrid)
|
|
.append(_this.imgContainer)
|
|
.append(_this.canvasElement)
|
|
.append(_this.svgContainer)
|
|
.append(_this.dropIndicator)
|
|
.append(_this.cursorContainer)
|
|
.append(_this.textContainer)
|
|
.append(_this.mouseOverlay);
|
|
|
|
// render newly added icons
|
|
dom.i2svg();
|
|
|
|
this.canvas = $("#whiteboardCanvas")[0];
|
|
this.canvas.height = $(window).height();
|
|
this.canvas.width = $(window).width();
|
|
this.ctx = this.canvas.getContext("2d");
|
|
this.oldGCO = this.ctx.globalCompositeOperation;
|
|
|
|
window.addEventListener("resize", function () {
|
|
// Handel resize
|
|
const dbCp = JSON.parse(JSON.stringify(_this.drawBuffer)); // Copy the buffer
|
|
_this.canvas.width = $(window).width();
|
|
_this.canvas.height = $(window).height(); // Set new canvas height
|
|
_this.drawBuffer = [];
|
|
_this.loadData(dbCp); // draw old content in
|
|
});
|
|
|
|
$(_this.mouseOverlay).on("mousedown touchstart", function (e) {
|
|
_this.mousedown(e);
|
|
});
|
|
|
|
_this.mousedown = function (e) {
|
|
if (_this.imgDragActive || _this.drawFlag) {
|
|
return;
|
|
}
|
|
if (ReadOnlyService.readOnlyActive) return;
|
|
|
|
_this.drawFlag = true;
|
|
|
|
const currentPos = Point.fromEvent(e);
|
|
|
|
if (_this.tool === "pen") {
|
|
_this.penSmoothLastCoords = [
|
|
currentPos.x,
|
|
currentPos.y,
|
|
currentPos.x,
|
|
currentPos.y,
|
|
currentPos.x,
|
|
currentPos.y,
|
|
];
|
|
} else if (_this.tool === "eraser") {
|
|
_this.drawEraserLine(
|
|
currentPos.x,
|
|
currentPos.y,
|
|
currentPos.x,
|
|
currentPos.y,
|
|
_this.thickness
|
|
);
|
|
_this.sendFunction({
|
|
t: _this.tool,
|
|
d: [currentPos.x, currentPos.y, currentPos.x, currentPos.y],
|
|
th: _this.thickness,
|
|
});
|
|
} else if (_this.tool === "line") {
|
|
_this.startCoords = currentPos;
|
|
_this.svgLine = document.createElementNS(svgns, "line");
|
|
_this.svgLine.setAttribute("stroke", "gray");
|
|
_this.svgLine.setAttribute("stroke-dasharray", "5, 5");
|
|
_this.svgLine.setAttribute("x1", currentPos.x);
|
|
_this.svgLine.setAttribute("y1", currentPos.y);
|
|
_this.svgLine.setAttribute("x2", currentPos.x);
|
|
_this.svgLine.setAttribute("y2", currentPos.y);
|
|
_this.svgContainer.append(_this.svgLine);
|
|
} else if (_this.tool === "rect" || _this.tool === "recSelect") {
|
|
_this.svgContainer.find("rect").remove();
|
|
_this.svgRect = document.createElementNS(svgns, "rect");
|
|
_this.svgRect.setAttribute("stroke", "gray");
|
|
_this.svgRect.setAttribute("stroke-dasharray", "5, 5");
|
|
_this.svgRect.setAttribute("style", "fill-opacity:0.0;");
|
|
_this.svgRect.setAttribute("x", currentPos.x);
|
|
_this.svgRect.setAttribute("y", currentPos.y);
|
|
_this.svgRect.setAttribute("width", 0);
|
|
_this.svgRect.setAttribute("height", 0);
|
|
_this.svgContainer.append(_this.svgRect);
|
|
_this.startCoords = currentPos;
|
|
} else if (_this.tool === "circle") {
|
|
_this.svgCirle = document.createElementNS(svgns, "circle");
|
|
_this.svgCirle.setAttribute("stroke", "gray");
|
|
_this.svgCirle.setAttribute("stroke-dasharray", "5, 5");
|
|
_this.svgCirle.setAttribute("style", "fill-opacity:0.0;");
|
|
_this.svgCirle.setAttribute("cx", currentPos.x);
|
|
_this.svgCirle.setAttribute("cy", currentPos.y);
|
|
_this.svgCirle.setAttribute("r", 0);
|
|
_this.svgContainer.append(_this.svgCirle);
|
|
_this.startCoords = currentPos;
|
|
}
|
|
|
|
_this.prevPos = currentPos;
|
|
};
|
|
|
|
_this.textContainer.on("mousemove touchmove", function (e) {
|
|
e.preventDefault();
|
|
|
|
if (_this.imgDragActive || !$(e.target).hasClass("textcontainer")) {
|
|
return;
|
|
}
|
|
if (ReadOnlyService.readOnlyActive) return;
|
|
|
|
const currentPos = Point.fromEvent(e);
|
|
|
|
ThrottlingService.throttle(currentPos, () => {
|
|
_this.lastPointerPosition = currentPos;
|
|
_this.sendFunction({
|
|
t: "cursor",
|
|
event: "move",
|
|
d: [currentPos.x, currentPos.y],
|
|
username: _this.settings.username,
|
|
});
|
|
});
|
|
});
|
|
|
|
_this.mouseOverlay.on("mousemove touchmove", function (e) {
|
|
e.preventDefault();
|
|
if (ReadOnlyService.readOnlyActive) return;
|
|
_this.triggerMouseMove(e);
|
|
});
|
|
|
|
_this.mouseOverlay.on("mouseup touchend touchcancel", function (e) {
|
|
_this.mouseup(e);
|
|
});
|
|
|
|
_this.mouseup = function (e) {
|
|
if (_this.imgDragActive) {
|
|
return;
|
|
}
|
|
if (ReadOnlyService.readOnlyActive) return;
|
|
_this.drawFlag = false;
|
|
_this.drawId++;
|
|
_this.ctx.globalCompositeOperation = _this.oldGCO;
|
|
|
|
let currentPos = Point.fromEvent(e);
|
|
|
|
if (currentPos.isZeroZero) {
|
|
_this.sendFunction({
|
|
t: "cursor",
|
|
event: "out",
|
|
username: _this.settings.username,
|
|
});
|
|
}
|
|
|
|
if (_this.tool === "line") {
|
|
if (_this.pressedKeys.shift) {
|
|
currentPos = _this.getRoundedAngles(currentPos);
|
|
}
|
|
_this.drawPenLine(
|
|
currentPos.x,
|
|
currentPos.y,
|
|
_this.startCoords.x,
|
|
_this.startCoords.y,
|
|
_this.drawcolor,
|
|
_this.thickness
|
|
);
|
|
_this.sendFunction({
|
|
t: _this.tool,
|
|
d: [currentPos.x, currentPos.y, _this.startCoords.x, _this.startCoords.y],
|
|
c: _this.drawcolor,
|
|
th: _this.thickness,
|
|
});
|
|
_this.svgContainer.find("line").remove();
|
|
} else if (_this.tool === "pen") {
|
|
_this.drawId--;
|
|
_this.pushPointSmoothPen(currentPos.x, currentPos.y);
|
|
_this.drawId++;
|
|
} else if (_this.tool === "rect") {
|
|
if (_this.pressedKeys.shift) {
|
|
if (
|
|
(currentPos.x - _this.startCoords.x) *
|
|
(currentPos.y - _this.startCoords.y) >
|
|
0
|
|
) {
|
|
currentPos = new Point(
|
|
currentPos.x,
|
|
_this.startCoords.y + (currentPos.x - _this.startCoords.x)
|
|
);
|
|
} else {
|
|
currentPos = new Point(
|
|
currentPos.x,
|
|
_this.startCoords.y - (currentPos.x - _this.startCoords.x)
|
|
);
|
|
}
|
|
}
|
|
_this.drawRec(
|
|
_this.startCoords.x,
|
|
_this.startCoords.y,
|
|
currentPos.x,
|
|
currentPos.y,
|
|
_this.drawcolor,
|
|
_this.thickness
|
|
);
|
|
_this.sendFunction({
|
|
t: _this.tool,
|
|
d: [_this.startCoords.x, _this.startCoords.y, currentPos.x, currentPos.y],
|
|
c: _this.drawcolor,
|
|
th: _this.thickness,
|
|
});
|
|
_this.svgContainer.find("rect").remove();
|
|
} else if (_this.tool === "circle") {
|
|
const r = currentPos.distTo(_this.startCoords);
|
|
_this.drawCircle(
|
|
_this.startCoords.x,
|
|
_this.startCoords.y,
|
|
r,
|
|
_this.drawcolor,
|
|
_this.thickness
|
|
);
|
|
_this.sendFunction({
|
|
t: _this.tool,
|
|
d: [_this.startCoords.x, _this.startCoords.y, r],
|
|
c: _this.drawcolor,
|
|
th: _this.thickness,
|
|
});
|
|
_this.svgContainer.find("circle").remove();
|
|
} else if (_this.tool === "recSelect") {
|
|
_this.imgDragActive = true;
|
|
if (_this.pressedKeys.shift) {
|
|
if (
|
|
(currentPos.x - _this.startCoords.x) *
|
|
(currentPos.y - _this.startCoords.y) >
|
|
0
|
|
) {
|
|
currentPos = new Point(
|
|
currentPos.x,
|
|
_this.startCoords.y + (currentPos.x - _this.startCoords.x)
|
|
);
|
|
} else {
|
|
currentPos = new Point(
|
|
currentPos.x,
|
|
_this.startCoords.y - (currentPos.x - _this.startCoords.x)
|
|
);
|
|
}
|
|
}
|
|
|
|
const width = Math.abs(_this.startCoords.x - currentPos.x);
|
|
const height = Math.abs(_this.startCoords.y - currentPos.y);
|
|
const left =
|
|
_this.startCoords.x < currentPos.x ? _this.startCoords.x : currentPos.x;
|
|
const top = _this.startCoords.y < currentPos.y ? _this.startCoords.y : currentPos.y;
|
|
_this.mouseOverlay.css({ cursor: "default" });
|
|
const imgDiv = $(
|
|
`<div class="dragMe" style="position:absolute; left: ${left}px; top: ${top}px; width: ${width}px; border: 2px dotted gray; overflow: hidden; height: ${height}px;" cursor:move;">
|
|
<canvas style="cursor:move; position:absolute; top:0px; left:0px;" width="${width}" height="${height}"></canvas>
|
|
<div style="position:absolute; right:5px; top:3px;">
|
|
<button draw="1" style="margin: 0px 0px; background: #03a9f4; padding: 5px; margin-top: 3px; color: white;" class="addToCanvasBtn btn btn-default">Drop</button>
|
|
<button style="margin: 0px 0px; background: #03a9f4; padding: 5px; margin-top: 3px; color: white;" class="xCanvasBtn btn btn-default">x</button>
|
|
</div>
|
|
</div>`
|
|
);
|
|
const dragCanvas = $(imgDiv).find("canvas");
|
|
const dragOutOverlay = $(
|
|
`<div class="dragOutOverlay" style="position:absolute; left: ${left}px; top: ${top}px; width: ${width}px; height: ${height}px; background:white;"></div>`
|
|
);
|
|
_this.mouseOverlay.append(dragOutOverlay);
|
|
_this.mouseOverlay.append(imgDiv);
|
|
|
|
const destCanvasContext = dragCanvas[0].getContext("2d");
|
|
destCanvasContext.drawImage(
|
|
_this.canvas,
|
|
left,
|
|
top,
|
|
width,
|
|
height,
|
|
0,
|
|
0,
|
|
width,
|
|
height
|
|
);
|
|
imgDiv
|
|
.find(".xCanvasBtn")
|
|
.off("click")
|
|
.click(function () {
|
|
_this.imgDragActive = false;
|
|
_this.refreshCursorAppearance();
|
|
imgDiv.remove();
|
|
dragOutOverlay.remove();
|
|
});
|
|
imgDiv
|
|
.find(".addToCanvasBtn")
|
|
.off("click")
|
|
.click(function () {
|
|
_this.imgDragActive = false;
|
|
_this.refreshCursorAppearance();
|
|
const p = imgDiv.position();
|
|
const leftT = Math.round(p.left * 100) / 100;
|
|
const topT = Math.round(p.top * 100) / 100;
|
|
_this.drawId++;
|
|
_this.sendFunction({
|
|
t: _this.tool,
|
|
d: [left, top, leftT, topT, width, height],
|
|
});
|
|
_this.dragCanvasRectContent(left, top, leftT, topT, width, height);
|
|
imgDiv.remove();
|
|
dragOutOverlay.remove();
|
|
});
|
|
imgDiv.draggable();
|
|
_this.svgContainer.find("rect").remove();
|
|
}
|
|
};
|
|
|
|
_this.mouseOverlay.on("mouseout", function (e) {
|
|
if (ReadOnlyService.readOnlyActive) return;
|
|
_this.triggerMouseOut();
|
|
});
|
|
|
|
_this.mouseOverlay.on("mouseover", function (e) {
|
|
if (ReadOnlyService.readOnlyActive) return;
|
|
_this.triggerMouseOver();
|
|
});
|
|
|
|
// On text container click (Add a new textbox)
|
|
_this.textContainer.on("click", function (e) {
|
|
const currentPos = Point.fromEvent(e);
|
|
const fontsize = _this.thickness * 0.5;
|
|
const txId = "tx" + +new Date();
|
|
_this.sendFunction({
|
|
t: "addTextBox",
|
|
d: [_this.drawcolor, fontsize, currentPos.x, currentPos.y, txId],
|
|
});
|
|
_this.addTextBox(_this.drawcolor, fontsize, currentPos.x, currentPos.y, txId, true);
|
|
});
|
|
},
|
|
/**
|
|
* For drawing lines at 0,45,90° ....
|
|
* @param {Point} currentPos
|
|
* @returns {Point}
|
|
*/
|
|
getRoundedAngles: function (currentPos) {
|
|
const { startCoords } = this;
|
|
|
|
// these transformations operate in the standard coordinate system
|
|
// y goes from bottom to up, x goes left to right
|
|
const dx = currentPos.x - startCoords.x; // browser x is reversed
|
|
const dy = startCoords.y - currentPos.y;
|
|
|
|
const angle = Math.atan2(dy, dx);
|
|
const angle45 = Math.round(angle / _45_DEG_IN_RAD) * _45_DEG_IN_RAD;
|
|
|
|
const dist = currentPos.distTo(startCoords);
|
|
let outX = startCoords.x + dist * Math.cos(angle45);
|
|
let outY = startCoords.y - dist * Math.sin(angle45);
|
|
|
|
return new Point(outX, outY);
|
|
},
|
|
triggerMouseMove: function (e) {
|
|
const _this = this;
|
|
if (_this.imgDragActive) {
|
|
return;
|
|
}
|
|
|
|
let currentPos = Point.fromEvent(e);
|
|
|
|
window.requestAnimationFrame(function () {
|
|
// update position
|
|
currentPos = Point.fromEvent(e);
|
|
|
|
if (_this.drawFlag) {
|
|
if (_this.tool === "pen") {
|
|
_this.pushPointSmoothPen(currentPos.x, currentPos.y);
|
|
} else if (_this.tool === "eraser") {
|
|
_this.drawEraserLine(
|
|
currentPos.x,
|
|
currentPos.y,
|
|
_this.prevPos.x,
|
|
_this.prevPos.y,
|
|
_this.thickness
|
|
);
|
|
_this.sendFunction({
|
|
t: _this.tool,
|
|
d: [currentPos.x, currentPos.y, _this.prevPos.x, _this.prevPos.y],
|
|
th: _this.thickness,
|
|
});
|
|
}
|
|
}
|
|
|
|
if (_this.tool === "eraser") {
|
|
const left = currentPos.x - _this.thickness;
|
|
const top = currentPos.y - _this.thickness;
|
|
if (_this.ownCursor) _this.ownCursor.css({ top: top + "px", left: left + "px" });
|
|
} else if (_this.tool === "pen") {
|
|
const left = currentPos.x - _this.thickness / 2;
|
|
const top = currentPos.y - _this.thickness / 2;
|
|
if (_this.ownCursor) _this.ownCursor.css({ top: top + "px", left: left + "px" });
|
|
} else if (_this.tool === "line") {
|
|
if (_this.svgLine) {
|
|
let posToUse = currentPos;
|
|
if (_this.pressedKeys.shift) {
|
|
posToUse = _this.getRoundedAngles(currentPos);
|
|
}
|
|
_this.svgLine.setAttribute("x2", posToUse.x);
|
|
_this.svgLine.setAttribute("y2", posToUse.y);
|
|
}
|
|
} else if (_this.tool === "rect" || (_this.tool === "recSelect" && _this.drawFlag)) {
|
|
if (_this.svgRect) {
|
|
const width = Math.abs(currentPos.x - _this.startCoords.x);
|
|
let height = Math.abs(currentPos.y - _this.startCoords.y);
|
|
if (_this.pressedKeys.shift) {
|
|
height = width;
|
|
const x =
|
|
currentPos.x < _this.startCoords.x
|
|
? _this.startCoords.x - width
|
|
: _this.startCoords.x;
|
|
const y =
|
|
currentPos.y < _this.startCoords.y
|
|
? _this.startCoords.y - width
|
|
: _this.startCoords.y;
|
|
_this.svgRect.setAttribute("x", x);
|
|
_this.svgRect.setAttribute("y", y);
|
|
} else {
|
|
const x =
|
|
currentPos.x < _this.startCoords.x ? currentPos.x : _this.startCoords.x;
|
|
const y =
|
|
currentPos.y < _this.startCoords.y ? currentPos.y : _this.startCoords.y;
|
|
_this.svgRect.setAttribute("x", x);
|
|
_this.svgRect.setAttribute("y", y);
|
|
}
|
|
|
|
_this.svgRect.setAttribute("width", width);
|
|
_this.svgRect.setAttribute("height", height);
|
|
}
|
|
} else if (_this.tool === "circle") {
|
|
const r = currentPos.distTo(_this.startCoords);
|
|
if (_this.svgCirle) {
|
|
_this.svgCirle.setAttribute("r", r);
|
|
}
|
|
}
|
|
|
|
_this.prevPos = currentPos;
|
|
});
|
|
|
|
ThrottlingService.throttle(currentPos, () => {
|
|
_this.lastPointerPosition = currentPos;
|
|
_this.sendFunction({
|
|
t: "cursor",
|
|
event: "move",
|
|
d: [currentPos.x, currentPos.y],
|
|
username: _this.settings.username,
|
|
});
|
|
});
|
|
},
|
|
triggerMouseOver: function () {
|
|
var _this = this;
|
|
if (_this.imgDragActive) {
|
|
return;
|
|
}
|
|
if (!_this.mouseover) {
|
|
var color = _this.drawcolor;
|
|
var widthHeight = _this.thickness;
|
|
if (_this.tool === "eraser") {
|
|
color = "#00000000";
|
|
widthHeight = widthHeight * 2;
|
|
}
|
|
if (_this.tool === "eraser" || _this.tool === "pen") {
|
|
_this.ownCursor = $(
|
|
'<div id="ownCursor" style="background:' +
|
|
color +
|
|
"; border:1px solid gray; position:absolute; width:" +
|
|
widthHeight +
|
|
"px; height:" +
|
|
widthHeight +
|
|
'px; border-radius:50%;"></div>'
|
|
);
|
|
_this.cursorContainer.append(_this.ownCursor);
|
|
}
|
|
}
|
|
_this.mouseover = true;
|
|
},
|
|
triggerMouseOut: function () {
|
|
var _this = this;
|
|
if (_this.imgDragActive) {
|
|
return;
|
|
}
|
|
_this.drawFlag = false;
|
|
_this.mouseover = false;
|
|
_this.ctx.globalCompositeOperation = _this.oldGCO;
|
|
if (_this.ownCursor) _this.ownCursor.remove();
|
|
_this.svgContainer.find("line").remove();
|
|
_this.svgContainer.find("rect").remove();
|
|
_this.svgContainer.find("circle").remove();
|
|
_this.sendFunction({ t: "cursor", event: "out" });
|
|
},
|
|
redrawMouseCursor: function () {
|
|
const _this = this;
|
|
_this.triggerMouseOut();
|
|
_this.triggerMouseOver();
|
|
_this.triggerMouseMove({ offsetX: _this.prevPos.x, offsetY: _this.prevPos.y });
|
|
},
|
|
delKeyAction: function () {
|
|
var _this = this;
|
|
$.each(_this.mouseOverlay.find(".dragOutOverlay"), function () {
|
|
var width = $(this).width();
|
|
var height = $(this).height();
|
|
var p = $(this).position();
|
|
var left = Math.round(p.left * 100) / 100;
|
|
var top = Math.round(p.top * 100) / 100;
|
|
_this.drawId++;
|
|
_this.sendFunction({ t: "eraseRec", d: [left, top, width, height] });
|
|
_this.eraseRec(left, top, width, height);
|
|
});
|
|
_this.mouseOverlay.find(".xCanvasBtn").click(); //Remove all current drops
|
|
_this.textContainer
|
|
.find("#" + _this.latestActiveTextBoxId)
|
|
.find(".removeIcon")
|
|
.click();
|
|
},
|
|
escKeyAction: function () {
|
|
var _this = this;
|
|
if (!_this.drawFlag) {
|
|
_this.svgContainer.empty();
|
|
}
|
|
_this.mouseOverlay.find(".xCanvasBtn").click(); //Remove all current drops
|
|
},
|
|
pushPointSmoothPen: function (X, Y) {
|
|
var _this = this;
|
|
if (_this.penSmoothLastCoords.length >= 8) {
|
|
_this.penSmoothLastCoords = [
|
|
_this.penSmoothLastCoords[2],
|
|
_this.penSmoothLastCoords[3],
|
|
_this.penSmoothLastCoords[4],
|
|
_this.penSmoothLastCoords[5],
|
|
_this.penSmoothLastCoords[6],
|
|
_this.penSmoothLastCoords[7],
|
|
];
|
|
}
|
|
_this.penSmoothLastCoords.push(X, Y);
|
|
if (_this.penSmoothLastCoords.length >= 8) {
|
|
_this.drawPenSmoothLine(_this.penSmoothLastCoords, _this.drawcolor, _this.thickness);
|
|
_this.sendFunction({
|
|
t: _this.tool,
|
|
d: _this.penSmoothLastCoords,
|
|
c: _this.drawcolor,
|
|
th: _this.thickness,
|
|
});
|
|
}
|
|
},
|
|
dragCanvasRectContent: function (xf, yf, xt, yt, width, height) {
|
|
var tempCanvas = document.createElement("canvas");
|
|
tempCanvas.width = width;
|
|
tempCanvas.height = height;
|
|
var tempCanvasContext = tempCanvas.getContext("2d");
|
|
tempCanvasContext.drawImage(this.canvas, xf, yf, width, height, 0, 0, width, height);
|
|
this.eraseRec(xf, yf, width, height);
|
|
this.ctx.drawImage(tempCanvas, xt, yt);
|
|
},
|
|
eraseRec: function (fromX, fromY, width, height) {
|
|
var _this = this;
|
|
_this.ctx.beginPath();
|
|
_this.ctx.rect(fromX, fromY, width, height);
|
|
_this.ctx.fillStyle = "rgba(0,0,0,1)";
|
|
_this.ctx.globalCompositeOperation = "destination-out";
|
|
_this.ctx.fill();
|
|
_this.ctx.closePath();
|
|
_this.ctx.globalCompositeOperation = _this.oldGCO;
|
|
},
|
|
drawPenLine: function (fromX, fromY, toX, toY, color, thickness) {
|
|
var _this = this;
|
|
_this.ctx.beginPath();
|
|
_this.ctx.moveTo(fromX, fromY);
|
|
_this.ctx.lineTo(toX, toY);
|
|
_this.ctx.strokeStyle = color;
|
|
_this.ctx.lineWidth = thickness;
|
|
_this.ctx.lineCap = _this.lineCap;
|
|
_this.ctx.stroke();
|
|
_this.ctx.closePath();
|
|
},
|
|
drawPenSmoothLine: function (coords, color, thickness) {
|
|
var _this = this;
|
|
var xm1 = coords[0];
|
|
var ym1 = coords[1];
|
|
var x0 = coords[2];
|
|
var y0 = coords[3];
|
|
var x1 = coords[4];
|
|
var y1 = coords[5];
|
|
var x2 = coords[6];
|
|
var y2 = coords[7];
|
|
var length = Math.sqrt(Math.pow(x0 - x1, 2) + Math.pow(y0 - y1, 2));
|
|
var steps = Math.ceil(length / 5);
|
|
_this.ctx.beginPath();
|
|
_this.ctx.moveTo(x0, y0);
|
|
for (var i = 0; i < steps; i++) {
|
|
var point = lanczosInterpolate(xm1, ym1, x0, y0, x1, y1, x2, y2, (i + 1) / steps);
|
|
_this.ctx.lineTo(point[0], point[1]);
|
|
}
|
|
_this.ctx.strokeStyle = color;
|
|
_this.ctx.lineWidth = thickness;
|
|
_this.ctx.lineCap = _this.lineCap;
|
|
_this.ctx.stroke();
|
|
_this.ctx.closePath();
|
|
},
|
|
drawEraserLine: function (fromX, fromY, toX, toY, thickness) {
|
|
var _this = this;
|
|
_this.ctx.beginPath();
|
|
_this.ctx.moveTo(fromX, fromY);
|
|
_this.ctx.lineTo(toX, toY);
|
|
_this.ctx.strokeStyle = "rgba(0,0,0,1)";
|
|
_this.ctx.lineWidth = thickness * 2;
|
|
_this.ctx.lineCap = _this.lineCap;
|
|
_this.ctx.globalCompositeOperation = "destination-out";
|
|
_this.ctx.stroke();
|
|
_this.ctx.closePath();
|
|
_this.ctx.globalCompositeOperation = _this.oldGCO;
|
|
},
|
|
drawRec: function (fromX, fromY, toX, toY, color, thickness) {
|
|
var _this = this;
|
|
toX = toX - fromX;
|
|
toY = toY - fromY;
|
|
_this.ctx.beginPath();
|
|
_this.ctx.rect(fromX, fromY, toX, toY);
|
|
_this.ctx.strokeStyle = color;
|
|
_this.ctx.lineWidth = thickness;
|
|
_this.ctx.lineCap = _this.lineCap;
|
|
_this.ctx.stroke();
|
|
_this.ctx.closePath();
|
|
},
|
|
drawCircle: function (fromX, fromY, radius, color, thickness) {
|
|
var _this = this;
|
|
_this.ctx.beginPath();
|
|
_this.ctx.arc(fromX, fromY, radius, 0, 2 * Math.PI, false);
|
|
_this.ctx.lineWidth = thickness;
|
|
_this.ctx.strokeStyle = color;
|
|
_this.ctx.stroke();
|
|
},
|
|
clearWhiteboard: function () {
|
|
var _this = this;
|
|
if (ReadOnlyService.readOnlyActive) return;
|
|
_this.canvas.height = _this.canvas.height;
|
|
_this.imgContainer.empty();
|
|
_this.textContainer.empty();
|
|
_this.sendFunction({ t: "clear" });
|
|
_this.drawBuffer = [];
|
|
_this.undoBuffer = [];
|
|
_this.drawId = 0;
|
|
},
|
|
setStrokeThickness(thickness) {
|
|
var _this = this;
|
|
_this.thickness = thickness;
|
|
|
|
if (_this.tool == "text" && _this.latestActiveTextBoxId) {
|
|
_this.sendFunction({
|
|
t: "setTextboxFontSize",
|
|
d: [_this.latestActiveTextBoxId, thickness],
|
|
});
|
|
_this.setTextboxFontSize(_this.latestActiveTextBoxId, thickness);
|
|
}
|
|
},
|
|
addImgToCanvasByUrl: function (url) {
|
|
var _this = this;
|
|
var oldTool = _this.tool;
|
|
_this.setTool("mouse"); //Set to mouse tool while dropping to prevent errors
|
|
_this.imgDragActive = true;
|
|
_this.mouseOverlay.css({ cursor: "default" });
|
|
var imgDiv = $(
|
|
'<div class="dragMe" style="border: 2px dashed gray; position:absolute; left:200px; top:200px; min-width:160px; min-height:100px; cursor:move;">' +
|
|
'<img style="width:100%; height:100%;" src="' +
|
|
url +
|
|
'">' +
|
|
'<div style="position:absolute; right:5px; top:3px;">' +
|
|
'<button draw="1" style="margin: 0px 0px; background: #03a9f4; padding: 5px; margin-top: 3px; color: white;" class="addToCanvasBtn btn btn-default">Draw to canvas</button> ' +
|
|
'<button draw="0" style="margin: 0px 0px; background: #03a9f4; padding: 5px; margin-top: 3px; color: white;" class="addToBackgroundBtn btn btn-default">Add to background</button> ' +
|
|
'<button style="margin: 0px 0px; background: #03a9f4; padding: 5px; margin-top: 3px; color: white;" class="xCanvasBtn btn btn-default">x</button>' +
|
|
"</div>" +
|
|
'<i style="position:absolute; bottom: -4px; right: 2px; font-size: 2em; color: gray; transform: rotate(-45deg);" class="fas fa-sort-down" aria-hidden="true"></i>' +
|
|
'<div class="rotationHandle" style="position:absolute; bottom: -30px; left: 0px; width:100%; text-align:center; cursor:ew-resize;"><i class="fa fa-undo"></i></div>' +
|
|
"</div>"
|
|
);
|
|
imgDiv
|
|
.find(".xCanvasBtn")
|
|
.off("click")
|
|
.click(function () {
|
|
_this.imgDragActive = false;
|
|
_this.refreshCursorAppearance();
|
|
imgDiv.remove();
|
|
_this.setTool(oldTool);
|
|
});
|
|
var rotationAngle = 0;
|
|
var recoupLeft = 0;
|
|
var recoupTop = 0;
|
|
var p = imgDiv.position();
|
|
var left = 200;
|
|
var top = 200;
|
|
imgDiv
|
|
.find(".addToCanvasBtn,.addToBackgroundBtn")
|
|
.off("click")
|
|
.click(function () {
|
|
var draw = $(this).attr("draw");
|
|
_this.imgDragActive = false;
|
|
|
|
var width = imgDiv.width();
|
|
var height = imgDiv.height();
|
|
|
|
if (draw == "1") {
|
|
//draw image to canvas
|
|
_this.drawImgToCanvas(url, width, height, left, top, rotationAngle);
|
|
} else {
|
|
//Add image to background
|
|
_this.drawImgToBackground(url, width, height, left, top, rotationAngle);
|
|
}
|
|
_this.sendFunction({
|
|
t: "addImgBG",
|
|
draw: draw,
|
|
url: url,
|
|
d: [width, height, left, top, rotationAngle],
|
|
});
|
|
_this.drawId++;
|
|
imgDiv.remove();
|
|
_this.refreshCursorAppearance();
|
|
_this.setTool(oldTool);
|
|
});
|
|
_this.mouseOverlay.append(imgDiv);
|
|
|
|
imgDiv.draggable({
|
|
start: function (event, ui) {
|
|
var left = parseInt($(this).css("left"), 10);
|
|
left = isNaN(left) ? 0 : left;
|
|
var top = parseInt($(this).css("top"), 10);
|
|
top = isNaN(top) ? 0 : top;
|
|
recoupLeft = left - ui.position.left;
|
|
recoupTop = top - ui.position.top;
|
|
},
|
|
drag: function (event, ui) {
|
|
ui.position.left += recoupLeft;
|
|
ui.position.top += recoupTop;
|
|
},
|
|
stop: function (event, ui) {
|
|
left = ui.position.left;
|
|
top = ui.position.top;
|
|
},
|
|
});
|
|
imgDiv.resizable();
|
|
var params = {
|
|
// Callback fired on rotation start.
|
|
start: function (event, ui) {},
|
|
// Callback fired during rotation.
|
|
rotate: function (event, ui) {
|
|
//console.log(ui)
|
|
},
|
|
// Callback fired on rotation end.
|
|
stop: function (event, ui) {
|
|
rotationAngle = ui.angle.current;
|
|
},
|
|
handle: imgDiv.find(".rotationHandle"),
|
|
};
|
|
imgDiv.rotatable(params);
|
|
|
|
// render newly added icons
|
|
dom.i2svg();
|
|
},
|
|
drawImgToBackground(url, width, height, left, top, rotationAngle) {
|
|
this.imgContainer.append(
|
|
'<img crossorigin="anonymous" style="width:' +
|
|
width +
|
|
"px; height:" +
|
|
height +
|
|
"px; position:absolute; top:" +
|
|
top +
|
|
"px; left:" +
|
|
left +
|
|
"px; transform: rotate(" +
|
|
rotationAngle +
|
|
'rad);" src="' +
|
|
url +
|
|
'">'
|
|
);
|
|
},
|
|
addTextBox(textcolor, fontsize, left, top, txId, newLocalBox) {
|
|
var _this = this;
|
|
var textBox = $(
|
|
'<div id="' +
|
|
txId +
|
|
'" class="textBox" style="font-family: Monospace; position:absolute; top:' +
|
|
top +
|
|
"px; left:" +
|
|
left +
|
|
'px;">' +
|
|
'<div contentEditable="true" spellcheck="false" class="textContent" style="outline: none; font-size:' +
|
|
fontsize +
|
|
"em; color:" +
|
|
textcolor +
|
|
'; min-width:50px; min-height:50px;"></div>' +
|
|
'<div title="remove textbox" class="removeIcon" style="position:absolute; cursor:pointer; top:-4px; right:2px;">x</div>' +
|
|
'<div title="move textbox" class="moveIcon" style="position:absolute; cursor:move; top:1px; left:2px; font-size: 0.5em;"><i class="fas fa-expand-arrows-alt"></i></div>' +
|
|
"</div>"
|
|
);
|
|
_this.latestActiveTextBoxId = txId;
|
|
textBox.click(function (e) {
|
|
e.preventDefault();
|
|
_this.latestActiveTextBoxId = txId;
|
|
return false;
|
|
});
|
|
textBox.on("mousemove touchmove", function (e) {
|
|
e.preventDefault();
|
|
if (_this.imgDragActive) {
|
|
return;
|
|
}
|
|
var textBoxPosition = textBox.position();
|
|
var currX = e.offsetX + textBoxPosition.left;
|
|
var currY = e.offsetY + textBoxPosition.top;
|
|
if ($(e.target).hasClass("removeIcon")) {
|
|
currX += textBox.width() - 4;
|
|
}
|
|
|
|
const newPointerPosition = new Point(currX, currY);
|
|
|
|
ThrottlingService.throttle(newPointerPosition, () => {
|
|
_this.lastPointerPosition = newPointerPosition;
|
|
_this.sendFunction({
|
|
t: "cursor",
|
|
event: "move",
|
|
d: [newPointerPosition.x, newPointerPosition.y],
|
|
username: _this.settings.username,
|
|
});
|
|
});
|
|
});
|
|
this.textContainer.append(textBox);
|
|
textBox.draggable({
|
|
handle: ".moveIcon",
|
|
stop: function () {
|
|
var textBoxPosition = textBox.position();
|
|
_this.sendFunction({
|
|
t: "setTextboxPosition",
|
|
d: [txId, textBoxPosition.top, textBoxPosition.left],
|
|
});
|
|
},
|
|
drag: function () {
|
|
var textBoxPosition = textBox.position();
|
|
_this.sendFunction({
|
|
t: "setTextboxPosition",
|
|
d: [txId, textBoxPosition.top, textBoxPosition.left],
|
|
});
|
|
},
|
|
});
|
|
textBox.find(".textContent").on("input", function () {
|
|
var text = btoa(unescape(encodeURIComponent($(this).html()))); //Get html and make encode base64 also take care of the charset
|
|
_this.sendFunction({ t: "setTextboxText", d: [txId, text] });
|
|
});
|
|
textBox
|
|
.find(".removeIcon")
|
|
.off("click")
|
|
.click(function (e) {
|
|
$("#" + txId).remove();
|
|
_this.sendFunction({ t: "removeTextbox", d: [txId] });
|
|
e.preventDefault();
|
|
return false;
|
|
});
|
|
if (newLocalBox) {
|
|
textBox.find(".textContent").focus();
|
|
}
|
|
if (this.tool === "text") {
|
|
textBox.addClass("active");
|
|
}
|
|
|
|
// render newly added icons
|
|
dom.i2svg();
|
|
},
|
|
setTextboxText(txId, text) {
|
|
$("#" + txId)
|
|
.find(".textContent")
|
|
.html(decodeURIComponent(escape(atob(text)))); //Set decoded base64 as html
|
|
},
|
|
removeTextbox(txId) {
|
|
$("#" + txId).remove();
|
|
},
|
|
setTextboxPosition(txId, top, left) {
|
|
$("#" + txId).css({ top: top + "px", left: left + "px" });
|
|
},
|
|
setTextboxFontSize(txId, fontSize) {
|
|
$("#" + txId)
|
|
.find(".textContent")
|
|
.css({ "font-size": fontSize + "em" });
|
|
},
|
|
setTextboxFontColor(txId, color) {
|
|
$("#" + txId)
|
|
.find(".textContent")
|
|
.css({ color: color });
|
|
},
|
|
drawImgToCanvas(url, width, height, left, top, rotationAngle, doneCallback) {
|
|
var _this = this;
|
|
var img = document.createElement("img");
|
|
img.onload = function () {
|
|
rotationAngle = rotationAngle ? rotationAngle : 0;
|
|
if (rotationAngle === 0) {
|
|
_this.ctx.drawImage(img, left, top, width, height);
|
|
} else {
|
|
_this.ctx.save();
|
|
_this.ctx.translate(left + width / 2, top + height / 2);
|
|
_this.ctx.rotate(rotationAngle);
|
|
_this.ctx.drawImage(img, -(width / 2), -(height / 2), width, height);
|
|
_this.ctx.restore();
|
|
}
|
|
if (doneCallback) {
|
|
doneCallback();
|
|
}
|
|
};
|
|
img.src = url;
|
|
},
|
|
undoWhiteboard: function (username) {
|
|
//Not call this directly because you will get out of sync whit others...
|
|
var _this = this;
|
|
if (!username) {
|
|
username = _this.settings.username;
|
|
}
|
|
for (var i = _this.drawBuffer.length - 1; i >= 0; i--) {
|
|
if (_this.drawBuffer[i]["username"] == username) {
|
|
var drawId = _this.drawBuffer[i]["drawId"];
|
|
for (var i = _this.drawBuffer.length - 1; i >= 0; i--) {
|
|
if (
|
|
_this.drawBuffer[i]["drawId"] == drawId &&
|
|
_this.drawBuffer[i]["username"] == username
|
|
) {
|
|
_this.undoBuffer.push(_this.drawBuffer[i]);
|
|
_this.drawBuffer.splice(i, 1);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (_this.undoBuffer.length > 1000) {
|
|
_this.undoBuffer.splice(0, _this.undoBuffer.length - 1000);
|
|
}
|
|
_this.canvas.height = _this.canvas.height;
|
|
_this.imgContainer.empty();
|
|
_this.loadDataInSteps(_this.drawBuffer, false, function (stepData) {
|
|
//Nothing to do
|
|
});
|
|
},
|
|
redoWhiteboard: function (username) {
|
|
//Not call this directly because you will get out of sync whit others...
|
|
var _this = this;
|
|
if (!username) {
|
|
username = _this.settings.username;
|
|
}
|
|
for (var i = _this.undoBuffer.length - 1; i >= 0; i--) {
|
|
if (_this.undoBuffer[i]["username"] == username) {
|
|
var drawId = _this.undoBuffer[i]["drawId"];
|
|
for (var i = _this.undoBuffer.length - 1; i >= 0; i--) {
|
|
if (
|
|
_this.undoBuffer[i]["drawId"] == drawId &&
|
|
_this.undoBuffer[i]["username"] == username
|
|
) {
|
|
_this.drawBuffer.push(_this.undoBuffer[i]);
|
|
_this.undoBuffer.splice(i, 1);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
_this.canvas.height = _this.canvas.height;
|
|
_this.imgContainer.empty();
|
|
_this.loadDataInSteps(_this.drawBuffer, false, function (stepData) {
|
|
//Nothing to do
|
|
});
|
|
},
|
|
undoWhiteboardClick: function () {
|
|
if (ReadOnlyService.readOnlyActive) return;
|
|
this.sendFunction({ t: "undo" });
|
|
this.undoWhiteboard();
|
|
},
|
|
redoWhiteboardClick: function () {
|
|
if (ReadOnlyService.readOnlyActive) return;
|
|
this.sendFunction({ t: "redo" });
|
|
this.redoWhiteboard();
|
|
},
|
|
setTool: function (tool) {
|
|
this.tool = tool;
|
|
if (this.tool === "text") {
|
|
$(".textBox").addClass("active");
|
|
this.textContainer.appendTo($(whiteboardContainer)); //Bring textContainer to the front
|
|
} else {
|
|
$(".textBox").removeClass("active");
|
|
this.mouseOverlay.appendTo($(whiteboardContainer));
|
|
}
|
|
this.refreshCursorAppearance();
|
|
this.mouseOverlay.find(".xCanvasBtn").click();
|
|
this.latestActiveTextBoxId = null;
|
|
},
|
|
setDrawColor(color) {
|
|
var _this = this;
|
|
_this.drawcolor = color;
|
|
$("#whiteboardColorpicker").css({ background: color });
|
|
if (_this.tool == "text" && _this.latestActiveTextBoxId) {
|
|
_this.sendFunction({
|
|
t: "setTextboxFontColor",
|
|
d: [_this.latestActiveTextBoxId, color],
|
|
});
|
|
_this.setTextboxFontColor(_this.latestActiveTextBoxId, color);
|
|
}
|
|
},
|
|
updateSmallestScreenResolution() {
|
|
const { smallestScreenResolution } = InfoService;
|
|
const { showSmallestScreenIndicator } = ConfigService;
|
|
if (showSmallestScreenIndicator && smallestScreenResolution) {
|
|
const { w: width, h: height } = smallestScreenResolution;
|
|
this.backgroundGrid.empty();
|
|
if (width < $(window).width() || height < $(window).height()) {
|
|
this.backgroundGrid.append(
|
|
'<div style="position:absolute; left:0px; top:0px; border-right:3px dotted black; border-bottom:3px dotted black; width:' +
|
|
width +
|
|
"px; height:" +
|
|
height +
|
|
'px;"></div>'
|
|
);
|
|
this.backgroundGrid.append(
|
|
'<div style="position:absolute; left:' +
|
|
(width + 5) +
|
|
'px; top:0px;">smallest screen participating</div>'
|
|
);
|
|
}
|
|
}
|
|
},
|
|
handleEventsAndData: function (content, isNewData, doneCallback) {
|
|
var _this = this;
|
|
var tool = content["t"];
|
|
var data = content["d"];
|
|
var color = content["c"];
|
|
var username = content["username"];
|
|
var thickness = content["th"];
|
|
window.requestAnimationFrame(function () {
|
|
if (tool === "line" || tool === "pen") {
|
|
if (data.length == 4) {
|
|
//Only used for old json imports
|
|
_this.drawPenLine(data[0], data[1], data[2], data[3], color, thickness);
|
|
} else {
|
|
_this.drawPenSmoothLine(data, color, thickness);
|
|
}
|
|
} else if (tool === "rect") {
|
|
_this.drawRec(data[0], data[1], data[2], data[3], color, thickness);
|
|
} else if (tool === "circle") {
|
|
_this.drawCircle(data[0], data[1], data[2], color, thickness);
|
|
} else if (tool === "eraser") {
|
|
_this.drawEraserLine(data[0], data[1], data[2], data[3], thickness);
|
|
} else if (tool === "eraseRec") {
|
|
_this.eraseRec(data[0], data[1], data[2], data[3]);
|
|
} else if (tool === "recSelect") {
|
|
_this.dragCanvasRectContent(data[0], data[1], data[2], data[3], data[4], data[5]);
|
|
} else if (tool === "addImgBG") {
|
|
if (content["draw"] == "1") {
|
|
_this.drawImgToCanvas(
|
|
content["url"],
|
|
data[0],
|
|
data[1],
|
|
data[2],
|
|
data[3],
|
|
data[4],
|
|
doneCallback
|
|
);
|
|
} else {
|
|
_this.drawImgToBackground(
|
|
content["url"],
|
|
data[0],
|
|
data[1],
|
|
data[2],
|
|
data[3],
|
|
data[4]
|
|
);
|
|
}
|
|
} else if (tool === "addTextBox") {
|
|
_this.addTextBox(data[0], data[1], data[2], data[3], data[4]);
|
|
} else if (tool === "setTextboxText") {
|
|
_this.setTextboxText(data[0], data[1]);
|
|
} else if (tool === "removeTextbox") {
|
|
_this.removeTextbox(data[0]);
|
|
} else if (tool === "setTextboxPosition") {
|
|
_this.setTextboxPosition(data[0], data[1], data[2]);
|
|
} else if (tool === "setTextboxFontSize") {
|
|
_this.setTextboxFontSize(data[0], data[1]);
|
|
} else if (tool === "setTextboxFontColor") {
|
|
_this.setTextboxFontColor(data[0], data[1]);
|
|
} else if (tool === "clear") {
|
|
_this.canvas.height = _this.canvas.height;
|
|
_this.imgContainer.empty();
|
|
_this.textContainer.empty();
|
|
_this.drawBuffer = [];
|
|
_this.undoBuffer = [];
|
|
_this.drawId = 0;
|
|
} else if (tool === "cursor" && _this.settings) {
|
|
if (content["event"] === "move") {
|
|
if (_this.cursorContainer.find("." + content["username"]).length >= 1) {
|
|
_this.cursorContainer
|
|
.find("." + content["username"])
|
|
.css({ left: data[0] + "px", top: data[1] - 15 + "px" });
|
|
} else {
|
|
_this.cursorContainer.append(
|
|
'<div style="font-size:0.8em; padding-left:2px; padding-right:2px; background:gray; color:white; border-radius:3px; position:absolute; left:' +
|
|
data[0] +
|
|
"px; top:" +
|
|
(data[1] - 151) +
|
|
'px;" class="userbadge ' +
|
|
content["username"] +
|
|
'">' +
|
|
'<div style="width:4px; height:4px; background:gray; position:absolute; top:13px; left:-2px; border-radius:50%;"></div>' +
|
|
decodeURIComponent(atob(content["username"])) +
|
|
"</div>"
|
|
);
|
|
}
|
|
} else {
|
|
_this.cursorContainer.find("." + content["username"]).remove();
|
|
}
|
|
} else if (tool === "undo") {
|
|
_this.undoWhiteboard(username);
|
|
} else if (tool === "redo") {
|
|
_this.redoWhiteboard(username);
|
|
}
|
|
});
|
|
|
|
if (
|
|
isNewData &&
|
|
[
|
|
"line",
|
|
"pen",
|
|
"rect",
|
|
"circle",
|
|
"eraser",
|
|
"addImgBG",
|
|
"recSelect",
|
|
"eraseRec",
|
|
"addTextBox",
|
|
"setTextboxText",
|
|
"removeTextbox",
|
|
"setTextboxPosition",
|
|
"setTextboxFontSize",
|
|
"setTextboxFontColor",
|
|
].includes(tool)
|
|
) {
|
|
content["drawId"] = content["drawId"] ? content["drawId"] : _this.drawId;
|
|
content["username"] = content["username"]
|
|
? content["username"]
|
|
: _this.settings.username;
|
|
_this.drawBuffer.push(content);
|
|
}
|
|
},
|
|
userLeftWhiteboard(username) {
|
|
this.cursorContainer.find("." + username).remove();
|
|
},
|
|
refreshUserBadges() {
|
|
this.cursorContainer.find(".userbadge").remove();
|
|
},
|
|
getImageDataBase64(options, callback) {
|
|
var _this = this;
|
|
var width = this.mouseOverlay.width();
|
|
var height = this.mouseOverlay.height();
|
|
var copyCanvas = document.createElement("canvas");
|
|
copyCanvas.width = width;
|
|
copyCanvas.height = height;
|
|
var imageFormat = options.imageFormat || "png";
|
|
var drawBackgroundGrid = options.drawBackgroundGrid || false;
|
|
|
|
var brackGroundImg = new Image();
|
|
brackGroundImg.src = _this.settings.backgroundGridUrl;
|
|
|
|
brackGroundImg.onload = function () {
|
|
var destCtx = copyCanvas.getContext("2d"); //Draw the maincanvas to the exportcanvas
|
|
|
|
if (imageFormat === "jpeg") {
|
|
//Set white background for jpeg images
|
|
destCtx.fillStyle = "#FFFFFF";
|
|
destCtx.fillRect(0, 0, width, height);
|
|
}
|
|
|
|
if (drawBackgroundGrid) {
|
|
destCtx.globalAlpha = 0.8;
|
|
var ptrn = destCtx.createPattern(brackGroundImg, "repeat"); // Create a pattern with this image, and set it to "repeat".
|
|
destCtx.fillStyle = ptrn;
|
|
destCtx.fillRect(0, 0, copyCanvas.width, copyCanvas.height); // context.fillRect(x, y, width, height);
|
|
destCtx.globalAlpha = 1;
|
|
}
|
|
|
|
$.each(_this.imgContainer.find("img"), function () {
|
|
//Draw Backgroundimages to the export canvas
|
|
var width = $(this).width();
|
|
var height = $(this).height();
|
|
var p = $(this).position();
|
|
var left = Math.round(p.left * 100) / 100;
|
|
var top = Math.round(p.top * 100) / 100;
|
|
destCtx.drawImage(this, left, top, width, height);
|
|
});
|
|
|
|
//Copy drawings
|
|
destCtx.drawImage(_this.canvas, 0, 0);
|
|
|
|
var textBoxCnt = 0;
|
|
$.each($(".textBox"), function () {
|
|
//Draw the text on top
|
|
textBoxCnt++;
|
|
|
|
var textContainer = $(this);
|
|
var p = textContainer.position();
|
|
|
|
var left = Math.round(p.left * 100) / 100;
|
|
var top = Math.round(p.top * 100) / 100;
|
|
|
|
html2canvas(this, {
|
|
backgroundColor: "rgba(0, 0, 0, 0)",
|
|
removeContainer: true,
|
|
}).then(function (canvas) {
|
|
console.log("canvas", canvas);
|
|
|
|
destCtx.drawImage(canvas, left, top);
|
|
textBoxCnt--;
|
|
checkForReturn();
|
|
});
|
|
});
|
|
|
|
function checkForReturn() {
|
|
if (textBoxCnt == 0) {
|
|
var url = copyCanvas.toDataURL("image/" + imageFormat);
|
|
callback(url);
|
|
}
|
|
}
|
|
checkForReturn();
|
|
};
|
|
},
|
|
getImageDataJson() {
|
|
var sendObj = [];
|
|
for (var i = 0; i < this.drawBuffer.length; i++) {
|
|
sendObj.push(JSON.parse(JSON.stringify(this.drawBuffer[i])));
|
|
delete sendObj[i]["username"];
|
|
delete sendObj[i]["wid"];
|
|
delete sendObj[i]["drawId"];
|
|
}
|
|
return JSON.stringify(sendObj);
|
|
},
|
|
loadData: function (content) {
|
|
var _this = this;
|
|
_this.loadDataInSteps(content, true, function (stepData) {
|
|
if (
|
|
stepData["username"] == _this.settings.username &&
|
|
_this.drawId < stepData["drawId"]
|
|
) {
|
|
_this.drawId = stepData["drawId"] + 1;
|
|
}
|
|
});
|
|
},
|
|
loadDataInSteps(content, isNewData, callAfterEveryStep) {
|
|
var _this = this;
|
|
|
|
function lData(index) {
|
|
for (var i = index; i < content.length; i++) {
|
|
if (content[i]["t"] === "addImgBG" && content[i]["draw"] == "1") {
|
|
_this.handleEventsAndData(content[i], isNewData, function () {
|
|
callAfterEveryStep(content[i], i);
|
|
lData(i + 1);
|
|
});
|
|
break;
|
|
} else {
|
|
_this.handleEventsAndData(content[i], isNewData);
|
|
callAfterEveryStep(content[i], i);
|
|
}
|
|
}
|
|
}
|
|
lData(0);
|
|
},
|
|
loadJsonData(content, doneCallback) {
|
|
var _this = this;
|
|
_this.loadDataInSteps(content, false, function (stepData, index) {
|
|
_this.sendFunction(stepData);
|
|
if (index >= content.length - 1) {
|
|
//Done with all data
|
|
_this.drawId++;
|
|
if (doneCallback) {
|
|
doneCallback();
|
|
}
|
|
}
|
|
});
|
|
},
|
|
sendFunction: function (content) {
|
|
//Sends every draw to server
|
|
var _this = this;
|
|
content["wid"] = _this.settings.whiteboardId;
|
|
content["username"] = _this.settings.username;
|
|
content["drawId"] = _this.drawId;
|
|
|
|
var tool = content["t"];
|
|
if (_this.settings.sendFunction) {
|
|
_this.settings.sendFunction(content);
|
|
}
|
|
if (
|
|
[
|
|
"line",
|
|
"pen",
|
|
"rect",
|
|
"circle",
|
|
"eraser",
|
|
"addImgBG",
|
|
"recSelect",
|
|
"eraseRec",
|
|
"addTextBox",
|
|
"setTextboxText",
|
|
"removeTextbox",
|
|
"setTextboxPosition",
|
|
"setTextboxFontSize",
|
|
"setTextboxFontColor",
|
|
].includes(tool)
|
|
) {
|
|
_this.drawBuffer.push(content);
|
|
}
|
|
},
|
|
refreshCursorAppearance() {
|
|
//Set cursor depending on current active tool
|
|
var _this = this;
|
|
if (_this.tool === "pen" || _this.tool === "eraser") {
|
|
_this.mouseOverlay.css({ cursor: "none" });
|
|
} else if (_this.tool === "mouse") {
|
|
this.mouseOverlay.css({ cursor: "default" });
|
|
} else {
|
|
//Line, Rec, Circle, Cutting
|
|
_this.mouseOverlay.css({ cursor: "crosshair" });
|
|
}
|
|
},
|
|
};
|
|
|
|
function lanczosKernel(x) {
|
|
if (x == 0) {
|
|
return 1.0;
|
|
}
|
|
return (2 * Math.sin(Math.PI * x) * Math.sin((Math.PI * x) / 2)) / Math.pow(Math.PI * x, 2);
|
|
}
|
|
|
|
function lanczosInterpolate(xm1, ym1, x0, y0, x1, y1, x2, y2, a) {
|
|
var cm1 = lanczosKernel(1 + a);
|
|
var c0 = lanczosKernel(a);
|
|
var c1 = lanczosKernel(1 - a);
|
|
var c2 = lanczosKernel(2 - a);
|
|
var delta = (cm1 + c0 + c1 + c2 - 1) / 4;
|
|
cm1 -= delta;
|
|
c0 -= delta;
|
|
c1 -= delta;
|
|
c2 -= delta;
|
|
return [cm1 * xm1 + c0 * x0 + c1 * x1 + c2 * x2, cm1 * ym1 + c0 * y0 + c1 * y1 + c2 * y2];
|
|
}
|
|
|
|
export default whiteboard;
|