2022-02-03 16:39:59 +00:00
// 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
// // });
// }
2021-11-22 19:54:04 +00:00
class Canvas {
2022-02-03 16:39:59 +00:00
constructor ( wrapperEl , preload _id ) {
2021-11-22 19:54:04 +00:00
this . allowDrawing = false ;
2022-02-08 11:07:28 +00:00
this . socket = null ; // don't initialise right away
2021-12-21 12:14:32 +00:00
this . viewbox = { "x" : 0 , "y" : 0 , "width" : null , "height" : null } ;
2021-11-22 19:54:04 +00:00
this . url = window . location . origin . replace ( 'http' , 'ws' ) + '/ws?' + window . location . search . substring ( 1 ) ;
2021-12-21 12:14:32 +00:00
// build the interface
2021-11-22 19:54:04 +00:00
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 ) ;
2022-02-08 11:07:28 +00:00
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' ) ;
}
} )
2022-02-09 07:18:28 +00:00
this . colors = [ "black" , "#cc1414" , "blue" , "green" ] ;
2021-11-22 19:54:04 +00:00
this . resize ( ) ;
2022-01-19 12:32:06 +00:00
window . addEventListener ( 'resize' , ( ev ) => this . requestResize ( ) ) ;
2021-11-22 19:54:04 +00:00
this . paths = [ ] ;
2021-12-21 12:14:32 +00:00
this . viewboxes = [ ] ;
this . events = [ ] ; // all paths & viewboxes events
2021-11-22 19:54:04 +00:00
this . isDrawing = false ;
this . hasMouseDown = false ;
this . currentStrokeEl = null ;
this . startTime = null ;
2021-12-21 12:14:32 +00:00
this . isMoving = false ;
document . body . addEventListener ( 'pointermove' , ( ev ) => {
2022-01-19 12:32:06 +00:00
ev . stopPropagation ( ) ;
ev . preventDefault ( ) ;
2022-02-08 11:07:28 +00:00
if ( ev . pointerType == "touch" || ev . buttons & 2 ) { // 4: middle mouse button
2021-12-21 12:14:32 +00:00
this . moveCanvas ( ev ) ;
} else { // pointerType == pen or mouse
this . draw ( ev ) ;
}
} ) ;
document . body . addEventListener ( 'pointerup' , ( ev ) => {
2022-01-19 12:32:06 +00:00
ev . stopPropagation ( ) ;
ev . preventDefault ( ) ;
2022-02-08 11:07:28 +00:00
if ( ev . pointerType == "touch" || ev . buttons & 2 || this . isMoving ) { // buttons is 0 on pointerup
2021-12-21 12:14:32 +00:00
this . endMoveCanvas ( ev ) ;
this . isMoving = false ;
} else { // pointerType == pen or mouse
2022-02-03 16:39:59 +00:00
location . hash = '#' + this . filename ; // only update when drawn.
2021-12-21 12:14:32 +00:00
this . penup ( ev ) ;
}
} ) ;
2022-02-08 11:07:28 +00:00
this . svgEl . addEventListener ( 'contextmenu' , function ( e ) {
// do something here...
e . preventDefault ( ) ;
} , false ) ;
2021-12-21 12:14:32 +00:00
this . svgEl . addEventListener ( 'pointerdown' , ( ev ) => {
2022-01-19 12:32:06 +00:00
ev . stopPropagation ( ) ;
ev . preventDefault ( ) ;
2022-02-08 11:07:28 +00:00
if ( ev . pointerType == "touch" || ev . buttons & 2 ) { // 4: middle mouse button, 2; right mouse button
2021-12-21 12:14:32 +00:00
this . isMoving = true ;
this . startMoveCanvas ( ev ) ;
2022-02-08 11:07:28 +00:00
} else if ( ev . buttons & 1 ) { // pointerType == pen or mouse
2021-12-21 12:14:32 +00:00
this . startStroke ( ev ) ;
}
} ) ;
2021-11-22 19:54:04 +00:00
this . createToolbox ( ) ;
this . setColor ( this . colors [ 0 ] ) ;
2022-03-25 08:32:37 +00:00
this . socket = new WebSocket ( this . url ) ; // TODO: reconnectingwebsocket
2021-11-22 19:54:04 +00:00
this . socket . addEventListener ( 'open' , ( e ) => {
2022-02-08 11:07:28 +00:00
this . sendDimensions ( ) ;
2022-02-03 16:39:59 +00:00
if ( preload _id ) {
// signal if we want to continue from an existing drawing
this . socket . send ( JSON . stringify ( {
'event' : 'preload' ,
'file' : preload _id ,
} ) ) ;
}
2022-02-08 11:07:28 +00:00
} ) ;
2021-11-22 19:54:04 +00:00
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 ( )
}
2022-02-03 16:39:59 +00:00
if ( msg . hasOwnProperty ( 'preloaded_svg' ) ) {
console . log ( 'preloaded' , msg ) ;
2022-02-08 11:07:28 +00:00
if ( msg . dimensions [ 0 ] != this . viewbox . width || msg . dimensions [ 1 ] != this . viewbox . height ) {
2022-02-03 16:39:59 +00:00
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()
}
} ) ;
2022-03-25 08:32:37 +00:00
this . socket . addEventListener ( 'close' , ( e ) => {
this . closeTheFloor ( ) ;
alert ( 'Internet connection seems to have problems. Please reload the page to reconnect.' ) ;
} ) ;
2022-02-03 16:39:59 +00:00
}
setPreloaded ( json _url ) {
this . preloaded _resource = json _url
const request = new Request ( this . preloaded _resource + '.svg' , {
method : 'GET' ,
2021-11-22 19:54:04 +00:00
} ) ;
2022-02-03 16:39:59 +00:00
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 ) ;
} )
2021-11-22 19:54:04 +00:00
}
2021-12-21 12:14:32 +00:00
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 } ;
2022-02-03 16:39:59 +00:00
this . currentMoves . push ( Object . assign ( { 't' : window . performance . now ( ) - this . startTime } , this . viewbox ) ) ;
2021-12-21 12:14:32 +00:00
this . applyViewBox ( )
}
2021-11-22 19:54:04 +00:00
openTheFloor ( ) {
this . wrapperEl . classList . remove ( 'closed' ) ;
}
2022-03-25 08:32:37 +00:00
closeTheFloor ( ) {
this . wrapperEl . classList . add ( 'closed' ) ;
}
2021-11-22 19:54:04 +00:00
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 ( ) {
2021-12-21 12:14:32 +00:00
this . viewbox . width = window . innerWidth ;
this . viewbox . height = window . innerHeight ;
this . applyViewBox ( ) ;
2022-02-08 11:07:28 +00:00
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 ) ) ;
} )
}
2021-12-21 12:14:32 +00:00
}
applyViewBox ( ) {
const viewBox = ` ${ this . viewbox . x } ${ this . viewbox . y } ${ this . viewbox . width } ${ this . viewbox . height } ` ;
2021-11-22 19:54:04 +00:00
this . svgEl . setAttribute ( 'viewBox' , viewBox ) ;
2021-12-21 12:14:32 +00:00
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);
// }
2021-11-22 19:54:04 +00:00
}
requestResize ( ) {
2022-01-19 12:32:06 +00:00
this . resize ( ) ;
// alert('Resize not implemented yet. Please reloade the page');
2021-11-22 19:54:04 +00:00
}
getCoordinates ( e ) {
// convert event coordinates into relative positions on x & y axis
let box = this . svgEl . getBoundingClientRect ( ) ;
2022-02-09 07:18:28 +00:00
let x = ( e . x - box [ 'left' ] + this . viewbox . x ) ;
let y = ( e . y - box [ 'top' ] + this . viewbox . y ) ;
2021-11-22 19:54:04 +00:00
return { 'x' : x , 'y' : y } ;
}
2022-02-09 07:18:28 +00:00
// isInsideBounds(pos) {
// let box = this.svgEl.getBoundingClientRect();
// return !(pos['x'] < 0 || pos['y'] < 0 || pos['x'] > box['width'] || pos['y'] > box['height']);
// }
2021-11-22 19:54:04 +00:00
draw ( e ) {
let pos = this . getCoordinates ( e ) ;
2021-12-21 12:14:32 +00:00
if ( ! this . isDrawing && this . hasMouseDown /*&& this.isInsideBounds(pos)*/ ) {
2021-11-22 19:54:04 +00:00
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 ;
2021-12-21 12:14:32 +00:00
let path = {
2021-11-22 19:54:04 +00:00
'el' : strokeEl ,
'color' : this . currentColor ,
'points' : [ ]
2021-12-21 12:14:32 +00:00
} ;
this . paths . push ( path ) ;
this . events . push ( path ) ; // same ref.
2021-11-22 19:54:04 +00:00
if ( this . startTime === null ) {
// initiate timer on first stroke
this . startTime = window . performance . now ( ) ;
}
}
endStroke ( pos ) {
if ( ! this . isDrawing ) {
return ;
}
2022-02-03 16:39:59 +00:00
2021-11-22 19:54:04 +00:00
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 ] ;
2021-12-21 12:14:32 +00:00
const d = {
'event' : 'stroke' ,
2021-11-22 19:54:04 +00:00
'color' : stroke . color ,
'points' : stroke . points
2021-12-21 12:14:32 +00:00
} ;
console . log ( 'send' , d ) ;
this . socket . send ( JSON . stringify ( d ) ) ;
2021-11-22 19:54:04 +00:00
}
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 ) {
2022-02-09 07:18:28 +00:00
d += ` M ${ stroke [ 0 ] } , ${ stroke [ 1 ] } ` ;
2021-11-22 19:54:04 +00:00
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 ] ] ;
2022-02-09 07:18:28 +00:00
d += ` ${ rel _stroke [ 0 ] } , ${ rel _stroke [ 1 ] } ` ;
2021-11-22 19:54:04 +00:00
}
last _stroke = stroke ;
}
return d ;
}
}