diff --git a/hugvey/story.py b/hugvey/story.py index c0478d6..cb0787c 100644 --- a/hugvey/story.py +++ b/hugvey/story.py @@ -162,10 +162,18 @@ class Condition(object): return self.method(story) def _hasMetTimeout(self, story): - now = time.time() + now = story.timer.getElapsed() # check if the message already finished playing if not story.lastMsgFinishTime: return False + + if 'onlyIfNoReply' in self.vars and self.vars['onlyIfNoReply']: + if story.currentReply and story.currentReply.hasUtterances(): + logger.log(LOG_BS, f'Only if no reply has text! {story.currentReply.getText()}') + # 'onlyIfNoReply': only use this timeout if participants doesn't speak. + return False +# else: +# logger.debug('Only if no reply has no text yet!') return now - story.lastMsgFinishTime >= float(self.vars['seconds']) @@ -196,7 +204,10 @@ class Condition(object): results = result.groupdict() for captureGroup in results: story.variableValues[captureGroup] = results[captureGroup] - + + if 'instantMatch' in self.vars and self.vars['instantMatch']: + logger.info(f"Instant match on {self.vars['regex']}, {self.vars}") + return True # TODO: implement 'instant match' -> don't wait for isFinished() if r.isSpeaking(): @@ -206,11 +217,14 @@ class Condition(object): # print(self.vars) # either there's a match, or nothing to match at all if 'delays' in self.vars: - replyDuration = story.timer.getElapsed() - r.getFirstUtterance().endTime - timeSinceReply = story.timer.getElapsed() - r.getLastUtterance().endTime + if story.lastMsgFinishTime is None: + return False + # time between finishing playback and ending of speaking: + replyDuration = r.getLastUtterance().endTime - story.lastMsgFinishTime delays = sorted(self.vars['delays'], key=lambda k: float(k['minReplyDuration']), reverse=True) for delay in delays: if replyDuration > float(delay['minReplyDuration']): + timeSinceReply = story.timer.getElapsed() - r.getLastUtterance().endTime logger.log(LOG_BS, f"check delay duration is now {replyDuration}, already waiting for {timeSinceReply}, have to wait {delay['waitTime']}") if timeSinceReply > float(delay['waitTime']): return True @@ -434,6 +448,9 @@ class Story(object): self.commands = [] # queue of commands to send self.log = [] # all nodes/elements that are triggered self.currentReply = None + + for msg in self.getMessages(): + pass def add(self, obj): if obj.id in self.elements: @@ -460,7 +477,9 @@ class Story(object): if id in self.elements: return self.elements[id] return None - + def getMessages(self): + return [el for el in self.elements if type(el) == Message] + def stop(self): logger.info("Stop Story") if self.isRunning: @@ -482,7 +501,8 @@ class Story(object): if e['event'] == "playbackFinish": if e['msgId'] == self.currentMessage.id: - self.lastMsgFinishTime = time.time() + #TODO: migrate value to Messagage instead of Story + self.lastMsgFinishTime = self.timer.getElapsed() if self.currentMessage.id not in self.directionsPerMsg: logger.info("THE END!") @@ -573,8 +593,14 @@ class Story(object): self.currentMessage = message self.lastMsgTime = time.time() self.lastMsgFinishTime = None # to be filled in by the event + +# if not reset: self.previousReply = self.currentReply # we can use this for interrptions self.currentReply = self.currentMessage.reply +# else: +# # if we press 'save & play', it should not remember it's last reply to that msg +# self.previousReply = self.currentReply # we can use this for interrptions +# self.currentReply = self.currentMessage.reply logger.info("Current message: ({0}) \"{1}\"".format( message.id, message.text)) diff --git a/www/js/hugvey_console.js b/www/js/hugvey_console.js index b5c5f81..61948e3 100644 --- a/www/js/hugvey_console.js +++ b/www/js/hugvey_console.js @@ -15,7 +15,6 @@ class Panopticon { }, methods: { time_passed: function( hugvey, property ) { - console.log( "property!", Date( hugvey[property] * 1000 ) ); return moment( Date( hugvey[property] * 1000 ) ).fromNow(); }, timer: function(hugvey, property) { @@ -202,14 +201,12 @@ class Graph { let graph = this; this.controlDown = false; document.addEventListener( 'keydown', function( e ) { - console.log( e ); if ( e.which == "16" ) { // shift graph.controlDown = true; document.body.classList.add( 'controlDown' ); } } ); document.addEventListener( 'keyup', function( e ) { - console.log( e ); if ( e.which == "16" ) { // shift graph.controlDown = false; document.body.classList.remove( 'controlDown' ); @@ -236,8 +233,6 @@ class Graph { clickMsg( msg ) { // event when a message is clicked. - console.log( msg ); - if ( this.controlDown ) { this.secondarySelectMsg( msg ); } else { @@ -282,7 +277,7 @@ class Graph { let startAttributes = { 'name': msg['@id'] + '-start', - 'readonly': 'readonly', +// 'readonly': 'readonly', 'type': 'checkbox', 'on': { 'change': this.getEditEventListener() @@ -344,10 +339,10 @@ class Graph { 'change': function(e) { audioSpan.innerHTML = "..."; panopticon.graph.saveJson(msg['@id'], e.target, function(e2){ - console.log(e); +// console.log(e, e2); audioSpan.innerHTML = e.target.files[0].name + "*"; // reload graph: - console.log('reload', panopticon.graph.language_code); +// console.log('reload', panopticon.graph.language_code); panopticon.loadNarrative(panopticon.graph.language_code); }); // console.log(this,e); @@ -461,7 +456,6 @@ class Graph { for ( let conditionId of direction['conditions'] ) { let condition = this.getNodeById( conditionId ); - console.log(conditionId, condition); directionEl.appendChild( this.getEditConditionFormEl( condition, direction ) ); } @@ -518,7 +512,8 @@ class Graph { getConditionTypes() { return { 'timeout': { - 'seconds': { 'type': 'number', 'value': 10, 'min': 0, 'step': 0.1, 'unit': "s" } + 'seconds': { 'type': 'number', 'value': 10, 'min': 0, 'step': 0.1, 'unit': "s" }, + '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." } }, 'replyContains': { 'delays.0.minReplyDuration': { 'type': 'number', 'value': 0, 'min': 0, 'step': 0.1, 'label': 'Delay 1 - reply duration', 'unit': "s", 'readonly': 'readonly' }, @@ -541,12 +536,16 @@ class Graph { attr['name'] = typeof conditionId == 'undefined' ? v : `${conditionId}-vars.${v}`; if(typeof values != 'undefined') { let value = this._getValueForPath(v, values); + if(attr['type'] == 'checkbox' ) { + if(value) + attr['checked'] = 'checked'; + } attr['value'] = typeof value == 'undefined' ? "": value; attr['on'] = { 'change': this.getEditEventListener() } ; } else { - console.log(attr); +// console.log(attr); } inputs.push( @@ -573,17 +572,21 @@ class Graph { _getValueForPath(path, vars) { path = path.split( '.' ); // use vars.test to set ['vars']['test'] = value let v = vars; + let result = null; 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]]; + if(i == path.length - 1) { + result = v; + } if(typeof v == 'undefined') { break; } } - return v; + return result; } /** @@ -595,11 +598,9 @@ class Graph { */ _formPathToVars(path, value, vars) { path = path.split( '.' ); // use vars.test to set ['vars']['test'] = value - console.log(path); let res = vars; for ( let i = 0; i < path.length; i++ ) { if ( i == ( path.length - 1 ) ) { - console.log( 'last', path[i] ); res[path[i]] = value; } else { if(!isNaN(parseInt(path[i+1])) && isFinite(path[i+1])) { @@ -636,8 +637,24 @@ class Graph { form.delete( 'type' ); let label = form.get( 'label' ); form.delete( 'label' ); + + // checkboxes to true/false + let defs = g.getConditionTypes()[type]; + console.log(defs); + for(let field in defs) { + console.log(field); + if(defs[field]['type'] == 'checkbox') { + console.info('configure checkbox', field); + form.set(field, form.has(field)); + } + } + let vars = {}; for ( var pair of form.entries() ) { + // FormData only has strings & blobs, we want booleans: + if(pair[1] === 'true') pair[1] = true; + if(pair[1] === 'false') pair[1] = false; + vars = g._formPathToVars(pair[0], pair[1], vars); } // TODO: checkboxes @@ -841,18 +858,20 @@ class Graph { getEditEventListener() { let graph = this; let el = function( e ) { + console.info("Changed", e); let parts = e.srcElement.name.split( '-' ); let field = parts.pop(); let id = parts.join('-'); - console.log( this, graph ); let node = graph.getNodeById( id ); let path = field.split( '.' ); // use vars.test to set ['vars']['test'] = value - console.log(id, node); var res = node; + let value = e.srcElement.value + if(e.srcElement.type == 'checkbox') { + value = e.srcElement.checked; + } for ( var i = 0; i < path.length; i++ ) { if ( i == ( path.length - 1 ) ) { - console.log( 'last', path[i] ); - res[path[i]] = e.srcElement.value; + res[path[i]] = value; } else { res = res[path[i]]; } @@ -885,6 +904,7 @@ class Graph { } d.push( n ); } + console.info("Jsonified graph:",d); return JSON.stringify( d ); } @@ -923,7 +943,7 @@ class Graph { let blob = new Blob( [this.getJsonString()], { type: "application/json" } ); formData.append( "json", blob ); - console.log( formData ); + console.info("Save json", formData ); var request = new XMLHttpRequest(); request.open( "POST", "http://localhost:8888/upload" ); @@ -1010,7 +1030,6 @@ class Graph { this.clickMsg( d ); }.bind( this ) ) ; - console.log( 'a' ); let circle = newNodeG.append( "circle" ) .attr( 'r', this.nodeSize ) // .text(d => d.id) @@ -1058,7 +1077,6 @@ class Graph { link.attr( 'class', l => { return `link ` + ( l['conditions'].length == 0 ? "link--noconditions" : "link--withconditions" ); } ); link.attr('id', (l) => l['@id']); - // console.log('c'); let formatText = ( t ) => { if ( t.length > this.maxChars ) { return t.substr( 0, this.maxChars - 3 ) + '...';