import { LoadSave } from './LoadSave' ; import { Wallpaper } from './Wallpaper' ; import { UserListComponent } from './UserListComponent' ; import { BackgroundTexture } from './BackgroundTexture' ; import { ShareMessage , Parameter } from './ShareMessage' ; import { BackgroundDocuments } from './BackgroundDocuments' ; import { Loading } from './Loading' ; import { getCanvas } from './main' ; import { MagnetManager } from './magnetManager' ; import { BoardManager } from './boardManager' ; import { UserManager } from './UserManager' ; import { ErrorMessage } from './ErrorMessage' ; import { ShareEvent } from './ShareEvent' ; import config from './config.json' ; import { ConstraintDrawing } from './ConstraintDrawing' ; import { ActionDeserializer , HTMLdeserialize } from './ActionDeserializer' ; /**
 * the class that enables to share the board
 */ export class Share { static ws : WebSocket = undefined ; static id : string = undefined ; static canWriteValueByDefault = true ; static lastMessageTime = Date . now ( ) ; static isTryingToConnect = false ; param { * } a description static tryConnect ( f ) { Share . isTryingToConnect = true ; Share . id = Share . getIDInSharedURL ( ) ; if ( Share . ws != undefined ) return ; Share . ws = new WebSocket ( config . server . websocket ) ; Share . ws . binaryType = "arraybuffer" ; Share . ws . onerror = Share . whenDisconnected ; Share . ws . onclose = Share . whenDisconnected ; Share . ws . onopen = ( ) => { Share . protocolTestConnection ( ) ; f ( ) ; Share . isTryingToConnect = false ; } ; Share . ws . onmessage = ( msg ) => { Share . lastMessageTime = Date . now ( ) ; Share . _treatReceivedMessage ( JSON . parse ( msg . data ) ) ; } ; setInterval ( ( ) => this . send ( { type : "heartbeat" , userid : UserManager . me . userID } ) , 3000 ) ; } /**
     * executed when the connection is lost
     */ static whenDisconnected ( ) { ErrorMessage . show ( "You seem disconnected..." ) ; if ( ! Share . isTryingToConnect ) LoadSave . saveLocalStorage ( ) ; } /**
     * when launched, this function will keep testing the quality of the connection
     */ static protocolTestConnection ( ) { const CONNECTIVITYDELAY = 15000 ; setInterval ( ( ) => { if ( Date . now ( ) - Share . lastMessageTime > CONNECTIVITYDELAY ) { Share . whenDisconnected ( ) ; } } , CONNECTIVITYDELAY ) ; } returns /**static isOnGitHub(): boolean {
        return window.location.origin.indexOf("github") >= 0;
    }**/ /**
     * initialization
     */ static init ( ) { document . getElementById ( "shareButton" ) . onclick = ( ) => { if ( ! Share . isShared ( ) ) Share . share ( ) ; } ; document . getElementById ( "buttonShare" ) . onclick = function ( ) { if ( ! Share . isShared ( ) ) { Share . share ( ) ; } else { Share . copyShareUrl ( ) ; } } ; document . getElementById ( "joinButton" ) . onclick = ( ) => { window . open ( window . location . href , "_self" ) ; } ; document . getElementById ( "buttonOnlyRootCanWrite" ) . onclick = ( ) => Share . setCanWriteForAllExceptMeAndRoot ( false ) ; document . getElementById ( "buttonAllCanWrite" ) . onclick = ( ) => Share . setCanWriteForAllExceptMeAndRoot ( true ) ; /**if (!Share.isOnGitHub())
            document.getElementById('ShareGithub').hidden = true;**/ if ( Share . isSharedURL ( ) ) { const tryJoin = ( ) => { try { if ( Share . id != null ) { Share . join ( Share . id ) ; Share . updateQRCodeAndTextBoxWithURL ( ) ; } } catch ( e ) { Share . ws = undefined ; Share . showConnectionError ( ) ; } } ; Share . tryConnect ( tryJoin ) ; } document . getElementById ( "buttonAskPrivilege" ) . onclick = Share . askPrivilege ; document . getElementById ( "buttonCopyShareUrl" ) . onclick = Share . copyShareUrl ; } description static askPrivilege ( ) { const passwordCandidate = ( < HTMLInputElement > document . getElementById ( "passwordCandidate" ) ) . value ; Share . send ( { type : "askprivilege" , password : passwordCandidate } ) ; } description static copyShareUrl ( ) { const sharelink = ( < HTMLInputElement > document . getElementById ( "shareUrl" ) ) . value ; navigator . clipboard . writeText ( sharelink ) . then ( ( ) => { document . getElementById ( "shareUrlCopied" ) . hidden = false ; } , ( ) => { document . getElementById ( "shareUrlCopied" ) . hidden = false ; } ) ; } returns static isShared ( ) { return Share . id != undefined ; } description static showConnectionError ( ) { /**if (Share.isOnGitHub())
            ErrorMessage.show("For sharing, first go to a deployed server. Go to menu/share for more information.");
        else*/ ErrorMessage . show ( "Impossible to connect to the server" ) ; } returns static isRoot ( ) { return document . getElementById ( "askPrivilege" ) . hidden ; } returns static isPassword ( ) { return ( < HTMLInputElement > document . getElementById ( "password" ) ) . value != "" ; } description static share ( ) { try { const password = ( < HTMLInputElement > document . getElementById ( "password" ) ) . value ; Share . tryConnect ( ( ) => Share . send ( { type : "share" , password : password } ) ) ; document . getElementById ( "buttonShare" ) . classList . add ( "alreadyShared" ) ; document . getElementById ( "join" ) . hidden = true ; Share . setCanWriteForAllExceptMeAndRoot ( ( password == "" ) ) ; Share . setRoot ( UserManager . me . userID ) ; } catch ( e ) { Share . ws = undefined ; Share . showConnectionError ( ) ; } } param { * } msg description static _treatReceivedMessage ( msg ) { switch ( msg . type ) { case "id" : Share . _setTableauID ( msg . id ) ; break ; case "youruserid" : UserManager . setMyUserID ( msg . userid ) ; document . getElementById ( "shareAndJoin" ) . hidden = true ; document . getElementById ( "buttonShare" ) . classList . add ( "alreadyShared" ) ; break ; case "user" : console . log ( "existing user: " , msg . userid ) ; if ( msg . userid == UserManager . me . userID ) throw "oops... an already existing user has the same name than me" ; UserManager . add ( msg . userid ) ; break ; case "wait" : Loading . show ( ) ; break ; case "ready" : Loading . hide ( ) ; break ; case "setWidth" : getCanvas ( ) . width = msg . width ; break ; case "root" : if ( UserManager . getNumberOfUsers ( ) == 1 ) Share . canWriteValueByDefault = true ; Share . execute ( "setRoot" , [ UserManager . me . userID ] ) ; break ; case "accessdenied" : ErrorMessage . show ( "Access denied" ) ; break ; case "join" : console . log ( "a new user is joining: " , msg . userid ) ; UserManager . add ( msg . userid ) ; if ( UserManager . isIamResponsibleForData ( ) ) Share . sendAllDataTo ( msg . userid ) ; break ; case "heartbeat" : UserManager . users [ msg . userid ] . recordHeartBeat ( ) ; break ; case "leave" : UserManager . leave ( msg . userid ) ; break ; case "fullCanvas" : Loading . hide ( ) ; BoardManager . load ( msg . data ) ; break ; case "actions" : Loading . hide ( ) ; console . log ( "list of actions received" ) ; BoardManager . timeline . load ( JSON . parse ( msg . data ) , msg . t ) ; console . log ( "list of actions loaded" ) ; break ; case "action" : BoardManager . timeline . insertActionNowAlreadyExecuted ( ActionDeserializer . deserialize ( msg . action ) ) ; break ; case "svg" : console . log ( "received svg!" ) ; document . getElementById ( "svg" ) . innerHTML = msg . data ; ConstraintDrawing . reset ( ) ; break ; case "documents" : BackgroundDocuments . getDocumentPanel ( ) . innerHTML = msg . data ; break ; case "magnets" : document . getElementById ( "magnets" ) . innerHTML = msg . magnets ; MagnetManager . installMagnets ( ) ; break ; case "background" : Wallpaper . set ( msg . data ) ; break ; case "magnetChanged" : document . getElementById ( msg . magnetid ) . outerHTML = msg . data ; MagnetManager . installMagnets ( ) ; break ; case "newmagnet" : MagnetManager . addMagnetFromAnotherUser ( msg . userid , HTMLdeserialize ( msg . data ) ) ; break ; case "execute" : ShareEvent [ msg . event ] ( ... msg . params ) ; break ; } } private static sendAllDataTo ( idNewUser ) { Share . send ( { type : "wait" , to : idNewUser } ) ; Share . executeTo ( "setCanWriteValueByDefault" , [ Share . canWriteValueByDefault ] , idNewUser ) ; Share . execute ( "setUserCanWrite" , [ idNewUser , Share . canWriteValueByDefault ] ) ; for ( const userid in UserManager . users ) { Share . executeTo ( "setUserName" , [ userid , UserManager . users [ userid ] . name ] , idNewUser ) ; Share . executeTo ( "setCurrentColor" , [ userid , UserManager . users [ userid ] . color ] , idNewUser ) ; Share . executeTo ( "setUserCanWrite" , [ userid , UserManager . users [ userid ] . canWrite ] , idNewUser ) ; if ( UserManager . users [ userid ] . isRoot ) Share . executeTo ( "setRoot" , [ userid ] , idNewUser ) ; } Share . send ( { type : "setWidth" , width : getCanvas ( ) . width , to : idNewUser } ) ; Share . executeTo ( "setBackgroundColor" , [ BackgroundTexture . currentBackgroundTexture ] , idNewUser ) ; Share . sendMagnets ( idNewUser ) ; Share . send ( { type : "actions" , to : idNewUser , data : JSON . stringify ( BoardManager . timeline . serialize ( ) ) , t : BoardManager . timeline . getCurrentIndex ( ) } ) ; /**for(const action of BoardManager.cancelStack.stack) {
            Share.send({ type: "action", to: msg.userid, action: action.serialize()});
        }*/ Share . send ( { type : "svg" , to : idNewUser , data : document . getElementById ( "svg" ) . innerHTML } ) ; Share . send ( { type : "ready" , to : idNewUser } ) ; Share . send ( { type : "documents" , to : idNewUser , data : BackgroundDocuments . getDocumentPanel ( ) . innerHTML } ) ; if ( Wallpaper . is ) Share . send ( { type : "background" , to : idNewUser , data : Wallpaper . dataURL } ) ; } /**
     * set the user of id userid to be a root user (all privileges)
     * modify the interface if it is the current user
     */ static setRoot ( userid ) { console . log ( `setRoot(${ userid })` ) ; UserManager . setUserCanWrite ( userid , true ) ; UserManager . users [ userid ] . isRoot = true ; if ( UserManager . me . userID == userid ) { console . log ( `setRoot(${ userid }): it is me` ) ; document . getElementById ( "askPrivilege" ) . hidden = true ; document . getElementById ( "shareMode" ) . hidden = false ; UserListComponent . updateGUIUsers ( ) ; } else UserListComponent . updateGUIUser ( userid ) ; } param { * } msg description static send ( msg ) { msg . id = Share . id ; this . ws . send ( JSON . stringify ( msg ) ) ; } param { * } blob param { * } to description static sendFullCanvas ( to ? ) { Share . send ( { type : "fullCanvas" , data : getCanvas ( ) . toDataURL ( ) , to : to } ) ; } param to description static sendMagnets ( to ? ) { if ( Share . isShared ( ) ) { if ( to ) Share . send ( { type : "magnets" , magnets : document . getElementById ( "magnets" ) . innerHTML , to : to } ) ; else Share . send ( { type : "magnets" , magnets : document . getElementById ( "magnets" ) . innerHTML } ) ; } } param element description static sendNewMagnet ( element ) { console . log ( "new magnet sent!" ) ; Share . send ( { type : "newmagnet" , userid : UserManager . me . userID , data : element . outerHTML } ) ; } param element description static sendMagnetChanged ( element ) { Share . send ( { type : "magnetChanged" , magnetid : element . id , data : element . outerHTML } ) ; } private static _adaptObjectForExecute ( obj ) { if ( obj instanceof MouseEvent ) { return { pressure : ( < PointerEvent > obj ) . pressure , offsetX : obj . offsetX , offsetY : obj . offsetY , shiftKey : obj . shiftKey , ctrlKey : obj . ctrlKey } ; } else return obj ; } param { * } event param { * } params param { * } to description static executeTo ( event , params , to ) { Share . send ( < ShareMessage > { type : "execute" , event : event , params : params . map ( ( param ) => Share . _adaptObjectForExecute ( param ) ) , to : to } ) ; } param { * } event param { * } params description static execute ( event , params ) { ShareEvent [ event ] ( ... params ) ; if ( Share . isShared ( ) ) Share . send ( < ShareMessage > { type : "execute" , event : event , params : params . map ( ( param ) => Share . _adaptObjectForExecute ( param ) ) } ) ; } description static updateQRCodeAndTextBoxWithURL ( ) { Array . from ( document . getElementsByClassName ( "visibleIfShared" ) ) . forEach ( element => { ( < HTMLElement > element ) . hidden = false ; } ) ; const url = document . location . href ; param newUrl description const generateQRCode = ( url ) => { const imgQRCode = < HTMLImageElement > document . getElementById ( "qrcode" ) ; imgQRCode . hidden = false ; imgQRCode . src = "https://chart.googleapis.com/chart?cht=qr&chl=" + url + "&chs=200x200&chld=L|0" ; } ; const urlCorrected = url . startsWith ( "file://" ) ? config . server . frontend + "?id=" + Share . id : url ; generateQRCode ( urlCorrected ) ; ( < HTMLInputElement > document . getElementById ( "shareUrl" ) ) . value = urlCorrected ; } param id description static _setTableauID ( id ) { Share . id = id ; const url = document . location . href ; const newUrl = url + "?id=" + id ; history . pushState ( { } , null , newUrl ) ; Share . updateQRCodeAndTextBoxWithURL ( ) ; UserListComponent . updateGUIUsers ( ) ; } returns static isSharedURL ( ) { const params = ( new URL ( document . location . href ) ) . searchParams ; return params . get ( 'id' ) != null ; } returns static getTableauNoirID ( ) { return Share . isSharedURL ( ) ? Share . getIDInSharedURL ( ) : "local" ; } returns static getIDInSharedURL ( ) { return ( new URL ( document . location . href ) ) . searchParams . get ( 'id' ) ; } param id description static join ( id ) { LoadSave . loadFromLocalStorage ( ) ; Share . execute ( "setUserCanWrite" , [ UserManager . me . userID , false ] ) ; Share . canWriteValueByDefault = false ; Share . send ( { type : "join" , id : id } ) ; } param canWrite description static setCanWriteForAllExceptMeAndRoot ( canWrite ) { console . log ( `setCanWriteForAllExceptMeAndRoot(${ canWrite })` ) ; Share . execute ( "setCanWriteValueByDefault" , [ canWrite ] ) ; for ( const userid in UserManager . users ) { const b = UserManager . users [ userid ] == UserManager . me || UserManager . users [ userid ] . isRoot ; Share . execute ( "setUserCanWrite" , [ userid , b || canWrite ] ) ; } } }