2019-01-23 14:26:44 +00:00
var panopticon ;
class Panopticon {
constructor ( ) {
console . log ( "Init panopticon" ) ;
2019-01-25 09:43:55 +00:00
this . languages = [ ]
2019-01-25 14:45:46 +00:00
// this.selectedHugvey = null;
2019-01-23 14:26:44 +00:00
this . hugveys = new Vue ( {
2019-01-25 14:45:46 +00:00
el : "#interface" ,
2019-01-23 14:26:44 +00:00
data : {
uptime : 0 ,
languages : [ ] ,
2019-01-25 14:45:46 +00:00
hugveys : [ ] ,
selectedId : null ,
2019-01-23 14:26:44 +00:00
} ,
methods : {
2019-01-24 13:27:04 +00:00
time _passed : function ( hugvey , property ) {
return moment ( Date ( hugvey [ property ] * 1000 ) ) . fromNow ( ) ;
2019-01-23 14:26:44 +00:00
} ,
2019-01-25 09:43:55 +00:00
timer : function ( hugvey , property ) {
return panopticon . stringToHHMMSS ( hugvey [ property ] ) ;
} ,
2019-01-24 13:27:04 +00:00
loadNarrative : function ( code , file ) {
2019-01-25 14:45:46 +00:00
panopticon . hugveys . selectedId = null ;
2019-01-24 13:27:04 +00:00
return panopticon . loadNarrative ( code , file ) ;
2019-01-25 09:43:55 +00:00
} ,
2019-01-25 10:59:03 +00:00
pause : function ( hv ) {
hv . status = "loading" ;
return panopticon . pause ( hv . id ) ;
2019-01-25 09:43:55 +00:00
} ,
2019-01-25 10:59:03 +00:00
resume : function ( hv ) {
hv . status = "loading" ;
return panopticon . resume ( hv . id ) ;
2019-01-25 09:43:55 +00:00
} ,
2019-01-25 10:59:03 +00:00
restart : function ( hv ) {
hv . status = "loading" ;
return panopticon . restart ( hv . id ) ;
} ,
change _lang : function ( hv , lang _code ) {
hv . status = "loading" ;
return panopticon . change _language ( hv . id , lang _code ) ;
2019-01-25 13:10:19 +00:00
} ,
showHugvey : function ( hv ) {
2019-01-25 14:45:46 +00:00
panopticon . hugveys . selectedId = hv . language ? hv . id : null ;
2019-01-25 13:10:19 +00:00
panopticon . updateSelectedHugvey ( ) ;
2019-01-23 14:26:44 +00:00
}
}
} ) ;
2019-01-24 13:27:04 +00:00
2019-01-25 09:43:55 +00:00
this . socket = new ReconnectingWebSocket ( "ws://localhost:8888/ws" , null , { debug : false , reconnectInterval : 3000 } ) ;
2019-01-23 14:26:44 +00:00
this . graph = new Graph ( ) ;
2019-03-27 12:36:09 +00:00
this . eventDataSet = new vis . DataSet ( [
{ content : '.' , start : new Date ( ) , type : 'point' , group : 1 }
] ) ;
this . timeline = false ;
document . getElementById ( 'toggleTimeline' ) . addEventListener ( 'click' , function ( ) {
document . body . classList . toggle ( 'showTimeline' ) ;
} ) ;
2019-01-23 14:26:44 +00:00
this . socket . addEventListener ( 'open' , ( e ) => {
this . send ( { action : 'init' } ) ;
} ) ;
2019-03-27 12:36:09 +00:00
// request close before unloading
window . addEventListener ( 'beforeunload' , function ( ) {
panopticon . socket . close ( ) ;
} ) ;
2019-01-23 14:26:44 +00:00
this . socket . addEventListener ( 'close' , function ( e ) {
console . log ( 'Closed connection' ) ;
} ) ;
this . socket . addEventListener ( 'message' , ( e ) => {
let msg = JSON . parse ( e . data ) ;
if ( typeof msg [ 'alert' ] !== 'undefined' ) {
2019-01-24 13:27:04 +00:00
alert ( msg [ 'alert' ] ) ;
2019-01-23 14:26:44 +00:00
}
2019-01-24 13:27:04 +00:00
2019-01-23 14:26:44 +00:00
if ( typeof msg [ 'action' ] === 'undefined' ) {
console . error ( "not a valid message: " + e . data ) ;
return ;
}
2019-03-27 12:36:09 +00:00
console . debug ( msg ) ;
2019-01-23 14:26:44 +00:00
switch ( msg [ 'action' ] ) {
2019-01-24 13:27:04 +00:00
2019-01-23 14:26:44 +00:00
case 'status' :
2019-01-24 13:27:04 +00:00
this . hugveys . uptime = this . stringToHHMMSS ( msg [ 'uptime' ] ) ;
2019-01-23 14:26:44 +00:00
this . hugveys . languages = msg [ 'languages' ] ;
2019-01-25 09:43:55 +00:00
this . languages = msg [ 'languages' ] ;
2019-01-23 14:26:44 +00:00
this . hugveys . hugveys = msg [ 'hugveys' ] ;
2019-01-25 14:45:46 +00:00
if ( this . hugveys . selectedId ) {
2019-01-25 13:10:19 +00:00
this . updateSelectedHugvey ( ) ;
}
2019-03-27 12:36:09 +00:00
if ( ! this . timeline ) {
console . log ( 'init timeline' ) ;
let groups = [ ] ;
for ( let hid of msg [ 'hugvey_ids' ] ) {
groups . push ( { id : parseInt ( hid ) , content : 'Hugvey #' + hid } ) ;
this . eventDataSet . add ( { content : 'initiate' , start : new Date ( ) , type : 'point' , group : parseInt ( hid ) } )
}
let dataGroups = new vis . DataSet ( groups ) ;
let options = {
// 'rollingMode': {'follow': true, 'offset': .8 }
} ;
console . log ( 'groups' , dataGroups , groups , options ) ;
this . timeline = new vis . Timeline ( document . getElementById ( 'timeline' ) , this . eventDataSet , dataGroups , options ) ;
//
let startDate = new Date ( ) ;
startDate . setMinutes ( startDate . getMinutes ( ) - 1 ) ;
let endDate = new Date ( ) ;
endDate . setMinutes ( endDate . getMinutes ( ) + 20 ) ;
setTimeout ( function ( ) {
panopticon . timeline . setWindow ( startDate , endDate ) ;
} , 500 ) ;
console . log ( startDate , endDate ) ;
setInterval ( function ( ) { panopticon . timeline . moveTo ( new Date ( ) ) ; } , 1000 ) ;
}
2019-01-23 14:26:44 +00:00
break ;
2019-03-27 12:36:09 +00:00
case 'log' :
let hv _id = parseInt ( msg [ 'id' ] ) ;
if ( this . timeline ) {
// {'action': 'log', 'id':hugvey_id, 'type': items[0], 'info', 'args'}
let d , parts ;
switch ( msg [ 'type' ] ) {
case 'message' :
// info: en-njsm8bwbd "Are you up for a conversation?"
parts = msg [ 'info' ] . trim ( ) . split ( ' ' ) ;
let msgId = parts . shift ( ) ;
let msgUuid = parts . shift ( ) ;
let msgEvent = parts . shift ( ) ;
let msgContent = parts . join ( ' ' ) ;
let mId = 'm-' + msgUuid + '-' + hv _id ;
d = this . eventDataSet . get ( mId ) ;
console . log ( msgId , msgEvent , msgContent ) ;
if ( d !== null && msgEvent == 'done' ) {
d [ 'end' ] = new Date ( ) ;
this . eventDataSet . update ( d ) ;
console . log ( 'update' , d ) ;
} else {
this . eventDataSet . add ( { id : mId , content : msgContent , title : ` ${ msgContent } ( ${ msgId } ) ` , start : new Date ( ) , group : hv _id , 'className' : 'message' } ) ;
}
break ;
case 'speaking' :
// start/content/end
parts = msg [ 'info' ] . trim ( ) . split ( ' ' ) ;
let info = parts . shift ( ) ;
let id = parts . shift ( ) ;
let content = parts . join ( ' ' ) ;
let scId = 'sc-' + id + '-' + hv _id ;
if ( info . startsWith ( 'start' ) ) {
this . eventDataSet . add ( { content : info , start : new Date ( ) , type : 'point' , group : hv _id , 'className' : 'speech' } ) ;
}
if ( info . startsWith ( 'content' ) ) {
d = this . eventDataSet . get ( scId ) ;
if ( d !== null ) {
console . log ( 'alter' ) ;
d [ 'content' ] = content ;
d [ 'end' ] = new Date ( ) ;
d [ 'title' ] = content ;
this . eventDataSet . update ( d ) ;
} else {
console . log ( 'add' ) ;
this . eventDataSet . add ( { id : scId , content : content , title : content , start : new Date ( ) , group : hv _id , 'className' : 'speech' } ) ;
}
}
if ( info . startsWith ( 'end' ) ) {
d = this . eventDataSet . get ( scId ) ;
if ( d !== null ) {
d [ 'end' ] = new Date ( ) ;
this . eventDataSet . update ( d ) ;
}
}
break ;
case 'story' :
// 'info': 'start'/'finished'
this . eventDataSet . add ( { content : msg [ 'type' ] + ': ' + msg [ 'info' ] + ': ' + msg [ 'args' ] , start : new Date ( ) , type : 'point' , group : hv _id , 'className' : 'story' } ) ;
break ;
case 'condition' :
case 'direction' :
// don't draw these :-0
break ;
default :
this . eventDataSet . add ( { content : msg [ 'type' ] + ': ' + msg [ 'info' ] + ': ' + msg [ 'args' ] , start : new Date ( ) , type : 'point' , group : hv _id } ) ;
break ;
}
}
break ;
2019-01-23 14:26:44 +00:00
}
} ) ;
}
2019-01-25 13:10:19 +00:00
updateSelectedHugvey ( ) {
2019-01-25 14:45:46 +00:00
let hv = null ;
if ( this . hugveys . selectedId ) {
hv = this . getHugvey ( this . hugveys . selectedId ) ;
if ( hv . language && this . graph . language _code != hv . language ) {
this . loadNarrative ( hv . language ) ;
}
2019-01-25 13:10:19 +00:00
}
this . graph . updateHugveyStatus ( hv ) ;
}
getHugvey ( id ) {
for ( let hv of this . hugveys . hugveys ) {
if ( hv . id == id ) {
return hv ;
}
}
return null ;
}
2019-01-23 14:26:44 +00:00
send ( msg ) {
2019-01-24 13:27:04 +00:00
if ( this . socket . readyState == WebSocket . OPEN ) {
this . socket . send ( JSON . stringify ( msg ) ) ;
2019-01-23 14:26:44 +00:00
} else {
2019-01-24 13:27:04 +00:00
console . error ( "Socket not open: " , this . socket . readyState ) ;
2019-01-23 14:26:44 +00:00
}
}
getStatus ( ) {
2019-01-24 13:27:04 +00:00
// console.log('get status', this, panopticon);
2019-01-23 14:26:44 +00:00
panopticon . send ( { action : 'get_status' } ) ;
}
init ( ) {
setInterval ( this . getStatus , 3000 ) ;
}
2019-01-24 13:27:04 +00:00
stringToHHMMSS ( string ) {
var sec _num = parseInt ( string , 10 ) ; // don't forget the second param
var hours = Math . floor ( sec _num / 3600 ) ;
var minutes = Math . floor ( ( sec _num - ( hours * 3600 ) ) / 60 ) ;
var seconds = sec _num - ( hours * 3600 ) - ( minutes * 60 ) ;
if ( hours < 10 ) { hours = "0" + hours ; }
if ( minutes < 10 ) { minutes = "0" + minutes ; }
if ( seconds < 10 ) { seconds = "0" + seconds ; }
return hours + ':' + minutes + ':' + seconds ;
}
loadNarrative ( code , file ) {
2019-01-25 09:43:55 +00:00
if ( typeof file == 'undefined' ) {
for ( let lang of this . languages ) {
if ( lang [ 'code' ] == code ) {
file = lang [ 'file' ] ;
}
}
}
2019-01-23 14:26:44 +00:00
let req = new XMLHttpRequest ( ) ;
let graph = this . graph ;
2019-01-24 13:27:04 +00:00
req . addEventListener ( "load" , function ( e ) {
graph . loadData ( JSON . parse ( this . response ) , code ) ;
// console.log(, e);
} ) ;
req . open ( "GET" , "/local/" + file ) ;
2019-01-23 14:26:44 +00:00
req . send ( ) ;
}
2019-01-24 13:27:04 +00:00
resume ( hv _id ) {
this . send ( { action : 'resume' , hugvey : hv _id } )
2019-01-23 14:26:44 +00:00
}
2019-01-24 13:27:04 +00:00
pause ( hv _id ) {
2019-01-25 13:10:19 +00:00
this . send ( { action : 'pause' , hugvey : hv _id } )
2019-01-23 14:26:44 +00:00
}
2019-01-24 13:27:04 +00:00
restart ( hv _id ) {
2019-01-25 13:10:19 +00:00
this . send ( { action : 'restart' , hugvey : hv _id } ) ;
2019-01-23 14:26:44 +00:00
}
2019-01-25 10:59:03 +00:00
change _language ( hv _id , lang _code ) {
this . send ( { action : 'change_language' , hugvey : hv _id , lang _code : lang _code } ) ;
}
2019-01-25 14:45:46 +00:00
playFromSelected ( msg _id ) {
if ( ! this . hugveys . selectedId ) {
alert ( 'No hugvey selected' ) ;
} else {
this . send ( { action : 'play_msg' , hugvey : this . hugveys . selectedId , msg _id : msg _id } )
}
}
2019-01-23 14:26:44 +00:00
}
window . addEventListener ( 'load' , function ( ) {
panopticon = new Panopticon ( ) ;
panopticon . init ( ) ;
2019-01-24 13:27:04 +00:00
} ) ;
2019-01-23 14:26:44 +00:00
2019-01-24 13:27:04 +00:00
class Graph {
2019-01-23 14:26:44 +00:00
constructor ( ) {
this . width = 1280 ;
this . height = 1024 ;
this . nodeSize = 80 ;
this . maxChars = 16 ;
2019-01-24 13:27:04 +00:00
this . svg = d3 . select ( '#graph' ) ;
this . container = d3 . select ( '#container' ) ;
2019-01-23 14:26:44 +00:00
this . selectedMsg = null ;
2019-01-23 21:38:27 +00:00
this . language _code = null ;
2019-01-23 14:26:44 +00:00
this . messages = [ ] ; // initialise empty array. For the simulation, make sure we keep the same array object
this . directions = [ ] ; // initialise empty array. For the simulation, make sure we keep the same array object
this . conditions = [ ] ; // initialise empty array. For the simulation, make sure we keep the same array object
2019-01-25 09:43:55 +00:00
this . diversions = [ ] ; // initialise empty array. For the simulation, make sure we keep the same array object
2019-01-23 14:26:44 +00:00
let graph = this ;
this . controlDown = false ;
2019-01-24 13:27:04 +00:00
document . addEventListener ( 'keydown' , function ( e ) {
2019-02-02 17:20:55 +00:00
if ( e . which == "16" ) { // shift
2019-01-23 14:26:44 +00:00
graph . controlDown = true ;
2019-01-24 13:27:04 +00:00
document . body . classList . add ( 'controlDown' ) ;
2019-01-23 14:26:44 +00:00
}
2019-01-24 13:27:04 +00:00
} ) ;
document . addEventListener ( 'keyup' , function ( e ) {
2019-02-02 17:20:55 +00:00
if ( e . which == "16" ) { // shift
2019-01-23 14:26:44 +00:00
graph . controlDown = false ;
2019-01-24 13:27:04 +00:00
document . body . classList . remove ( 'controlDown' ) ;
2019-01-23 14:26:44 +00:00
}
2019-01-24 13:27:04 +00:00
} ) ;
2019-01-23 14:26:44 +00:00
let c = this . container ;
2019-01-24 13:27:04 +00:00
let zoomed = function ( ) {
c . attr ( "transform" , d3 . event . transform ) ;
2019-01-23 14:26:44 +00:00
}
2019-01-24 13:27:04 +00:00
this . svg . call ( d3 . zoom ( )
2019-02-25 16:05:14 +00:00
. scaleExtent ( [ 1 / 16 , 8 ] )
2019-01-24 13:27:04 +00:00
. on ( "zoom" , zoomed ) ) ;
2019-01-23 14:26:44 +00:00
2019-01-24 13:27:04 +00:00
this . nodesG = this . container . append ( "g" )
. attr ( "id" , "nodes" )
2019-01-23 14:26:44 +00:00
2019-01-24 13:27:04 +00:00
this . linkG = this . container . append ( "g" )
. attr ( "id" , "links" ) ;
2019-01-23 14:26:44 +00:00
2019-01-24 13:27:04 +00:00
document . getElementById ( 'btn-save' ) . addEventListener ( 'click' , function ( e ) { graph . saveJson ( ) ; } ) ;
document . getElementById ( 'btn-addMsg' ) . addEventListener ( 'click' , function ( e ) { graph . createMsg ( ) ; } ) ;
2019-03-07 19:19:43 +00:00
document . getElementById ( 'btn-diversions' ) . addEventListener ( 'click' , function ( e ) { graph . showDiversions ( ) ; } ) ;
2019-01-23 14:26:44 +00:00
}
2019-01-24 13:27:04 +00:00
clickMsg ( msg ) {
2019-01-23 14:26:44 +00:00
// event when a message is clicked.
2019-01-24 13:27:04 +00:00
if ( this . controlDown ) {
this . secondarySelectMsg ( msg ) ;
2019-01-23 14:26:44 +00:00
} else {
2019-01-24 13:27:04 +00:00
this . selectMsg ( msg ) ;
2019-01-23 14:26:44 +00:00
}
}
2019-01-24 13:27:04 +00:00
secondarySelectMsg ( msg ) {
if ( this . selectedMsg !== null ) {
this . addDirection ( this . selectedMsg , msg ) ;
2019-01-23 14:26:44 +00:00
} else {
2019-01-24 13:27:04 +00:00
console . error ( 'No message selected as Source' ) ;
2019-01-23 14:26:44 +00:00
}
}
2019-01-24 13:27:04 +00:00
selectMsg ( msg ) {
let selectedEls = document . getElementsByClassName ( 'selectedMsg' ) ;
while ( selectedEls . length > 0 ) {
selectedEls [ 0 ] . classList . remove ( 'selectedMsg' ) ;
2019-01-23 14:26:44 +00:00
}
2019-01-24 13:27:04 +00:00
document . getElementById ( msg [ '@id' ] ) . classList . add ( 'selectedMsg' ) ;
2019-01-23 14:26:44 +00:00
this . selectedMsg = msg ;
2019-01-24 13:27:04 +00:00
this . showMsg ( msg ) ;
2019-01-23 14:26:44 +00:00
}
updateMsg ( ) {
// used eg. after a condition creation.
2019-01-24 13:27:04 +00:00
this . showMsg ( this . selectedMsg ) ;
2019-01-23 14:26:44 +00:00
}
2019-02-18 19:38:54 +00:00
getAudioUrlForMsg ( msg ) {
let isVariable = msg [ 'text' ] . includes ( '$' ) ? '1' : '0' ;
return ` http://localhost:8888/voice?text= ${ encodeURIComponent ( msg [ 'text' ] ) } &variable= ${ isVariable } &filename=0 ` ;
}
2019-03-07 19:19:43 +00:00
getNumericId ( prefix ) {
let id , i = 0 ;
let hasId = function ( a , id ) {
for ( let i of a ) {
if ( i [ '@id' ] == id ) {
return true ;
}
}
return false ;
}
do {
id = prefix + i ;
i ++ ;
} while ( hasId ( this . data , id ) )
return id ;
}
createDiversion ( type ) {
let div = {
"@id" : this . getNumericId ( this . language _code . substring ( 0 , 2 ) + ` -div- ${ type } # ` ) ,
'@type' : 'Diversion' ,
'type' : type ,
'params' : { }
}
if ( type == 'no_response' ) {
div [ 'params' ] [ 'consecutiveSilences' ] = 3 ;
div [ 'params' ] [ 'timesOccured' ] = 0 ;
div [ 'params' ] [ 'returnAfterStrand' ] = true ;
div [ 'params' ] [ 'msgId' ] = "" ;
}
else if ( type == 'repeat' ) {
div [ 'params' ] [ 'regex' ] = "can you repeat that\\?" ;
} else {
console . log ( "invalid type" , type ) ;
alert ( 'invalid type for diversion' ) ;
}
this . data . push ( div ) ;
this . updateFromData ( ) ;
this . build ( ) ;
this . showDiversions ( ) ;
return msg ;
}
deleteDiversion ( div ) {
this . _rmNode ( div ) ;
this . showDiversions ( ) ;
}
showDiversions ( ) {
let msgEl = document . getElementById ( 'msg' ) ;
msgEl . innerHTML = "" ;
let divsNoResponse = [ ] , divsRepeat = [ ] ;
for ( let div of this . diversions ) {
if ( div [ 'type' ] == 'no_response' ) {
let returnAttrs = {
'type' : 'checkbox' ,
'on' : {
'change' : ( e ) => div [ 'params' ] [ 'returnAfterStrand' ] = e . target . checked
}
}
if ( div [ 'params' ] [ 'returnAfterStrand' ] ) {
returnAttrs [ 'checked' ] = 'checked' ;
}
let msgOptions = [ crel ( 'option' , "" ) ] ;
let starts = this . messages . filter ( m => m . hasOwnProperty ( 'start' ) && m [ 'start' ] == true ) ;
for ( let startMsg of starts ) {
let optionParams = { } ;
if ( div [ 'params' ] [ 'msgId' ] == startMsg [ '@id' ] ) {
optionParams [ 'selected' ] = 'selected' ;
}
msgOptions . push ( crel ( 'option' , optionParams , startMsg [ '@id' ] ) ) ;
}
divsNoResponse . push ( crel (
'div' , {
'class' : 'diversion' ,
'on' : {
'mouseover' : function ( e ) {
if ( div [ 'params' ] [ 'msgId' ] )
document . getElementById ( div [ 'params' ] [ 'msgId' ] ) . classList . add ( 'selectedMsg' ) ;
} ,
'mouseout' : function ( e ) {
if ( div [ 'params' ] [ 'msgId' ] )
document . getElementById ( div [ 'params' ] [ 'msgId' ] ) . classList . remove ( 'selectedMsg' ) ;
}
}
} ,
crel ( 'h3' , div [ '@id' ] ) ,
crel (
'div' , {
'class' : 'btn btn--delete' ,
'on' : {
'click' : ( e ) => this . deleteDiversion ( div )
}
} , 'Delete diversion' ) ,
crel ( 'label' , 'Consecutive Silences' ,
crel ( 'input' , {
'type' : 'number' ,
'value' : div [ 'params' ] [ 'consecutiveSilences' ] ,
'on' : {
'change' : ( e ) => div [ 'params' ] [ 'consecutiveSilences' ] = parseInt ( e . target . value )
}
} )
) ,
crel ( 'label' , 'On n-th instance' ,
crel ( 'input' , {
'type' : 'number' ,
'value' : div [ 'params' ] [ 'timesOccured' ] ,
'on' : {
'change' : ( e ) => div [ 'params' ] [ 'timesOccured' ] = parseInt ( e . target . value )
}
} )
) ,
crel ( 'label' , 'Return to point of departure afterwards' ,
crel ( 'input' , returnAttrs )
) ,
crel ( 'label' , 'Go to (start message)' ,
crel ( 'select' , { 'on' : {
'change' : ( e ) => div [ 'params' ] [ 'msgId' ] = e . target . value
} } , ... msgOptions )
)
) ) ;
}
if ( div [ 'type' ] == 'repeat' ) {
divsRepeat . push ( crel (
'div' , { 'class' : 'diversion' } ,
crel ( 'h3' , div [ '@id' ] ) ,
crel (
'div' , {
'class' : 'btn btn--delete' ,
'on' : {
'click' : ( e ) => this . deleteDiversion ( div )
}
} , 'Delete diversion' ) ,
crel ( 'label' , 'Regex' ,
crel ( 'input' , {
'type' : 'text' ,
'value' : div [ 'params' ] [ 'regex' ] ,
'on' : {
'change' : ( e ) => div [ 'params' ] [ 'regex' ] = e . target . value
}
} )
)
) ) ;
}
}
console . log ( divsNoResponse , divsRepeat ) ;
let divEl = crel (
'div' ,
{
'id' : 'diversions'
} ,
crel ( 'h1' , 'Configure Diversions' ) ,
crel ( 'div' ,
crel ( 'h2' , 'In case of No Response' ) ,
... divsNoResponse ,
crel ( 'div' ,
{
'class' : 'btn' ,
'on' : {
'click' : ( e ) => this . createDiversion ( 'no_response' )
}
} ,
'New case for no_response'
)
) ,
crel ( 'div' ,
crel ( 'h2' , 'Request repeat' ) ,
... divsRepeat ,
crel ( 'div' ,
{
'class' : 'btn' ,
'on' : {
'click' : ( e ) => this . createDiversion ( 'repeat' )
}
} ,
'New case for repeat'
)
)
) ;
msgEl . appendChild ( divEl ) ;
}
2019-01-23 14:26:44 +00:00
2019-01-24 13:27:04 +00:00
showMsg ( msg ) {
let msgEl = document . getElementById ( 'msg' ) ;
2019-01-23 14:26:44 +00:00
msgEl . innerHTML = "" ;
2019-01-25 14:45:46 +00:00
2019-01-25 16:09:18 +00:00
if ( msg == null ) {
return ;
2019-01-25 14:45:46 +00:00
}
2019-03-07 19:19:43 +00:00
2019-01-24 13:27:04 +00:00
let startAttributes = {
2019-01-23 14:26:44 +00:00
'name' : msg [ '@id' ] + '-start' ,
2019-02-15 11:26:56 +00:00
// 'readonly': 'readonly',
2019-01-23 14:26:44 +00:00
'type' : 'checkbox' ,
'on' : {
'change' : this . getEditEventListener ( )
}
}
2019-01-24 13:27:04 +00:00
if ( msg [ 'start' ] == true ) {
2019-01-23 14:26:44 +00:00
startAttributes [ 'checked' ] = 'checked' ;
}
2019-03-07 19:19:43 +00:00
let beginningAttributes = {
'name' : msg [ '@id' ] + '-beginning' ,
// 'readonly': 'readonly',
'type' : 'checkbox' ,
'on' : {
'change' : this . getEditEventListener ( )
}
}
if ( msg [ 'beginning' ] == true ) {
beginningAttributes [ 'checked' ] = 'checked' ;
}
2019-02-18 19:38:54 +00:00
2019-02-28 17:58:03 +00:00
let params = { } ;
if ( msg . hasOwnProperty ( 'params' ) ) {
params = msg [ 'params' ] ;
} else {
msg [ 'params' ] = { } ;
}
2019-02-24 20:38:08 +00:00
let audioSrcEl = crel ( 'source' , { 'src' : msg [ 'audio' ] ? msg [ 'audio' ] [ 'file' ] : this . getAudioUrlForMsg ( msg ) } ) ;
// console.log(msg['audio']);
2019-01-24 14:01:01 +00:00
let audioSpan = crel (
'span' ,
{
'title' : msg [ 'audio' ] ? msg [ 'audio' ] [ 'file' ] : "" ,
'class' : "label-value" ,
} ,
2019-02-18 19:38:54 +00:00
crel (
'audio' , { 'controls' : 'controls' } ,
audioSrcEl
2019-02-24 20:38:08 +00:00
) ,
crel ( 'div' , msg [ 'audio' ] ? crel (
'div' ,
crel ( 'div' , {
'class' : 'btn btn--delete' ,
'on' : {
'click' : function ( e ) {
e . stopPropagation ( ) ;
e . preventDefault ( ) ;
panopticon . graph . getNodeById ( msg [ '@id' ] ) [ 'audio' ] = null ;
panopticon . graph . showMsg ( msg ) ;
}
}
} , 'del' ) ,
"uploaded"
) : 'Auto-generated' )
2019-01-24 14:01:01 +00:00
) ;
2019-01-24 13:27:04 +00:00
let msgInfoEl = crel ( 'div' , { 'class' : 'msg__info' } ,
2019-01-25 16:09:18 +00:00
crel ( 'div' , {
2019-02-24 20:38:08 +00:00
'class' : 'btn btn--delete btn--delete-msg' ,
2019-01-25 16:09:18 +00:00
'on' : {
'click' : function ( e ) {
if ( confirm ( ` Are you sure you want to remove message ${ msg [ '@id' ] } ` ) ) {
panopticon . graph . rmMsg ( msg ) ;
panopticon . graph . showMsg ( null ) ;
}
}
}
} , 'delete' ) ,
2019-02-25 11:18:45 +00:00
crel ( 'h1' , { 'class' : 'msg__id' } , msg [ '@id' ] + ` ( ${ panopticon . graph . distances [ msg [ '@id' ] ] } ) ` ) ,
2019-01-24 13:27:04 +00:00
crel ( 'label' ,
crel ( 'span' , 'Text' ) ,
crel ( 'input' , {
2019-01-23 14:26:44 +00:00
'name' : msg [ '@id' ] + '-text' ,
'value' : msg [ 'text' ] ,
'on' : {
2019-02-18 19:38:54 +00:00
'change' : this . getEditEventListener ( function ( ) {
audioSrcEl . src = panopticon . graph . getAudioUrlForMsg ( msg ) ;
audioSrcEl . parentElement . load ( ) ;
} )
2019-01-23 14:26:44 +00:00
}
} )
) ,
2019-01-24 13:27:04 +00:00
crel ( 'label' ,
crel ( 'span' , 'Start' ) ,
crel ( 'input' , startAttributes )
2019-01-24 14:01:01 +00:00
) ,
2019-03-07 19:19:43 +00:00
crel ( 'label' ,
crel ( 'span' , 'Beginning' ) ,
crel ( 'input' , beginningAttributes )
) ,
2019-01-24 14:01:01 +00:00
crel ( 'label' ,
crel ( 'span' , 'Audio' ) ,
audioSpan ,
crel ( 'input' , {
'type' : 'file' ,
'name' : 'audio' ,
'accept' : '.wav,.ogg,.mp3' ,
'on' : {
'change' : function ( e ) {
audioSpan . innerHTML = "..." ;
panopticon . graph . saveJson ( msg [ '@id' ] , e . target , function ( e2 ) {
2019-02-15 11:26:56 +00:00
// console.log(e, e2);
2019-01-24 14:01:01 +00:00
audioSpan . innerHTML = e . target . files [ 0 ] . name + "<sup>*</sup>" ;
2019-01-25 09:43:55 +00:00
// reload graph:
2019-02-15 11:26:56 +00:00
// console.log('reload', panopticon.graph.language_code);
2019-01-25 09:43:55 +00:00
panopticon . loadNarrative ( panopticon . graph . language _code ) ;
2019-01-24 14:01:01 +00:00
} ) ;
// console.log(this,e);
}
}
} )
2019-02-11 20:28:48 +00:00
) ,
crel ( 'label' ,
2019-02-28 17:58:03 +00:00
crel ( 'span' , {
"title" : "The time after the reply in which one can still interrupt to continue speaking"
} , 'Afterrun time' ) ,
crel ( 'input' , {
'name' : msg [ '@id' ] + '-afterrunTime' ,
'step' : "0.1" ,
'value' : msg [ 'afterrunTime' ] ,
'type' : 'number' ,
'on' : {
'change' : this . getEditEventListener ( )
}
} )
) ,
crel ( 'label' ,
crel ( 'span' , {
"title" : "Playback volume factor"
} , 'Volume factor' ) ,
crel ( 'input' , {
'name' : msg [ '@id' ] + '-params.vol' ,
'value' : params . hasOwnProperty ( 'vol' ) ? params [ 'vol' ] : 1 ,
'step' : "0.1" ,
'type' : 'number' ,
'on' : {
'change' : this . getEditEventListener ( )
}
} )
) ,
crel ( 'label' ,
crel ( 'span' , {
"title" : "Playback tempo factor"
} , 'Tempo factor' ) ,
crel ( 'input' , {
'name' : msg [ '@id' ] + '-params.tempo' ,
'value' : params . hasOwnProperty ( 'tempo' ) ? params [ 'tempo' ] : 1 ,
'step' : "0.1" ,
'min' : "0.1" ,
'type' : 'number' ,
'on' : {
'change' : this . getEditEventListener ( )
}
} )
) ,
crel ( 'label' ,
crel ( 'span' , {
"title" : "Playback pitch factor"
} , 'Pitch factor' ) ,
crel ( 'input' , {
'name' : msg [ '@id' ] + '-params.pitch' ,
'value' : params . hasOwnProperty ( 'pitch' ) ? params [ 'pitch' ] : 0 ,
'step' : "0.1" ,
'type' : 'number' ,
'on' : {
'change' : this . getEditEventListener ( )
}
} )
) ,
// color for beter overview
crel ( 'label' ,
crel ( 'span' , {
"title" : "Color - for your eyes only"
} , 'Color' ) ,
crel ( 'input' , {
'name' : msg [ '@id' ] + '-color' ,
'value' : msg . hasOwnProperty ( 'color' ) ? msg [ 'color' ] : '#77618e' ,
'type' : 'color' ,
'on' : {
'change' : this . getEditEventListener ( )
}
2019-02-11 20:28:48 +00:00
} )
2019-01-23 14:26:44 +00:00
)
) ;
2019-01-24 13:27:04 +00:00
msgEl . appendChild ( msgInfoEl ) ;
2019-01-23 14:26:44 +00:00
2019-01-25 16:09:18 +00:00
if ( panopticon . hugveys . selectedId ) {
let playEl = crel (
'div' ,
{ 'class' : 'play' } ,
crel (
'div' , {
'class' : 'btn btn--play' ,
'on' : {
'click' : function ( e ) {
console . log ( 'go save' ) ;
panopticon . graph . saveJson ( null , null , function ( ) {
console . log ( 'saved, now play' ) ;
panopticon . playFromSelected ( msg [ '@id' ] ) ;
} ) ;
}
}
} ,
"Save & play on #" + panopticon . hugveys . selectedId
)
) ;
msgEl . appendChild ( playEl ) ;
}
2019-01-23 14:26:44 +00:00
// let directionHEl = document.createElement('h2');
// directionHEl.innerHTML = "Directions";
2019-01-24 13:27:04 +00:00
let fromDirections = [ ] , toDirections = [ ] ;
2019-01-23 14:26:44 +00:00
2019-01-24 13:27:04 +00:00
for ( let direction of this . getDirectionsTo ( msg ) ) {
toDirections . push ( this . getDirectionEl ( direction , msg ) ) ;
2019-01-23 14:26:44 +00:00
}
2019-01-24 13:27:04 +00:00
for ( let direction of this . getDirectionsFrom ( msg ) ) {
fromDirections . push ( this . getDirectionEl ( direction , msg ) ) ;
2019-01-23 14:26:44 +00:00
}
2019-01-24 13:27:04 +00:00
let directionsEl = crel ( 'div' , { 'class' : 'directions' } ,
crel ( 'h2' , 'Directions' ) ,
2019-01-30 17:00:40 +00:00
... toDirections ,
crel ( 'div' , {
'class' : 'btn btn--create' ,
'on' : {
'click' : function ( e ) {
panopticon . graph . createConnectedMsg ( msg ) ;
}
}
} , 'Create new message' ) ,
... fromDirections
2019-01-23 14:26:44 +00:00
) ;
2019-01-24 13:27:04 +00:00
msgEl . appendChild ( directionsEl ) ;
2019-01-23 14:26:44 +00:00
}
2019-01-24 13:27:04 +00:00
getDirectionEl ( direction , msg ) {
2019-01-23 14:26:44 +00:00
let g = this ;
2019-01-24 13:27:04 +00:00
let directionEl = crel ( 'div' ,
{
'class' : 'direction ' + ( direction [ 'source' ] == msg ? 'dir-to' : 'dir-from' ) ,
'on' : {
'mouseover' : function ( e ) {
directionEl . classList . add ( 'dir-highlight' ) ;
document . getElementById ( direction [ '@id' ] ) . classList . add ( 'dir-highlight' ) ;
} ,
'mouseout' : function ( e ) {
directionEl . classList . remove ( 'dir-highlight' ) ;
document . getElementById ( direction [ '@id' ] ) . classList . remove ( 'dir-highlight' ) ;
}
}
} ,
crel (
'h3' ,
{ 'title' : direction [ '@id' ] } ,
2019-02-18 21:33:31 +00:00
direction [ 'source' ] == msg ? ` To ${ direction [ 'target' ] [ 'text' ] } ` : ` From ${ direction [ 'source' ] [ 'text' ] } `
2019-01-24 13:27:04 +00:00
) ,
crel ( 'div' , {
'class' : 'btn btn--delete' ,
'on' : {
2019-01-25 16:09:18 +00:00
'click' : ( e ) => {
if ( confirm ( "Do you want to remove this direction and its conditions?" ) ) {
g . rmDirection ( direction ) ;
}
}
2019-01-24 13:27:04 +00:00
}
2019-01-25 16:09:18 +00:00
} , 'disconnect' )
2019-01-24 13:27:04 +00:00
) ;
for ( let conditionId of direction [ 'conditions' ] ) {
let condition = this . getNodeById ( conditionId ) ;
directionEl . appendChild ( this . getEditConditionFormEl ( condition , direction ) ) ;
2019-01-23 14:26:44 +00:00
}
2019-01-24 13:27:04 +00:00
directionEl . appendChild ( this . getAddConditionFormEl ( direction ) ) ;
2019-01-23 14:26:44 +00:00
return directionEl ;
}
2019-01-24 13:27:04 +00:00
getEditConditionFormEl ( condition , direction ) {
let conditionEl = crel ( 'div' , { 'class' : 'condition condition--edit' } ,
2019-01-25 16:09:18 +00:00
crel ( 'h4' , { 'title' : condition [ '@id' ] } , condition [ 'type' ] ) ,
crel ( 'div' , {
'class' : 'btn btn--delete' ,
'on' : {
'click' : ( e ) => {
if ( confirm ( "Do you want to remove this condition?" ) ) {
2019-02-25 14:56:59 +00:00
// console.log('remove condition for direction', condition, direction);
2019-01-25 16:09:18 +00:00
panopticon . graph . rmCondition ( condition , direction ) ;
}
}
}
2019-02-11 20:28:48 +00:00
} , 'delete' ) ,
... this . getConditionInputsForType ( condition [ 'type' ] , condition [ '@id' ] , condition [ 'vars' ] )
2019-01-23 14:26:44 +00:00
)
2019-01-24 13:27:04 +00:00
let labelLabel = document . createElement ( 'label' ) ;
2019-01-23 14:26:44 +00:00
labelLabel . innerHTML = "Description" ;
2019-01-24 13:27:04 +00:00
let labelInput = crel ( 'input' , {
'name' : ` ${ condition [ '@id' ] } -label ` ,
'value' : typeof condition [ 'label' ] == 'undefined' ? "" : condition [ 'label' ] ,
'on' : {
'change' : this . getEditEventListener ( )
}
} ) ;
labelLabel . appendChild ( labelInput ) ;
conditionEl . appendChild ( labelLabel ) ;
2019-02-11 20:28:48 +00:00
2019-01-23 14:26:44 +00:00
2019-02-11 20:28:48 +00:00
// for ( let v in condition['vars'] ) {
// let varLabel = document.createElement( 'label' );
// varLabel.innerHTML = v;
// let varInput = document.createElement( 'input' );
// if ( v == 'seconds' ) {
// varInput.type = 'number';
// }
// varInput.name = `${condition['@id']}-vars.${v}`;
// varInput.value = condition['vars'][v];
// varInput.addEventListener( 'change', this.getEditEventListener() );
// varLabel.appendChild( varInput );
// conditionEl.appendChild( varLabel );
// }
2019-01-23 14:26:44 +00:00
return conditionEl ;
}
getConditionTypes ( ) {
2019-02-11 20:28:48 +00:00
return {
2019-01-23 14:26:44 +00:00
'timeout' : {
2019-02-15 11:26:56 +00:00
'seconds' : { 'type' : 'number' , 'value' : 10 , 'min' : 0 , 'step' : 0.1 , 'unit' : "s" } ,
2019-03-27 14:43:02 +00:00
'onlyIfNoReply' : { 'type' : 'checkbox' , label : "Only if no reply" , "title" : "This timeout is only used if the participant doesn't say a word. If the participant starts speaking within the time of this timeout condition, only other conditions are applicable." } ,
'noReplyNeeded' : { 'type' : 'checkbox' , label : "No reply needed" , "title" : "If checked, the timeout is not counted for consecutive-timeouts (for diversions)." } ,
2019-01-23 14:26:44 +00:00
} ,
'replyContains' : {
2019-02-11 20:28:48 +00:00
'delays.0.minReplyDuration' : { 'type' : 'number' , 'value' : 0 , 'min' : 0 , 'step' : 0.1 , 'label' : 'Delay 1 - reply duration' , 'unit' : "s" , 'readonly' : 'readonly' } ,
'delays.0.waitTime' : { 'type' : 'number' , 'value' : 3 , 'min' : 0 , 'step' : 0.1 , 'label' : 'Delay 1 - wait time' , 'unit' : "s" } ,
'delays.1.minReplyDuration' : { 'type' : 'number' , 'value' : 5 , 'min' : 0 , 'step' : 0.1 , 'label' : 'Delay 2 - reply duration' , 'unit' : "s" } ,
'delays.1.waitTime' : { 'type' : 'number' , 'value' : 1 , 'min' : 0 , 'step' : 0.1 , 'label' : 'Delay 2 - time' , 'unit' : "s" } ,
'delays.2.minReplyDuration' : { 'type' : 'number' , 'value' : 10 , 'min' : 0 , 'step' : 0.1 , 'label' : 'Delay 3 - reply duration' , 'unit' : "s" } ,
'delays.2.waitTime' : { 'type' : 'number' , 'value' : 0 , 'min' : 0 , 'step' : 0.1 , 'label' : 'Delay 3 - time' , 'unit' : "s" } ,
'regex' : { 'value' : '' , 'placeholder' : "match any input" } ,
'instantMatch' : { 'value' : '' , 'title' : "When matched, don't wait for reply to finish. Instantly take this direction." , 'type' : 'checkbox' } ,
2019-03-29 13:11:48 +00:00
} ,
'variable' : {
'variable' : { 'value' : '' , 'placeholder' : "Variable name (without $)" } ,
'notSet' : { "label" : "Not set" , 'value' : '' , 'title' : "Match if the variable is _not_ set." , 'type' : 'checkbox' } ,
2019-01-23 14:26:44 +00:00
}
2019-02-11 20:28:48 +00:00
} ;
2019-01-23 14:26:44 +00:00
}
2019-02-11 20:28:48 +00:00
getConditionInputsForType ( type , conditionId , values ) {
let inputs = [ ] ;
2019-01-23 14:26:44 +00:00
let vars = this . getConditionTypes ( ) [ type ] ;
2019-01-24 13:27:04 +00:00
for ( let v in vars ) {
2019-01-23 14:26:44 +00:00
let attr = vars [ v ] ;
2019-02-11 20:28:48 +00:00
attr [ 'name' ] = typeof conditionId == 'undefined' ? v : ` ${ conditionId } -vars. ${ v } ` ;
if ( typeof values != 'undefined' ) {
let value = this . _getValueForPath ( v , values ) ;
2019-02-15 11:26:56 +00:00
if ( attr [ 'type' ] == 'checkbox' ) {
if ( value )
attr [ 'checked' ] = 'checked' ;
}
2019-02-11 20:28:48 +00:00
attr [ 'value' ] = typeof value == 'undefined' ? "" : value ;
attr [ 'on' ] = {
'change' : this . getEditEventListener ( )
} ;
} else {
2019-02-15 11:26:56 +00:00
// console.log(attr);
2019-02-11 20:28:48 +00:00
}
inputs . push (
2019-01-24 13:27:04 +00:00
crel ( 'label' ,
2019-02-11 20:28:48 +00:00
crel ( 'span' , {
'title' : attr . hasOwnProperty ( 'title' ) ? attr [ 'title' ] : ""
} , attr . hasOwnProperty ( 'label' ) ? attr [ 'label' ] : v ) ,
2019-01-24 13:27:04 +00:00
crel ( 'input' , attr )
2019-02-11 20:28:48 +00:00
// crel('span', {'class': 'label-unit'}, attr.hasOwnProperty('unit') ? attr['unit'] : "" )
2019-01-23 14:26:44 +00:00
)
) ;
}
2019-02-11 20:28:48 +00:00
return inputs ;
}
fillConditionFormForType ( conditionForm , type , values ) {
conditionForm . innerHTML = "" ;
let inputs = this . getConditionInputsForType ( type ) ;
for ( let i of inputs ) {
conditionForm . appendChild ( i ) ;
}
}
_getValueForPath ( path , vars ) {
path = path . split ( '.' ) ; // use vars.test to set ['vars']['test'] = value
let v = vars ;
2019-02-15 11:26:56 +00:00
let result = null ;
2019-02-11 20:28:48 +00:00
for ( let i = 0 ; i < path . length ; i ++ ) {
if ( ! isNaN ( parseInt ( path [ i ] ) ) && isFinite ( path [ i ] ) ) {
// is int, use array, instead of obj
path [ i ] = parseInt ( path [ i ] ) ;
}
v = v [ path [ i ] ] ;
2019-02-15 11:26:56 +00:00
if ( i == path . length - 1 ) {
result = v ;
}
2019-02-11 20:28:48 +00:00
if ( typeof v == 'undefined' ) {
break ;
}
}
2019-02-15 11:26:56 +00:00
return result ;
2019-02-11 20:28:48 +00:00
}
/ * *
* Save an array path ( string ) with a value to an object . Used to turn
* strings into nested arrays
* @ param string path
* @ param { any } value
* @ param array | object vars
* /
_formPathToVars ( path , value , vars ) {
path = path . split ( '.' ) ; // use vars.test to set ['vars']['test'] = value
let res = vars ;
for ( let i = 0 ; i < path . length ; i ++ ) {
if ( i == ( path . length - 1 ) ) {
res [ path [ i ] ] = value ;
} else {
if ( ! isNaN ( parseInt ( path [ i + 1 ] ) ) && isFinite ( path [ i + 1 ] ) ) {
// is int, use array, instead of obj
path [ i + 1 ] = parseInt ( path [ i + 1 ] ) ;
}
if ( typeof res [ path [ i ] ] == 'undefined' ) {
res [ path [ i ] ] = typeof path [ i + 1 ] == 'number' ? [ ] : { }
}
res = res [ path [ i ] ] ;
}
}
return vars ;
2019-01-23 14:26:44 +00:00
}
2019-01-24 13:27:04 +00:00
getAddConditionFormEl ( direction ) {
2019-01-23 14:26:44 +00:00
let optionEls = [ ] ;
let types = this . getConditionTypes ( ) ;
2019-01-24 13:27:04 +00:00
for ( let type in types ) {
optionEls . push ( crel ( 'option' , type ) ) ;
2019-01-23 14:26:44 +00:00
}
2019-01-24 13:27:04 +00:00
let conditionForm = crel ( 'div' , { 'class' : 'condition--vars' } ) ;
2019-01-23 14:26:44 +00:00
let g = this ;
2019-01-24 13:27:04 +00:00
let addConditionEl = crel ( 'div' , { 'class' : 'condition condition--add' } ,
crel ( 'form' , {
'on' : {
'submit' : function ( e ) {
e . preventDefault ( ) ;
let form = new FormData ( e . target ) ;
console . log ( 'submit' , form ) ;
let type = form . get ( 'type' ) ;
form . delete ( 'type' ) ;
let label = form . get ( 'label' ) ;
form . delete ( 'label' ) ;
2019-02-15 11:26:56 +00:00
// checkboxes to true/false
let defs = g . getConditionTypes ( ) [ type ] ;
2019-02-25 14:56:59 +00:00
// console.log(defs);
2019-02-15 11:26:56 +00:00
for ( let field in defs ) {
2019-02-25 14:56:59 +00:00
// console.log(field);
2019-02-15 11:26:56 +00:00
if ( defs [ field ] [ 'type' ] == 'checkbox' ) {
console . info ( 'configure checkbox' , field ) ;
form . set ( field , form . has ( field ) ) ;
}
}
2019-01-24 13:27:04 +00:00
let vars = { } ;
for ( var pair of form . entries ( ) ) {
2019-02-15 11:26:56 +00:00
// FormData only has strings & blobs, we want booleans:
if ( pair [ 1 ] === 'true' ) pair [ 1 ] = true ;
if ( pair [ 1 ] === 'false' ) pair [ 1 ] = false ;
2019-02-11 20:28:48 +00:00
vars = g . _formPathToVars ( pair [ 0 ] , pair [ 1 ] , vars ) ;
2019-01-23 14:26:44 +00:00
}
2019-02-11 20:28:48 +00:00
// TODO: checkboxes
2019-02-25 14:56:59 +00:00
// console.log("Createded", vars);
2019-01-24 13:27:04 +00:00
g . addConditionForDirection ( type , label , vars , direction ) ;
2019-01-23 14:26:44 +00:00
}
2019-01-24 13:27:04 +00:00
}
} ,
crel ( "h4" , {
'class' : "divToggle" ,
'on' : {
'click' : function ( e ) { this . classList . toggle ( 'opened' ) ; }
}
} , "Create New Condition" ) ,
crel ( 'div' , { 'class' : 'divToggle-target' } ,
crel ( "label" ,
crel ( 'span' , "Type" ) ,
crel ( 'select' , {
'name' : 'type' ,
'on' : {
'change' : function ( e ) {
g . fillConditionFormForType ( conditionForm , e . target . value ) ;
}
2019-01-23 14:26:44 +00:00
}
2019-01-24 13:27:04 +00:00
} , optionEls ) ,
) ,
crel ( "label" ,
crel ( 'span' , "Description" ) ,
crel ( 'input' , { 'name' : 'label' } )
) ,
conditionForm ,
crel ( 'input' , {
'type' : 'submit' ,
'value' : 'create'
} )
)
2019-01-23 14:26:44 +00:00
)
) ;
2019-01-24 13:27:04 +00:00
this . fillConditionFormForType ( conditionForm , optionEls [ 0 ] . value ) ;
2019-01-23 14:26:44 +00:00
return addConditionEl ;
}
2019-01-25 16:09:18 +00:00
/ * *
* remove condition from the graph or merely from the given direction
* @ param { any } condition The condition to remove
* @ param { any } direction if given , only remove from this direction
* /
rmCondition ( condition , direction ) {
2019-01-23 14:26:44 +00:00
let id = condition [ '@id' ] ;
// TODO
2019-01-24 13:27:04 +00:00
if ( typeof direction != 'undefined' ) {
2019-01-25 16:09:18 +00:00
let pos = direction [ 'conditions' ] . indexOf ( id ) ;
2019-02-11 20:28:48 +00:00
console . log ( 'delete' , id , 'on direction' ) ;
2019-01-25 16:09:18 +00:00
if ( pos > - 1 ) {
direction [ 'conditions' ] . splice ( pos , 1 ) ;
}
for ( let dir of this . directions ) {
2019-02-11 20:28:48 +00:00
// console.log('check if condition exists for dir', dir)
if ( dir [ 'conditions' ] . indexOf ( id ) > - 1 ) {
2019-01-25 16:09:18 +00:00
console . log ( "Condition still in use" ) ;
this . updateFromData ( ) ;
this . build ( ) ;
this . updateMsg ( ) ;
return ;
}
}
2019-02-11 20:28:48 +00:00
console . log ( 'No use, remove' , condition )
this . _rmNode ( condition ) ;
2019-01-25 16:09:18 +00:00
} else {
for ( let dir of this . directions ) {
let pos = dir [ 'conditions' ] . indexOf ( id ) ;
if ( pos > - 1 ) {
dir [ 'conditions' ] . splice ( pos , 1 ) ;
}
}
2019-02-11 20:28:48 +00:00
console . log ( 'remove condition?' , id )
this . _rmNode ( condition ) ;
2019-01-23 14:26:44 +00:00
}
2019-01-25 16:09:18 +00:00
this . updateMsg ( ) ;
2019-01-23 14:26:44 +00:00
}
2019-01-24 13:27:04 +00:00
getConditionEl ( condition ) {
let conditionEl = document . createElement ( 'div' ) ;
2019-01-23 14:26:44 +00:00
return conditionEl ;
}
2019-01-24 13:27:04 +00:00
getDirectionsFrom ( msg ) {
return this . directions . filter ( d => d [ 'source' ] == msg ) ;
2019-01-23 14:26:44 +00:00
}
2019-01-24 13:27:04 +00:00
getDirectionsTo ( msg ) {
return this . directions . filter ( d => d [ 'target' ] == msg ) ;
2019-01-23 14:26:44 +00:00
}
addMsg ( ) {
let msg = {
2019-01-24 13:27:04 +00:00
"@id" : this . language _code . substring ( 0 , 2 ) + "-n" + Date . now ( ) . toString ( 36 ) ,
2019-01-23 14:26:44 +00:00
"@type" : "Msg" ,
"text" : "New" ,
2019-02-11 20:28:48 +00:00
"start" : false ,
"afterrunTime" : 0.5 ,
2019-01-23 14:26:44 +00:00
}
2019-01-24 13:27:04 +00:00
this . data . push ( msg ) ;
2019-01-23 14:26:44 +00:00
this . updateFromData ( ) ;
this . build ( ) ;
2019-01-25 16:09:18 +00:00
this . selectMsg ( msg ) ;
2019-01-23 14:26:44 +00:00
return msg ;
}
2019-01-24 13:27:04 +00:00
rmMsg ( msg ) {
let invalidatedDirections = this . directions . filter ( d => d [ 'source' ] == msg || d [ 'target' ] == msg ) ;
console . log ( 'invalidated' , invalidatedDirections ) ;
for ( let dir of invalidatedDirections ) {
let i = this . data . indexOf ( dir ) ;
this . data . splice ( i , 1 ) ;
2019-01-23 14:26:44 +00:00
}
2019-01-24 13:27:04 +00:00
this . _rmNode ( msg ) ;
2019-01-23 14:26:44 +00:00
}
2019-01-24 13:27:04 +00:00
_rmNode ( node ) {
2019-01-23 14:26:44 +00:00
// remove msg/direction/condition/etc
2019-01-24 13:27:04 +00:00
let i = this . data . indexOf ( node ) ;
this . data . splice ( i , 1 ) ;
2019-01-23 14:26:44 +00:00
this . updateFromData ( ) ;
this . build ( ) ;
return this . data ;
}
2019-01-24 13:27:04 +00:00
addConditionForDirection ( type , label , vars , direction ) {
let con = this . addCondition ( type , label , vars , true ) ;
direction [ 'conditions' ] . push ( con [ '@id' ] ) ;
2019-01-23 14:26:44 +00:00
this . updateFromData ( ) ;
this . build ( ) ;
this . updateMsg ( ) ;
}
2019-01-24 13:27:04 +00:00
addCondition ( type , label , vars , skip ) {
2019-01-23 14:26:44 +00:00
let con = {
2019-01-24 13:27:04 +00:00
"@id" : this . language _code . substring ( 0 , 2 ) + "-c" + Date . now ( ) . toString ( 36 ) ,
2019-01-23 14:26:44 +00:00
"@type" : "Condition" ,
"type" : type ,
"label" : label ,
"vars" : vars
}
2019-01-24 13:27:04 +00:00
this . data . push ( con ) ;
if ( skip !== true ) {
2019-01-23 14:26:44 +00:00
this . updateFromData ( ) ;
this . build ( ) ;
}
return con ;
}
2019-01-24 13:27:04 +00:00
addDirection ( source , target ) {
2019-01-23 14:26:44 +00:00
let dir = {
2019-01-24 13:27:04 +00:00
"@id" : this . language _code . substring ( 0 , 2 ) + "-d" + Date . now ( ) . toString ( 36 ) ,
2019-01-23 14:26:44 +00:00
"@type" : "Direction" ,
"source" : source ,
"target" : target ,
"conditions" : [ ]
}
2019-01-24 13:27:04 +00:00
this . data . push ( dir ) ;
2019-01-23 14:26:44 +00:00
this . updateFromData ( ) ;
this . build ( ) ;
return dir ;
}
2019-01-24 13:27:04 +00:00
rmDirection ( dir ) {
this . _rmNode ( dir ) ;
2019-01-25 16:09:18 +00:00
// todo, remove orphaned conditions
2019-01-23 14:26:44 +00:00
}
createMsg ( ) {
this . addMsg ( ) ;
this . build ( ) ;
}
2019-01-30 17:00:40 +00:00
createConnectedMsg ( sourceMsg ) {
let newMsg = this . addMsg ( ) ;
2019-02-28 17:58:03 +00:00
this . getNodeById ( newMsg [ '@id' ] ) . y = this . getNodeById ( sourceMsg [ '@id' ] ) . y ;
if ( this . getNodeById ( sourceMsg [ '@id' ] ) . hasOwnProperty ( 'color' ) ) {
this . getNodeById ( newMsg [ '@id' ] ) . color = this . getNodeById ( sourceMsg [ '@id' ] ) . color
}
2019-01-30 17:00:40 +00:00
this . addDirection ( sourceMsg , newMsg ) ;
this . build ( ) ;
// reselect so that overview is updated
this . selectMsg ( newMsg ) ;
}
2019-01-23 14:26:44 +00:00
2019-01-24 13:27:04 +00:00
getNodeById ( id ) {
return this . data . filter ( node => node [ '@id' ] == id ) [ 0 ] ;
2019-01-23 14:26:44 +00:00
}
/ * *
* Use wrapper method , because for event handlers 'this' will refer to
* the input object
* /
2019-02-18 19:38:54 +00:00
getEditEventListener ( callback ) {
2019-01-23 14:26:44 +00:00
let graph = this ;
2019-01-24 13:27:04 +00:00
let el = function ( e ) {
2019-02-15 11:26:56 +00:00
console . info ( "Changed" , e ) ;
2019-01-24 13:27:04 +00:00
let parts = e . srcElement . name . split ( '-' ) ;
2019-01-25 16:09:18 +00:00
let field = parts . pop ( ) ;
let id = parts . join ( '-' ) ;
2019-01-24 13:27:04 +00:00
let node = graph . getNodeById ( id ) ;
let path = field . split ( '.' ) ; // use vars.test to set ['vars']['test'] = value
var res = node ;
2019-02-15 11:26:56 +00:00
let value = e . srcElement . value
if ( e . srcElement . type == 'checkbox' ) {
value = e . srcElement . checked ;
}
2019-01-24 13:27:04 +00:00
for ( var i = 0 ; i < path . length ; i ++ ) {
if ( i == ( path . length - 1 ) ) {
2019-02-15 11:26:56 +00:00
res [ path [ i ] ] = value ;
2019-01-23 14:26:44 +00:00
} else {
2019-01-24 13:27:04 +00:00
res = res [ path [ i ] ] ;
2019-01-23 14:26:44 +00:00
}
}
// node[field] = e.srcElement.value;
graph . build ( ) ;
2019-02-18 19:38:54 +00:00
if ( typeof callback !== 'undefined' ) {
callback ( ) ;
}
2019-01-23 14:26:44 +00:00
}
return el ;
}
getJsonString ( ) {
// recreate array to have the right order of items.
this . data = [ ... this . messages , ... this . conditions ,
2019-01-25 09:43:55 +00:00
... this . directions , ... this . diversions ]
2019-01-23 14:26:44 +00:00
let d = [ ] ;
2019-02-25 11:18:45 +00:00
// let toRemove = ['sourceX', 'sourceY', 'targetX', 'targetY', 'x', 'y', 'vx', 'vy']
let toRemove = [ 'sourceX' , 'sourceY' , 'targetX' , 'targetY' , 'vx' , 'vy' ]
2019-01-24 13:27:04 +00:00
for ( let node of this . data ) {
2019-01-23 14:26:44 +00:00
let n = { } ;
2019-02-25 14:56:59 +00:00
// console.log( node['source'] );
2019-01-24 13:27:04 +00:00
for ( let e in node ) {
if ( node . hasOwnProperty ( e ) && toRemove . indexOf ( e ) == - 1 ) {
if ( this . data . indexOf ( node [ e ] ) != - 1 ) {
2019-01-23 14:26:44 +00:00
n [ e ] = node [ e ] [ '@id' ] ;
} else {
n [ e ] = node [ e ] ;
}
}
}
2019-01-24 13:27:04 +00:00
d . push ( n ) ;
2019-01-23 14:26:44 +00:00
}
2019-02-15 11:26:56 +00:00
console . info ( "Jsonified graph:" , d ) ;
2019-01-24 13:27:04 +00:00
return JSON . stringify ( d ) ;
2019-01-23 14:26:44 +00:00
}
2019-01-23 21:38:27 +00:00
downloadJson ( ) {
2019-01-24 13:27:04 +00:00
if ( ! this . language _code ) {
alert ( "Make sure to load a language first" )
2019-01-23 21:38:27 +00:00
}
2019-01-24 13:27:04 +00:00
var blob = new Blob ( [ this . getJsonString ( ) ] , { type : 'application/json' } ) ;
if ( window . navigator . msSaveOrOpenBlob ) {
window . navigator . msSaveBlob ( blob , "pillow_talk.json" ) ;
2019-01-23 14:26:44 +00:00
}
2019-01-24 13:27:04 +00:00
else {
var elem = window . document . createElement ( 'a' ) ;
elem . href = window . URL . createObjectURL ( blob ) ;
2019-01-23 14:26:44 +00:00
elem . download = "pillow_talk.json" ;
2019-01-24 13:27:04 +00:00
document . body . appendChild ( elem ) ;
2019-01-23 14:26:44 +00:00
elem . click ( ) ;
2019-01-24 13:27:04 +00:00
document . body . removeChild ( elem ) ;
2019-01-23 14:26:44 +00:00
}
}
2019-01-24 13:27:04 +00:00
2019-01-24 14:01:01 +00:00
saveJson ( msg _id , fileInputElement , callback ) {
2019-01-24 13:27:04 +00:00
if ( ! this . language _code ) {
alert ( "Make sure to load a language first" )
2019-01-23 21:38:27 +00:00
}
2019-01-24 13:27:04 +00:00
2019-01-23 21:38:27 +00:00
let formData = new FormData ( ) ;
2019-01-24 13:27:04 +00:00
formData . append ( "language" , this . language _code ) ;
if ( msg _id ) {
formData . append ( "message_id" , msg _id ) ;
formData . append ( "audio" , fileInputElement . files [ 0 ] ) ;
2019-01-23 21:38:27 +00:00
}
2019-01-24 13:27:04 +00:00
let blob = new Blob ( [ this . getJsonString ( ) ] , { type : "application/json" } ) ;
formData . append ( "json" , blob ) ;
2019-02-15 11:26:56 +00:00
console . info ( "Save json" , formData ) ;
2019-01-23 21:38:27 +00:00
var request = new XMLHttpRequest ( ) ;
2019-01-24 13:27:04 +00:00
request . open ( "POST" , "http://localhost:8888/upload" ) ;
2019-01-24 14:01:01 +00:00
if ( callback ) {
request . addEventListener ( "load" , callback ) ;
}
2019-01-24 13:27:04 +00:00
request . send ( formData ) ;
2019-01-23 21:38:27 +00:00
}
2019-01-23 14:26:44 +00:00
2019-01-24 13:27:04 +00:00
loadData ( data , language _code ) {
2019-01-23 21:38:27 +00:00
this . language _code = language _code ;
2019-01-23 14:26:44 +00:00
this . data = data ;
this . updateFromData ( ) ;
2019-01-24 13:27:04 +00:00
this . build ( true ) ;
2019-01-23 14:26:44 +00:00
}
updateFromData ( ) {
2019-01-24 13:27:04 +00:00
this . messages = this . data . filter ( ( node ) => node [ '@type' ] == 'Msg' ) ;
this . directions = this . data . filter ( ( node ) => node [ '@type' ] == 'Direction' ) ;
this . conditions = this . data . filter ( ( node ) => node [ '@type' ] == 'Condition' ) ;
2019-01-25 09:43:55 +00:00
this . diversions = this . data . filter ( ( node ) => node [ '@type' ] == 'Diversion' ) ;
2019-01-24 13:27:04 +00:00
document . getElementById ( 'current_lang' ) . innerHTML = "" ;
document . getElementById ( 'current_lang' ) . appendChild ( crel ( 'span' , {
'class' : 'flag-icon ' + this . language _code
} ) ) ;
2019-02-18 21:33:31 +00:00
this . distances = this . calculateDistancesFromStart ( ) ;
2019-01-23 14:26:44 +00:00
// save state;
this . saveState ( ) ;
}
2019-01-25 13:10:19 +00:00
updateHugveyStatus ( hv ) {
let els = document . getElementsByClassName ( 'beenHit' ) ;
while ( els . length > 0 ) {
els [ 0 ] . classList . remove ( 'beenHit' ) ;
}
2019-01-25 14:45:46 +00:00
if ( ! hv || typeof hv [ 'history' ] == 'undefined' ) {
return ;
}
2019-01-25 13:10:19 +00:00
for ( let msg of hv [ 'history' ] [ 'messages' ] ) {
document . getElementById ( msg [ 0 ] [ 'id' ] ) . classList . add ( 'beenHit' ) ;
}
for ( let msg of hv [ 'history' ] [ 'directions' ] ) {
document . getElementById ( msg [ 0 ] [ 'id' ] ) . classList . add ( 'beenHit' ) ;
}
}
2019-01-23 14:26:44 +00:00
saveState ( ) {
2019-01-24 13:27:04 +00:00
window . localStorage . setItem ( "lastState" , this . getJsonString ( ) ) ;
2019-01-23 14:26:44 +00:00
}
hasSavedState ( ) {
2019-01-24 13:27:04 +00:00
return window . localStorage . getItem ( "lastState" ) !== null ;
2019-01-23 14:26:44 +00:00
}
loadFromState ( ) {
2019-01-24 13:27:04 +00:00
this . loadData ( JSON . parse ( window . localStorage . getItem ( "lastState" ) ) ) ;
2019-01-23 14:26:44 +00:00
}
2019-01-24 13:27:04 +00:00
build ( isInit ) {
this . simulation = d3 . forceSimulation ( this . messages )
2019-02-18 21:33:31 +00:00
. force ( "link" , d3 . forceLink ( this . directions ) . id ( d => d [ '@id' ] ) . strength ( 0 ) )
// .force( "charge", d3.forceManyBody().strength( 100 ) )
// .force( "center", d3.forceCenter( this.width / 2, this.height / 2 ) )
2019-02-25 17:07:25 +00:00
. force ( "collide" , d3 . forceCollide ( this . nodeSize * 1.5 ) . strength ( 3 ) )
2019-02-18 21:33:31 +00:00
. force ( "forceX" , d3 . forceX ( function ( m ) {
2019-02-25 16:05:14 +00:00
let fx = panopticon . graph . distances [ m [ '@id' ] ] !== null ? panopticon . graph . distances [ m [ '@id' ] ] [ 0 ] * panopticon . graph . nodeSize * 4 : 0
2019-02-25 14:56:59 +00:00
// console.log('fx', m['@id'], panopticon.graph.distances[m['@id']], fx);
2019-02-18 21:33:31 +00:00
return fx ;
} ) . strength ( 50 ) )
2019-02-25 16:05:14 +00:00
. force ( "forceY" , d3 . forceY ( function ( m ) {
// if(panopticon.graph.distances[m['@id']] !== null )
// console.log(panopticon.graph.distances[m['@id']][1]);
let fy = panopticon . graph . distances [ m [ '@id' ] ] !== null ? panopticon . graph . distances [ m [ '@id' ] ] [ 1 ] * panopticon . graph . nodeSize * 3 : 0
// console.log('fx', m['@id'], panopticon.graph.distances[m['@id']], fx);
return fy ;
} ) . strength ( 50 ) )
2019-02-25 11:18:45 +00:00
// .force( "forceY", d3.forceY(m => panopticon.graph.distances[m['@id']] !== null ? 0 : panopticon.graph.nodeSize * 3 ).strength(30))
2019-01-23 14:26:44 +00:00
;
2019-02-18 22:13:42 +00:00
this . simulation . velocityDecay ( . 99 ) ;
2019-01-23 14:26:44 +00:00
// Update existing nodes
let node = this . nodesG
2019-01-24 13:27:04 +00:00
. selectAll ( "g" )
. data ( this . messages , n => n [ '@id' ] )
;
2019-02-25 11:18:45 +00:00
2019-01-23 14:26:44 +00:00
// Update existing nodes
let newNode = node . enter ( ) ;
2019-01-24 13:27:04 +00:00
let newNodeG = newNode . append ( "g" )
. attr ( 'id' , d => d [ '@id' ] )
. on ( 'click' , function ( d ) {
this . clickMsg ( d ) ;
} . bind ( this ) )
;
let circle = newNodeG . append ( "circle" )
. attr ( 'r' , this . nodeSize )
// .text(d => d.id)
;
2019-01-30 17:00:40 +00:00
2019-01-24 13:27:04 +00:00
let textId = newNodeG . append ( "text" ) . attr ( 'class' , 'msg_id' ) ;
let textContent = newNodeG . append ( "text" ) . attr ( 'class' , 'msg_txt' ) ;
2019-01-24 14:27:22 +00:00
let statusIcon = newNodeG . append ( "image" )
. attr ( 'class' , 'status_icon' )
. attr ( 'x' , '-10' )
. attr ( 'y' , '10' )
. attr ( 'width' , '20' )
. attr ( 'height' , '20' )
;
2019-01-24 13:27:04 +00:00
// remove
node . exit ( ) . remove ( ) ;
node = node . merge ( newNodeG ) ;
2019-02-25 11:18:45 +00:00
2019-01-24 13:27:04 +00:00
// for all existing nodes:
node . attr ( 'class' , msg => {
let classes = [ ] ;
if ( this . selectedMsg == msg ) classes . push ( 'selectedMsg' ) ;
if ( msg [ 'start' ] == true ) classes . push ( 'startMsg' ) ;
if ( this . getDirectionsFrom ( msg ) . length < 1 ) {
classes . push ( 'endMsg' ) ;
if ( this . getDirectionsTo ( msg ) . length < 1 ) classes . push ( 'orphanedMsg' ) ;
}
2019-01-23 14:26:44 +00:00
2019-01-24 13:27:04 +00:00
return classes . join ( ' ' ) ;
} )
2019-02-25 11:18:45 +00:00
. on ( ".drag" , null )
. call (
d3 . drag ( this . simulation )
. on ( "start" , function ( d ) {
if ( ! d3 . event . active ) panopticon . graph . simulation . alphaTarget ( 0.3 ) . restart ( ) ;
d . fx = d . x ;
d . fy = d . y ;
} )
. on ( 'drag' , function ( d ) {
d . fx = d3 . event . x ;
d . fy = d3 . event . y ;
} )
. on ( "end" , function ( d ) {
if ( ! d3 . event . active ) panopticon . graph . simulation . alphaTarget ( 0 ) ;
d . fx = null ;
d . fy = null ;
} )
// .container(document.getElementById('container'))
) ;
2019-02-28 17:58:03 +00:00
node . select ( 'circle' ) . attr ( 'style' , ( d ) => 'fill: ' + ( d . hasOwnProperty ( 'color' ) ? d [ 'color' ] : '#77618e' ) ) ;
2019-01-24 13:27:04 +00:00
let link = this . linkG
. selectAll ( "line" )
. data ( this . directions )
;
let newLink = link . enter ( )
. append ( "line" )
;
//remove
link . exit ( ) . remove ( ) ;
link = link . merge ( newLink ) ;
link . attr ( 'class' , l => { return ` link ` + ( l [ 'conditions' ] . length == 0 ? "link--noconditions" : "link--withconditions" ) ; } ) ;
link . attr ( 'id' , ( l ) => l [ '@id' ] ) ;
let formatText = ( t ) => {
if ( t . length > this . maxChars ) {
return t . substr ( 0 , this . maxChars - 3 ) + '...' ;
} else {
return t ;
}
} ;
node . selectAll ( "text.msg_id" ) . text ( d => d [ '@id' ] ) ;
node . selectAll ( "text.msg_txt" ) . text ( d => formatText ( ` ${ d [ 'text' ] } ` ) ) ;
2019-01-24 14:27:22 +00:00
node . selectAll ( "image.status_icon" ) . attr ( 'xlink:href' , d => d [ 'audio' ] ? '' : '/images/music-broken.svg' ) ;
2019-01-24 13:27:04 +00:00
// console.log('q');
// // TODO: update text
// let text = newNodeG.append("text")
// // .attr('stroke', "black")
// .text(d => formatText(`(${d['@id']}) ${d['text']}`))
// // .attr('title', d => d.label)
// ;
let n = this . nodesG ;
this . simulation . on ( "tick" , ( ) => {
2019-01-23 14:26:44 +00:00
link
2019-01-24 13:27:04 +00:00
. each ( function ( d ) {
2019-01-23 14:26:44 +00:00
let sourceX , targetX , midX , dx , dy , angle ;
// This mess makes the arrows exactly perfect.
// thanks to http://bl.ocks.org/curran/9b73eb564c1c8a3d8f3ab207de364bf4
2019-01-24 13:27:04 +00:00
if ( d . source . x < d . target . x ) {
sourceX = d . source . x ;
targetX = d . target . x ;
} else if ( d . target . x < d . source . x ) {
targetX = d . target . x ;
sourceX = d . source . x ;
} else if ( d . target . isCircle ) {
targetX = sourceX = d . target . x ;
} else if ( d . source . isCircle ) {
targetX = sourceX = d . source . x ;
2019-01-23 14:26:44 +00:00
} else {
2019-01-24 13:27:04 +00:00
midX = ( d . source . x + d . target . x ) / 2 ;
if ( midX > d . target . x ) {
midX = d . target . x ;
} else if ( midX > d . source . x ) {
midX = d . source . x ;
} else if ( midX < d . target . x ) {
midX = d . target . x ;
} else if ( midX < d . source . x ) {
midX = d . source . x ;
}
targetX = sourceX = midX ;
2019-01-23 14:26:44 +00:00
}
dx = targetX - sourceX ;
dy = d . target . y - d . source . y ;
2019-01-24 13:27:04 +00:00
angle = Math . atan2 ( dx , dy ) ;
2019-01-23 14:26:44 +00:00
// Compute the line endpoint such that the arrow
// is touching the edge of the node rectangle perfectly.
2019-01-24 13:27:04 +00:00
d . sourceX = sourceX + Math . sin ( angle ) * this . nodeSize ;
d . targetX = targetX - Math . sin ( angle ) * this . nodeSize ;
d . sourceY = d . source . y + Math . cos ( angle ) * this . nodeSize ;
d . targetY = d . target . y - Math . cos ( angle ) * this . nodeSize ;
} . bind ( this ) )
. attr ( "x1" , function ( d ) { return d . sourceX ; } )
. attr ( "y1" , function ( d ) { return d . sourceY ; } )
. attr ( "x2" , function ( d ) { return d . targetX ; } )
. attr ( "y2" , function ( d ) { return d . targetY ; } ) ;
node . attr ( "transform" , d => ` translate( ${ d . x } , ${ d . y } ) ` ) ;
// .attr("cy", d => d.y);
} ) ;
// this.simulation.alpha(1);
// this.simulation.restart();
2019-02-25 11:18:45 +00:00
for ( let i = 0 , n = Math . ceil ( Math . log ( this . simulation . alphaMin ( ) ) / Math . log ( 1 - this . simulation . alphaDecay ( ) ) ) ; i < n ; ++ i ) {
this . simulation . tick ( ) ;
2019-01-23 14:26:44 +00:00
}
2019-01-24 13:27:04 +00:00
return this . svg . node ( ) ;
}
2019-02-18 21:33:31 +00:00
calculateDistancesFromStart ( ) {
2019-03-25 16:45:07 +00:00
console . time ( 'calculateDistancesFromStart' ) ;
2019-02-18 21:33:31 +00:00
let starts = this . messages . filter ( m => m . hasOwnProperty ( 'start' ) && m [ 'start' ] == true ) ;
if ( starts . length < 1 ) {
console . error ( "No start set" ) ;
return ;
}
2019-02-25 11:18:45 +00:00
2019-02-18 21:33:31 +00:00
//initiate distances
let distances = { } ;
for ( let msg of this . messages ) {
2019-02-25 11:18:45 +00:00
// distances[msg['@id']] = msg === startMsg ? 0 : null;
distances [ msg [ '@id' ] ] = null ;
2019-02-18 21:33:31 +00:00
}
2019-02-18 22:13:42 +00:00
let targetsPerMsg = { } ;
let sourcesPerMsg = { } ;
2019-02-25 14:56:59 +00:00
// console.log("dir", this.directions);
2019-02-18 21:33:31 +00:00
for ( let direction of this . directions ) {
let from = typeof direction [ 'source' ] == "string" ? direction [ 'source' ] : direction [ 'source' ] [ '@id' ] ;
let to = typeof direction [ 'target' ] == "string" ? direction [ 'target' ] : direction [ 'target' ] [ '@id' ] ;
2019-02-18 22:13:42 +00:00
if ( ! targetsPerMsg . hasOwnProperty ( from ) ) {
targetsPerMsg [ from ] = [ ] ;
2019-02-18 21:33:31 +00:00
}
2019-02-18 22:13:42 +00:00
targetsPerMsg [ from ] . push ( to ) ;
if ( ! sourcesPerMsg . hasOwnProperty ( to ) ) {
sourcesPerMsg [ to ] = [ ] ;
}
sourcesPerMsg [ to ] . push ( from ) ;
2019-02-18 21:33:31 +00:00
}
2019-02-25 16:05:14 +00:00
let traverseMsg = function ( msgId , depth , goingDown , yPos ) {
2019-02-18 22:13:42 +00:00
let msgsPerMsg = goingDown ? targetsPerMsg : sourcesPerMsg ;
2019-02-25 14:56:59 +00:00
// console.log(goingDown, msgId, depth);
2019-02-18 22:13:42 +00:00
if ( ! msgsPerMsg . hasOwnProperty ( msgId ) ) {
2019-02-18 21:33:31 +00:00
// end of trail
2019-02-25 16:05:14 +00:00
return yPos ;
2019-02-18 21:33:31 +00:00
}
2019-02-25 16:05:14 +00:00
2019-02-25 17:07:25 +00:00
let i = 0 , y = 0 ;
2019-02-18 22:13:42 +00:00
for ( let childMsgId of msgsPerMsg [ msgId ] ) {
2019-02-25 16:05:14 +00:00
if ( distances [ childMsgId ] === null || ( goingDown && distances [ childMsgId ] [ 0 ] > depth ) ) {
2019-02-25 17:07:25 +00:00
if ( distances [ childMsgId ] === null ) {
if ( i > 0 ) {
yPos ++ ;
}
i ++ ;
console . log ( 'set for id' , childMsgId , goingDown , depth , yPos ) ;
distances [ childMsgId ] = [ depth , yPos ] ;
}
else {
y ++ ;
}
2019-02-18 22:13:42 +00:00
// console.log(goingDown, childMsgId, depth);
2019-02-25 16:05:14 +00:00
yPos = traverseMsg ( childMsgId , goingDown ? ( depth + 1 ) : ( depth - 1 ) , goingDown , yPos ) ;
2019-02-18 22:13:42 +00:00
}
2019-02-25 16:05:14 +00:00
else if ( ! goingDown && distances [ childMsgId ] [ 0 ] < depth ) {
2019-02-25 17:07:25 +00:00
if ( childMsgId == 'en-njsgkr4az' ) {
console . log ( 'set for id' , childMsgId , goingDown ) ;
}
if ( distances [ childMsgId ] === null ) {
distances [ childMsgId ] = [ depth , yPos ] ;
}
2019-02-18 22:13:42 +00:00
// console.log('a', depth);
2019-02-25 16:05:14 +00:00
yPos = traverseMsg ( childMsgId , depth - 1 , goingDown , yPos ) ;
2019-02-18 21:33:31 +00:00
} else {
// apparently, there is a loop. Don't traverse it.
}
2019-02-25 16:05:14 +00:00
2019-02-18 21:33:31 +00:00
}
2019-02-25 17:07:25 +00:00
// if( i == 0 && y == 1) {
// // we reached an item that branches back into the tree
// return yPos -1;
// }
// console.log('yPos',msgId,yPos);
2019-02-25 16:05:14 +00:00
return yPos ;
2019-02-18 21:33:31 +00:00
}
2019-02-25 16:05:14 +00:00
let yPos = 0 ;
2019-03-25 16:45:07 +00:00
console . time ( 'step1' ) ;
2019-02-25 11:18:45 +00:00
for ( let startMsg of starts ) {
2019-03-25 16:45:07 +00:00
console . time ( 'start: ' + startMsg [ '@id' ] ) ;
2019-02-25 11:18:45 +00:00
if ( distances [ startMsg [ '@id' ] ] === null ) {
2019-02-25 16:05:14 +00:00
distances [ startMsg [ '@id' ] ] = [ 0 , yPos ] ;
2019-02-25 11:18:45 +00:00
}
2019-02-25 16:05:14 +00:00
yPos = traverseMsg ( startMsg [ '@id' ] , 1 , true , yPos ) ;
yPos += 1 ;
2019-03-25 16:45:07 +00:00
console . timeEnd ( 'start: ' + startMsg [ '@id' ] ) ;
2019-02-25 11:18:45 +00:00
}
2019-03-25 16:45:07 +00:00
console . timeEnd ( 'step1' ) ;
console . time ( 'step2' ) ;
2019-02-18 22:13:42 +00:00
// now we have the formal tree, lets try to polish the rest:
for ( let msgId in distances ) {
2019-03-25 16:45:07 +00:00
console . time ( 'polish: ' + msgId ) ;
2019-02-18 22:13:42 +00:00
if ( distances [ msgId ] === null ) {
continue ;
}
// let's see if there are parent nodes that are not in the distances array
// traverse up and see whether we encounter anything new
2019-02-25 16:05:14 +00:00
traverseMsg ( msgId , distances [ msgId ] [ 0 ] - 1 , false , distances [ msgId ] [ 1 ] )
2019-03-25 16:45:07 +00:00
console . timeEnd ( 'polish: ' + msgId ) ;
2019-02-18 22:13:42 +00:00
}
2019-03-25 16:45:07 +00:00
console . timeEnd ( 'step2' ) ;
2019-02-18 22:13:42 +00:00
// let additionalsDepth = 0;
//// now the secondary strands:
// for(let msgId in distances) {
// if(distances[msgId] !== null || sourcesPerMsg.hasOwnProperty(msgId)) {
// // it is already calculated, or it has a parent node (which we should traverse instead)
// continue;
// }
// distances[msgId] = additionalsDepth;
// traverseMsg(msgId, additionalsDepth+1, true);
//
// }
2019-03-25 16:45:07 +00:00
console . timeEnd ( "calculateDistancesFromStart" ) ;
2019-02-18 21:33:31 +00:00
return distances ;
}
2019-01-23 14:26:44 +00:00
}
2019-03-27 12:36:09 +00:00
//
//class Timeline {
// constructor(el, hugvey_ids) {
// this.el = el;
// this.logbook = []
// this.hugvey_ids = hugvey_ids;
//
// this.el.innerHTML = "";
// for(id of this.hugvey_ids) {
// this.el.appendChild(crel(
// 'div', {
//
// }
// ));
// }
// }
//
// log(msg) {
//// {"action": "log", "id": "3", "type": "story", "info": " start"}
// console.log('log!', msg);
// }
//}