// loadDrawing = function(identifier){ // const request = new Request('/copy_and_load/'+identifier, { // method: 'GET', // }); // // fetch(request) // // .then(response => response.json()) // // .then(data => { // // const metadata_req = new Request(`/annotations/${data.file}`, { // // method: 'GET', // // }); // // fetch(metadata_req) // // .then(response => response.ok ? response.json() : null) // // .then(metadata => { // // if (metadata !== null) { // // metadata.annotations = metadata.annotations.map((a) => new Annotation(a.tag, a.t_in, a.t_out)) // // } // // this.loadStrokes(data, metadata) // // }) // // .catch(e => console.log(e)); // // // do something with the data sent in the request // // }); // } class Canvas { constructor(wrapperEl, preload_id) { this.allowDrawing = false; this.socket = null; // don't initialise right away this.viewbox = { "x": 0, "y": 0, "width": null, "height": null }; this.url = window.location.origin.replace('http', 'ws') + '/ws?' + window.location.search.substring(1); // build the interface this.wrapperEl = wrapperEl; this.wrapperEl.classList.add('closed'); this.svgEl = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); this.wrapperEl.appendChild(this.svgEl); this.toolboxEl = document.createElement('div'); this.toolboxEl.classList.add('toolbox') this.wrapperEl.appendChild(this.toolboxEl); this.filenameEl = document.createElement('div'); this.filenameEl.classList.add('filename') this.wrapperEl.appendChild(this.filenameEl); this.fullscreenEl = document.createElement('div'); this.fullscreenEl.classList.add('button-fullscreen'); this.fullscreenEl.innerText = "Fullscreen"; this.wrapperEl.appendChild(this.fullscreenEl); this.fullscreenEl.addEventListener('click', (e) => { document.body.requestFullscreen(); }); document.body.addEventListener('fullscreenchange', (e) => { if (document.fullscreenElement) { document.body.classList.add('fullscreen'); } else { document.body.classList.remove('fullscreen'); } }) this.colors = ["black", "red", "blue", "green"]; this.resize(); window.addEventListener('resize', (ev) => this.requestResize()); this.paths = []; this.viewboxes = []; this.events = []; // all paths & viewboxes events this.isDrawing = false; this.hasMouseDown = false; this.currentStrokeEl = null; this.startTime = null; this.isMoving = false; document.body.addEventListener('pointermove', (ev) => { ev.stopPropagation(); ev.preventDefault(); if (ev.pointerType == "touch" || ev.buttons & 2) { // 4: middle mouse button this.moveCanvas(ev); } else { // pointerType == pen or mouse this.draw(ev); } }); document.body.addEventListener('pointerup', (ev) => { ev.stopPropagation(); ev.preventDefault(); if (ev.pointerType == "touch" || ev.buttons & 2 || this.isMoving) { // buttons is 0 on pointerup this.endMoveCanvas(ev); this.isMoving = false; } else { // pointerType == pen or mouse location.hash = '#' + this.filename; // only update when drawn. this.penup(ev); } }); this.svgEl.addEventListener('contextmenu', function (e) { // do something here... e.preventDefault(); }, false); this.svgEl.addEventListener('pointerdown', (ev) => { ev.stopPropagation(); ev.preventDefault(); if (ev.pointerType == "touch" || ev.buttons & 2) { // 4: middle mouse button, 2; right mouse button this.isMoving = true; this.startMoveCanvas(ev); } else if (ev.buttons & 1) { // pointerType == pen or mouse this.startStroke(ev); } }); this.createToolbox(); this.setColor(this.colors[0]); this.socket = new WebSocket(this.url); this.socket.addEventListener('open', (e) => { this.sendDimensions(); if (preload_id) { // signal if we want to continue from an existing drawing this.socket.send(JSON.stringify({ 'event': 'preload', 'file': preload_id, })); } }); this.socket.addEventListener('message', (e) => { let msg = JSON.parse(e.data); console.log('receive', msg); if (msg.hasOwnProperty('filename')) { console.log('filename', msg.filename); this.setFilename(msg.filename); this.openTheFloor() } if (msg.hasOwnProperty('preloaded_svg')) { console.log('preloaded', msg); if (msg.dimensions[0] != this.viewbox.width || msg.dimensions[1] != this.viewbox.height) { alert(`Loading file with different dimensions. This can lead to odd results. Original: ${msg.dimensions[0]}x${msg.dimensions[1]} Now: ${this.viewbox.width}x${this.viewbox.height}`) } this.setPreloaded(msg.preloaded_svg); // this.setFilename(msg.filename); // this.openTheFloor() } }); } setPreloaded(json_url) { this.preloaded_resource = json_url const request = new Request(this.preloaded_resource + '.svg', { method: 'GET', }); fetch(request) .then(response => response.text()) .then(body => { const parser = new DOMParser() const dom = parser.parseFromString(body, "image/svg+xml"); console.log(dom, dom.getRootNode().querySelectorAll('g')) const group = dom.getRootNode().querySelectorAll('g')[0] this.svgEl.prepend(group); }) } startMoveCanvas(ev) { this.moveCanvasPrevPoint = { "x": ev.x, "y": ev.y }; this.currentMoves = []; } endMoveCanvas(ev) { this.moveCanvasPrevPoint = null; // sync viewpoints const d = { 'event': 'viewbox', 'viewboxes': this.currentMoves }; console.log('send', d); this.socket.send(JSON.stringify(d)); } moveCanvas(ev) { if (this.moveCanvasPrevPoint === null) { return } const diff = { "x": ev.x - this.moveCanvasPrevPoint.x, "y": ev.y - this.moveCanvasPrevPoint.y, } this.viewbox.x -= diff.x; this.viewbox.y -= diff.y; this.moveCanvasPrevPoint = { "x": ev.x, "y": ev.y }; this.currentMoves.push(Object.assign({ 't': window.performance.now() - this.startTime }, this.viewbox)); this.applyViewBox() } openTheFloor() { this.wrapperEl.classList.remove('closed'); } setFilename(filename) { this.filename = filename; this.filenameEl.innerText = filename; } createToolbox() { const colorsEl = document.createElement('ul'); colorsEl.classList.add('colors'); for (let color of this.colors) { const colorEl = document.createElement('li'); colorEl.style.background = color; colorEl.addEventListener('click', (e) => { console.log('set color', color) this.setColor(color); }) colorsEl.appendChild(colorEl); } this.toolboxEl.appendChild(colorsEl); } setColor(color) { this.currentColor = color; const colorEls = this.toolboxEl.querySelectorAll('.colors li'); for (let colorEl of colorEls) { if (colorEl.style.backgroundColor == color) { colorEl.classList.add('selected'); } else { colorEl.classList.remove('selected'); } } } resize() { this.viewbox.width = window.innerWidth; this.viewbox.height = window.innerHeight; this.applyViewBox(); this.sendDimensions(); } sendDimensions() { const d = { 'event': 'dimensions', 'width': this.viewbox.width, 'height': this.viewbox.height }; if (this.socket === null) { // ignore ... } else if (this.socket.readyState) { this.socket.send(JSON.stringify(d)); } else { this.socket.addEventListener('open', (ev) => { this.socket.send(JSON.stringify(d)); }) } } applyViewBox() { const viewBox = `${this.viewbox.x} ${this.viewbox.y} ${this.viewbox.width} ${this.viewbox.height}`; this.svgEl.setAttribute('viewBox', viewBox); this.svgEl.setAttribute('width', this.viewbox.width + 'mm'); this.svgEl.setAttribute('height', this.viewbox.height + 'mm'); // todo save drag event; // const newViewbox = Object.assign({}, this.viewbox, {'t': window.performance.now() - this.startTime}); // const lastViewbox = this.viewboxes[this.viewboxes.length - 1]; // if(newViewbox.x == lastViewbox.x && newViewbox.y == lastViewbox.y && newViewbox.width == lastViewbox.width && newViewbox.height == lastViewbox.height){ // // do nothing, avoiding duplicate // } else { // this.viewboxes.push(newViewbox); // this.events.push(newViewbox); // } } requestResize() { this.resize(); // alert('Resize not implemented yet. Please reloade the page'); } getCoordinates(e) { // convert event coordinates into relative positions on x & y axis let box = this.svgEl.getBoundingClientRect(); let x = (e.x - box['left'] + this.viewbox.x) / box['width']; let y = (e.y - box['top'] + this.viewbox.y) / box['height']; return { 'x': x, 'y': y }; } isInsideBounds(pos) { return !(pos['x'] < 0 || pos['y'] < 0 || pos['x'] > 1 || pos['y'] > 1); } draw(e) { let pos = this.getCoordinates(e); if (!this.isDrawing && this.hasMouseDown /*&& this.isInsideBounds(pos)*/) { this.isDrawing = true; } if (this.isDrawing) { this.paths[this.paths.length - 1].points.push([pos['x'], pos['y'], 0, window.performance.now() - this.startTime]); let d = this.strokes2D(this.paths[this.paths.length - 1].points); this.currentStrokeEl.setAttribute('d', d); } } startStroke(e) { this.hasMouseDown = true; const strokeEl = document.createElementNS('http://www.w3.org/2000/svg', 'path'); strokeEl.style.stroke = this.currentColor; this.svgEl.appendChild(strokeEl); this.currentStrokeEl = strokeEl; let path = { 'el': strokeEl, 'color': this.currentColor, 'points': [] }; this.paths.push(path); this.events.push(path); // same ref. if (this.startTime === null) { // initiate timer on first stroke this.startTime = window.performance.now(); } } endStroke(pos) { if (!this.isDrawing) { return; } this.isDrawing = false; //document.body.removeEventListener('mousemove', draw); if (this.paths[this.paths.length - 1].points.length > 0) { // mark point as last of stroke this.paths[this.paths.length - 1].points[this.paths[this.paths.length - 1].points.length - 1][2] = 1; } const stroke = this.paths[this.paths.length - 1]; const d = { 'event': 'stroke', 'color': stroke.color, 'points': stroke.points }; console.log('send', d); this.socket.send(JSON.stringify(d)); } penup(e) { if (!this.hasMouseDown) { return; } this.hasMouseDown = false; let pos = this.getCoordinates(e); this.endStroke(pos); } strokes2D(strokes) { // strokes to a d attribute for a path let d = ""; let last_stroke = undefined; let cmd = ""; for (let stroke of strokes) { if (!last_stroke) { d += `M${stroke[0] * this.viewbox.width},${stroke[1] * this.viewbox.height} `; cmd = 'M'; } else { if (last_stroke[2] == 1) { d += " m"; cmd = 'm'; } else if (cmd != 'l') { d += ' l '; cmd = 'l'; } let rel_stroke = [stroke[0] - last_stroke[0], stroke[1] - last_stroke[1]]; d += `${rel_stroke[0] * this.viewbox.width},${rel_stroke[1] * this.viewbox.height} `; } last_stroke = stroke; } return d; } }