import { S } from './Script' ; import { MagnetTextManager } from './MagnetTextManager' ; import { ActionMagnetDelete } from './ActionMagnetDelete' ; import { ActionMagnetNew } from './ActionMagnetNew' ; import { Sound } from './Sound' ; import { UserManager } from './UserManager' ; import { ToolDraw } from './ToolDraw' ; import { Geometry } from './Geometry' ; import { ConstraintDrawing } from './ConstraintDrawing' ; import { ErrorMessage } from './ErrorMessage' ; import { getCanvas } from "./main" ; import { Share } from "./share" ; import { BoardManager } from './boardManager' ; import { Layout } from './Layout' ; import { Menu } from './Menu' ; import { TouchScreen } from './TouchScreen' ; import { ActionPrintMagnet } from './ActionPrintMagnet' ; export class MagnetManager { static currentMagnet = undefined ; static magnetUnderCursor = undefined ; param magnet returns static isVisible ( magnet ) { return magnet . style . visibility != "hidden" ; } param point returns static getMagnetNearPoint ( { x , y } ) { const magnets = MagnetManager . getMagnets ( ) ; for ( let i = 0 ; i < magnets . length ; i ++ ) { if ( MagnetManager . isVisible ( magnets [ i ] ) ) if ( MagnetManager . magnetNearContains ( magnets [ i ] , x , y ) ) return magnets [ i ] ; } return null ; } static getMagnetNearestFromPoint ( { x , y } ) { const magnets = MagnetManager . getMagnets ( ) ; let magnet = null ; let d = 99999999 ; for ( let i = 0 ; i < magnets . length ; i ++ ) { const m = MagnetManager . getMagnetCenter ( magnets [ i ] ) ; const dd = Geometry . distance ( m , { x : x , y : y } ) ; if ( dd < d ) { magnet = magnets [ i ] ; d = dd ; } } return magnet ; } /**
     * initialization
     */ static init ( ) { document . getElementById ( "clearMagnet" ) . onclick = MagnetManager . clearMagnet ; document . getElementById ( "magnetsArrange" ) . onclick = MagnetManager . arrange ; document . getElementById ( "magnetsCreateGraph" ) . onclick = MagnetManager . drawGraph ; } returns static getMagnetUnderCursor ( ) { return MagnetManager . magnetUnderCursor ; } returns static hasCurrentMagnet ( ) { return MagnetManager . currentMagnet == undefined ; } description static noCurrentMagnet ( ) { MagnetManager . currentMagnet = undefined ; } returns static getCurrentMagnetID ( ) { return MagnetManager . currentMagnet . id ; } param { boolean } b description static setInteractable ( b ) { const v = b ? "auto" : "none" ; const magnets = MagnetManager . getMagnets ( ) ; for ( let i = 0 ; i < magnets . length ; i ++ ) ( < HTMLElement > magnets [ i ] ) . style . pointerEvents = v ; } returns static getMagnets ( ) { return < HTMLCollectionOf < HTMLElement > > document . getElementsByClassName ( "magnet" ) ; } /**
     * reset all magnets (performed when the board is reset)
     */ static resetMagnets ( ) { document . getElementById ( "magnets" ) . innerHTML = "" ; } /**
     * delete all the magnets, i.e. magnet deletion actions are added in the timeline
     */ static clearMagnet ( ) { const magnets = MagnetManager . getMagnets ( ) ; for ( let i = 0 ; i < magnets . length ; i ++ ) { Share . execute ( "magnetRemove" , [ magnets [ i ] . id ] ) ; } Menu . hide ( ) ; } param { * } element description static addMagnet ( element ) { if ( element . id == "" ) element . id = MagnetManager . generateID ( ) ; MagnetManager . currentMagnet = element ; element . classList . add ( "magnet" ) ; document . getElementById ( "magnets" ) . appendChild ( element ) ; const f = ( ) => { BoardManager . addAction ( new ActionMagnetNew ( UserManager . me . userID , element ) ) ; if ( Share . isShared ( ) ) Share . sendNewMagnet ( element ) ; } ; if ( element . tagName == "IMG" ) element . addEventListener ( "load" , f ) ; else f ( ) ; MagnetManager . _installMagnet ( element ) ; } returns private static generateID ( ) { let id = "" ; do { id = "m" + Math . round ( Math . random ( ) * 1000000 ) ; } while ( document . getElementById ( id ) != undefined ) ; return id ; } param { * } userid param { * } element description static addMagnetFromAnotherUser ( userid , element ) { if ( element . id == "" ) element . id = MagnetManager . generateID ( ) ; MagnetManager . currentMagnet = element ; element . classList . add ( "magnet" ) ; document . getElementById ( "magnets" ) . appendChild ( element ) ; BoardManager . addAction ( new ActionMagnetNew ( userid , element ) ) ; MagnetManager . _installMagnet ( element ) ; } param m param x param y returns static magnetContains ( m , x , y ) { return ( parseInt ( m . style . left ) <= x && parseInt ( m . style . top ) <= y && x <= parseInt ( m . style . left ) + ( m . clientWidth ) && y <= parseInt ( m . style . top ) + ( m . clientHeight ) ) ; } param m param x param y returns static magnetNearContains ( m , x , y ) { const nbpixelsTheshold = 24 ; return ( parseInt ( m . style . left ) - nbpixelsTheshold <= x && parseInt ( m . style . top ) - nbpixelsTheshold <= y && x <= parseInt ( m . style . left ) + ( m . clientWidth ) + nbpixelsTheshold && y <= parseInt ( m . style . top ) + ( m . clientHeight ) + nbpixelsTheshold ) ; } description static arrange ( ) { const magnets = MagnetManager . getMagnets ( ) ; for ( let i = 0 ; i < magnets . length ; i ++ ) { const magnet = magnets [ i ] ; let x = undefined ; let y = undefined ; const dist = ( ) => { let minDist = 100000 ; for ( let j = 0 ; j < magnets . length ; j ++ ) { minDist = Math . min ( minDist , Math . abs ( x - parseInt ( ( < HTMLElement > magnets [ j ] ) . style . left ) ) + Math . abs ( y - parseInt ( ( < HTMLElement > magnets [ j ] ) . style . top ) ) ) ; } return minDist ; } ; const contains = ( ) => { for ( let j = 0 ; j < magnets . length ; j ++ ) { if ( MagnetManager . magnetContains ( magnets [ j ] , x , y ) || MagnetManager . magnetContains ( magnets [ j ] , x + magnet . clientWidth , y + magnet . clientHeight ) ) return true ; } return false ; } ; const rect = BoardManager . getCurrentScreenRectangle ( ) ; const generatePosition = ( ) => { let count = 0 ; const margin = 32 ; do { x = rect . x1 + ( Math . random ( ) * Layout . getWindowWidth ( ) ) ; y = rect . y1 + ( Math . random ( ) * 3 * Layout . getWindowHeight ( ) / 4 ) ; x = Math . max ( x , rect . x1 + margin ) ; y = Math . max ( y , rect . y1 + margin ) ; x = Math . min ( x , rect . x2 - magnet . clientWidth - margin ) ; y = Math . min ( y , rect . y2 - magnet . clientHeight - margin ) ; count ++ ; } while ( contains ( ) && count < 50 ) ; } ; let count = 0 ; let bestDist = 0 ; let bestX = undefined ; let bestY = undefined ; while ( count < 30 ) { generatePosition ( ) ; if ( bestDist < dist ( ) ) { bestX = x ; bestY = y ; bestDist = dist ( ) ; } count ++ ; } magnet . style . left = bestX ; magnet . style . top = bestY ; } } static getMagnetCenter ( m ) { return { x : Math . round ( parseInt ( m . style . left ) + m . clientWidth / 2 ) , y : Math . round ( parseInt ( m . style . top ) + m . clientHeight / 2 ) } ; } static getMagnetMiddleButton ( m ) { return { x : Math . round ( parseInt ( m . style . left ) + m . clientWidth / 2 ) , y : Math . round ( parseInt ( m . style . top ) + m . clientHeight ) } ; } returns static getNodes ( ) { const magnets = MagnetManager . getMagnets ( ) ; const nodes = [ ] ; for ( let i = 0 ; i < magnets . length ; i ++ ) { const m = magnets [ i ] ; const p = MagnetManager . getMagnetCenter ( m ) ; nodes . push ( { magnet : m , x : p . x , y : p . y } ) ; } return nodes ; } description static drawGraph ( ) { MagnetManager . arrange ( ) ; const nodes = MagnetManager . getNodes ( ) ; const edges = [ ] ; for ( let i = 0 ; i < nodes . length ; i ++ ) { edges [ i ] = [ ] ; for ( let j = 0 ; j < nodes . length ; j ++ ) { edges [ i ] [ j ] = 0 ; } } function intersects ( a , b , c , d , p , q , r , s ) { const det = ( c - a ) * ( s - q ) - ( r - p ) * ( d - b ) ; if ( det === 0 ) { return false ; } else { const lambda = ( ( s - q ) * ( r - a ) + ( p - r ) * ( s - b ) ) / det ; const gamma = ( ( b - d ) * ( r - a ) + ( c - a ) * ( s - b ) ) / det ; return ( 0 < lambda && lambda < 1 ) && ( 0 < gamma && gamma < 1 ) ; } } const isCrossing = ( i , j ) => { for ( let k = 0 ; k < nodes . length ; k ++ ) for ( let l = 0 ; l < nodes . length ; l ++ ) if ( edges [ k ] [ l ] ) { if ( intersects ( nodes [ i ] . x , nodes [ i ] . y , nodes [ j ] . x , nodes [ j ] . y , nodes [ k ] . x , nodes [ k ] . y , nodes [ l ] . x , nodes [ l ] . y ) ) return true ; } return false ; } ; for ( let i = 0 ; i < nodes . length ; i ++ ) for ( let j = 0 ; j < nodes . length ; j ++ ) { if ( Math . abs ( nodes [ i ] . x - nodes [ j ] . x ) + Math . abs ( nodes [ i ] . y - nodes [ j ] . y ) < 400 && ! isCrossing ( i , j ) ) { edges [ i ] [ j ] = 1 ; const line = ToolDraw . addSVGLine ( nodes [ i ] . x , nodes [ i ] . y , nodes [ j ] . x , nodes [ j ] . y , 2.0 , UserManager . me . color ) ; ConstraintDrawing . freeDraw ( [ line ] , nodes [ i ] . magnet . id , nodes [ j ] . magnet . id , MagnetManager . getMagnetCenter ( nodes [ i ] . magnet ) , MagnetManager . getMagnetCenter ( nodes [ j ] . magnet ) ) ; } } } description static installMagnets ( ) { const magnets = MagnetManager . getMagnets ( ) ; for ( let i = 0 ; i < magnets . length ; i ++ ) MagnetManager . _installMagnet ( magnets [ i ] ) ; eval ( "MathJax.typeset();" ) ; } param element description static setZIndex ( element ) { const f = ( ) => { const LARGENUMBER = 10000 ; element . style . zIndex = "" + ( LARGENUMBER - element . clientWidth ) ; } ; if ( element . tagName == "IMG" ) element . addEventListener ( "load" , f ) ; else f ( ) ; } param element returns private static createCopyMagnet ( element ) { const copy = < HTMLElement > element . cloneNode ( true ) ; copy . id = "" ; copy . onclick = element . onclick ; return copy ; } param element description private static magnetUnFocus ( element ) { element . blur ( ) ; if ( element . children . length > 0 ) ( < HTMLElement > element . children [ 0 ] ) . blur ( ) ; } param magnet returns private static getContainedMagnets ( element ) { param { * } element param { * } bigElement returns function inside ( element , bigElement ) { return element . offsetLeft > bigElement . offsetLeft && element . offsetTop > bigElement . offsetTop && element . offsetLeft + element . clientWidth < bigElement . offsetLeft + bigElement . clientWidth && element . offsetTop + element . clientHeight < bigElement . offsetTop + bigElement . clientHeight ; } const magnets = MagnetManager . getMagnets ( ) ; const otherElementsToMove = [ ] ; for ( let i = 0 ; i < magnets . length ; i ++ ) if ( magnets [ i ] != element && inside ( magnets [ i ] , element ) && MagnetManager . isVisible ( magnets [ i ] ) ) otherElementsToMove . push ( magnets [ i ] ) ; return otherElementsToMove ; } param element description private static makeDraggableElement ( element ) { let dx = 0 , dy = 0 , x = 0 , y = 0 ; element . addEventListener ( "pointerdown" , dragMouseDown ) ; TouchScreen . addTouchEvents ( element ) ; let otherElementsToMove = [ ] ; let canvasCursorStore = undefined ; let drag = true ; function dragMouseDown ( evt ) { drag = true ; MagnetManager . currentMagnet = element ; if ( evt . ctrlKey ) { /**makes a copy. The copy does not move. */ const copy = MagnetManager . createCopyMagnet ( element ) ; MagnetManager . addMagnet ( copy ) ; Share . execute ( "magnetMove" , [ copy . id , element . style . left , element . style . top ] ) ; } const canvas = getCanvas ( ) ; canvasCursorStore = canvas . style . cursor ; evt = evt || window . event ; evt . preventDefault ( ) ; x = evt . clientX * Layout . getZoom ( ) ; y = evt . clientY * Layout . getZoom ( ) ; MagnetManager . magnetUnFocus ( element ) ; document . onpointermove = elementDrag ; document . onpointerup = closeDragElement ; otherElementsToMove = MagnetManager . getContainedMagnets ( element ) ; Share . execute ( "magnetMoveStart" , [ element . id ] ) ; for ( const m of otherElementsToMove ) Share . execute ( "magnetMoveStart" , [ m . id ] ) ; S . onmagnetmousedown ( evt ) ; } function elementDrag ( e ) { if ( ! drag ) return ; MagnetManager . currentMagnet = element ; e . target . classList . add ( "magnetDrag" ) ; const canvas = getCanvas ( ) ; canvas . style . cursor = "none" ; e = e || window . event ; e . preventDefault ( ) ; dx = x - e . clientX * Layout . getZoom ( ) ; dy = y - e . clientY * Layout . getZoom ( ) ; x = e . clientX * Layout . getZoom ( ) ; y = e . clientY * Layout . getZoom ( ) ; const treatPointForMagnet = ( magnet ) => { const x = magnet . offsetLeft - dx ; const y = magnet . offsetTop - dy ; Share . execute ( "magnetMove" , [ magnet . id , x , y ] ) ; } ; treatPointForMagnet ( element ) ; for ( const el of otherElementsToMove ) treatPointForMagnet ( el ) ; ConstraintDrawing . update ( ) ; S . onmagnetmove ( e ) ; } function closeDragElement ( ) { if ( ! drag ) return ; drag = false ; const storeActionForMagnet = ( magnet ) => { Share . execute ( "magnetMoveStop" , [ magnet . id ] ) ; } ; storeActionForMagnet ( element ) ; for ( const el of otherElementsToMove ) storeActionForMagnet ( el ) ; const magnets = MagnetManager . getMagnets ( ) ; for ( let i = 0 ; i < magnets . length ; i ++ ) { magnets [ i ] . classList . remove ( "magnetDrag" ) ; } getCanvas ( ) . style . cursor = canvasCursorStore ; document . onmouseup = null ; document . onmousemove = null ; } } param element returns static getFont ( element ) { return ( < HTMLElement > element . children [ 0 ] ) . style . fontSize ; } static getTextColor ( element ) { return ( < HTMLElement > element . children [ 0 ] ) . style . color ; } param element returns static getText ( element ) { const content = element . children [ 0 ] ; if ( content . children . length == 0 ) return content . textContent ; else return Array . from ( content . children ) . map ( ( e ) => e . textContent ) . join ( '\n' ) ; } param element description static _installMagnet ( element ) { if ( MagnetTextManager . isTextMagnet ( element ) ) MagnetTextManager . installMagnetText ( element ) ; MagnetManager . makeDraggableElement ( element ) ; MagnetManager . setZIndex ( element ) ; element . onmouseenter = ( ) => { MagnetManager . magnetUnderCursor = element ; } ; element . onmouseleave = ( ) => { MagnetManager . magnetUnderCursor = undefined ; } ; } description static removeCurrentMagnet ( ) { if ( MagnetManager . currentMagnet == undefined ) return ; if ( ! MagnetManager . currentMagnet . classList . contains ( "magnet" ) ) ErrorMessage . show ( "oups, important bug. The application wanted to delete something else than a magnet!" ) ; Share . execute ( "magnetRemove" , [ MagnetManager . currentMagnet . id ] ) ; } param { * } id description static magnetRemove ( id ) { console . log ( `magnetRemove ${ id }` ) ; BoardManager . addAction ( new ActionMagnetDelete ( UserManager . me . userID , id ) ) ; MagnetManager . currentMagnet == undefined ; MagnetManager . magnetUnderCursor = undefined ; ConstraintDrawing . update ( ) ; } param magnet description static printMagnet ( magnet ) { if ( ! ( magnet instanceof HTMLImageElement ) ) return ; Sound . play ( "magnetprint" ) ; const magnetsOnTop = MagnetManager . getContainedMagnets ( magnet ) ; const doPrintMagnet = ( m ) => { const action = new ActionPrintMagnet ( UserManager . me . userID , m , parseInt ( m . style . left ) , parseInt ( m . style . top ) ) ; action . redo ( ) ; BoardManager . addAction ( action ) ; } ; doPrintMagnet ( magnet ) ; magnetsOnTop . map ( doPrintMagnet ) ; } static magnetColors = [ '' , 'rgb(255, 128, 0)' , 'rgb(0, 128, 0)' , 'rgb(192, 0, 0)' , 'rgb(0, 0, 255)' ] ; static nextBackgroundColor ( color ) { for ( let i = 0 ; i < MagnetManager . magnetColors . length ; i ++ ) { if ( MagnetManager . magnetColors [ i ] == color ) { return MagnetManager . magnetColors [ ( i + 1 ) % MagnetManager . magnetColors . length ] ; } } return MagnetManager . magnetColors [ 0 ] ; } static previousBackgroundColor ( color ) { for ( let i = 0 ; i < MagnetManager . magnetColors . length ; i ++ ) { if ( MagnetManager . magnetColors [ i ] == color ) { return MagnetManager . magnetColors [ ( i - 1 ) % MagnetManager . magnetColors . length ] ; } } return MagnetManager . magnetColors [ 0 ] ; } } window [ 'MagnetManager' ] = MagnetManager ;