Merge branch 'master' of gitlab.com:hugvey/hugvey
This commit is contained in:
commit
42b4185a69
8 changed files with 407 additions and 222 deletions
|
@ -30,7 +30,7 @@ def getWebSocketHandler(central_command):
|
||||||
class WebSocketHandler(tornado.websocket.WebSocketHandler):
|
class WebSocketHandler(tornado.websocket.WebSocketHandler):
|
||||||
CORS_ORIGINS = ['localhost']
|
CORS_ORIGINS = ['localhost']
|
||||||
connections = set()
|
connections = set()
|
||||||
|
|
||||||
def check_origin(self, origin):
|
def check_origin(self, origin):
|
||||||
parsed_origin = urlparse(origin)
|
parsed_origin = urlparse(origin)
|
||||||
# parsed_origin.netloc.lower() gives localhost:3333
|
# parsed_origin.netloc.lower() gives localhost:3333
|
||||||
|
@ -79,7 +79,7 @@ def getWebSocketHandler(central_command):
|
||||||
logger.exception(e)
|
logger.exception(e)
|
||||||
|
|
||||||
def send(self, message):
|
def send(self, message):
|
||||||
# Possible useless method: use self.write_message()
|
# Possible useless method: use self.write_message()
|
||||||
j = json.dumps(message)
|
j = json.dumps(message)
|
||||||
self.write_message(j)
|
self.write_message(j)
|
||||||
|
|
||||||
|
@ -100,40 +100,40 @@ def getWebSocketHandler(central_command):
|
||||||
def msgInit(self):
|
def msgInit(self):
|
||||||
msg = self.getStatusMsg()
|
msg = self.getStatusMsg()
|
||||||
self.send(msg)
|
self.send(msg)
|
||||||
|
|
||||||
def msgBlock(self, hv_id):
|
def msgBlock(self, hv_id):
|
||||||
central_command.hugveys[hv_id].eventQueue.put_nowait({'event': 'block'})
|
central_command.hugveys[hv_id].eventQueue.put_nowait({'event': 'block'})
|
||||||
|
|
||||||
def msgUnblock(self, hv_id):
|
def msgUnblock(self, hv_id):
|
||||||
central_command.hugveys[hv_id].eventQueue.put_nowait({'event': 'unblock'})
|
central_command.hugveys[hv_id].eventQueue.put_nowait({'event': 'unblock'})
|
||||||
|
|
||||||
def msgResume(self, hv_id):
|
def msgResume(self, hv_id):
|
||||||
central_command.hugveys[hv_id].eventQueue.put_nowait({'event': 'resume'})
|
central_command.hugveys[hv_id].eventQueue.put_nowait({'event': 'resume'})
|
||||||
|
|
||||||
def msgPause(self, hv_id):
|
def msgPause(self, hv_id):
|
||||||
central_command.hugveys[hv_id].eventQueue.put_nowait({'event': 'pause'})
|
central_command.hugveys[hv_id].eventQueue.put_nowait({'event': 'pause'})
|
||||||
|
|
||||||
def msgRestart(self, hv_id):
|
def msgRestart(self, hv_id):
|
||||||
central_command.hugveys[hv_id].eventQueue.put_nowait({'event': 'restart'})
|
central_command.hugveys[hv_id].eventQueue.put_nowait({'event': 'restart'})
|
||||||
|
|
||||||
def msgFinish(self, hv_id):
|
def msgFinish(self, hv_id):
|
||||||
central_command.hugveys[hv_id].eventQueue.put_nowait({'event': 'finish'})
|
central_command.hugveys[hv_id].eventQueue.put_nowait({'event': 'finish'})
|
||||||
|
|
||||||
def msgChangeLanguage(self, hv_id, lang_code):
|
def msgChangeLanguage(self, hv_id, lang_code):
|
||||||
central_command.hugveys[hv_id].eventQueue.put_nowait({'event': 'change_language', 'lang_code': lang_code})
|
central_command.hugveys[hv_id].eventQueue.put_nowait({'event': 'change_language', 'lang_code': lang_code})
|
||||||
|
|
||||||
def msgChangeLightId(self, hv_id, lightId):
|
def msgChangeLightId(self, hv_id, lightId):
|
||||||
central_command.hugveys[hv_id].eventQueue.put_nowait({'event': 'change_light', 'light_id': lightId})
|
central_command.hugveys[hv_id].eventQueue.put_nowait({'event': 'change_light', 'light_id': lightId})
|
||||||
|
|
||||||
def msgPlayMsg(self, hv_id, msg_id, reloadStory):
|
def msgPlayMsg(self, hv_id, msg_id, reloadStory):
|
||||||
central_command.hugveys[hv_id].eventQueue.put_nowait({'event': 'play_msg', 'msg_id': msg_id, 'reloadStory':bool(reloadStory)})
|
central_command.hugveys[hv_id].eventQueue.put_nowait({'event': 'play_msg', 'msg_id': msg_id, 'reloadStory':bool(reloadStory)})
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def write_to_clients(wsHandlerClass, msg):
|
def write_to_clients(wsHandlerClass, msg):
|
||||||
if msg is None:
|
if msg is None:
|
||||||
logger.critical("Tried to send 'none' to Panopticon")
|
logger.critical("Tried to send 'none' to Panopticon")
|
||||||
return
|
return
|
||||||
|
|
||||||
for client in wsHandlerClass.connections:
|
for client in wsHandlerClass.connections:
|
||||||
client.write_message(msg)
|
client.write_message(msg)
|
||||||
|
|
||||||
|
@ -143,7 +143,7 @@ class NonCachingStaticFileHandler(tornado.web.StaticFileHandler):
|
||||||
def set_extra_headers(self, path):
|
def set_extra_headers(self, path):
|
||||||
# Disable cache
|
# Disable cache
|
||||||
self.set_header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
|
self.set_header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
|
||||||
|
|
||||||
def getUploadHandler(central_command):
|
def getUploadHandler(central_command):
|
||||||
class UploadHandler(tornado.web.RequestHandler):
|
class UploadHandler(tornado.web.RequestHandler):
|
||||||
def set_default_headers(self):
|
def set_default_headers(self):
|
||||||
|
@ -160,12 +160,12 @@ def getUploadHandler(central_command):
|
||||||
logger.info('upload')
|
logger.info('upload')
|
||||||
langCode = self.get_argument("language")
|
langCode = self.get_argument("language")
|
||||||
langFile = os.path.join(central_command.config['web']['files_dir'] , central_command.languageFiles[langCode])
|
langFile = os.path.join(central_command.config['web']['files_dir'] , central_command.languageFiles[langCode])
|
||||||
|
|
||||||
storyData = json.loads(self.request.files['json'][0]['body'])
|
storyData = json.loads(self.request.files['json'][0]['body'])
|
||||||
# print(json.dumps(storyData))
|
# print(json.dumps(storyData))
|
||||||
# self.finish()
|
# self.finish()
|
||||||
# return
|
# return
|
||||||
|
|
||||||
if 'audio' in self.request.files:
|
if 'audio' in self.request.files:
|
||||||
msgId = self.get_argument("message_id")
|
msgId = self.get_argument("message_id")
|
||||||
audioFile = self.request.files['audio'][0]
|
audioFile = self.request.files['audio'][0]
|
||||||
|
@ -179,7 +179,7 @@ def getUploadHandler(central_command):
|
||||||
if 'audio' in storyData[i] and storyData[i]['audio'] is not None and os.path.exists(storyData[i]['audio']['file']):
|
if 'audio' in storyData[i] and storyData[i]['audio'] is not None and os.path.exists(storyData[i]['audio']['file']):
|
||||||
logger.info(f"Remove previous file {storyData[i]['audio']['file']} ({storyData[i]['audio']['original_name']})")
|
logger.info(f"Remove previous file {storyData[i]['audio']['file']} ({storyData[i]['audio']['original_name']})")
|
||||||
os.unlink(storyData[i]['audio']['file'])
|
os.unlink(storyData[i]['audio']['file'])
|
||||||
|
|
||||||
storyData[i]['audio'] = {
|
storyData[i]['audio'] = {
|
||||||
'file': audioFilename,
|
'file': audioFilename,
|
||||||
'original_name': original_fname
|
'original_name': original_fname
|
||||||
|
@ -188,17 +188,17 @@ def getUploadHandler(central_command):
|
||||||
logger.info(f'Save {original_fname} to {audioFilename}')
|
logger.info(f'Save {original_fname} to {audioFilename}')
|
||||||
fp.write(audioFile['body'])
|
fp.write(audioFile['body'])
|
||||||
break
|
break
|
||||||
|
|
||||||
# logger.info(os.path.abspath(langFile))
|
# logger.info(os.path.abspath(langFile))
|
||||||
langFile = os.path.abspath(langFile)
|
langFile = os.path.abspath(langFile)
|
||||||
with open(langFile, 'w') as json_fp:
|
with open(langFile, 'w') as json_fp:
|
||||||
logger.info(f'Save story to {langFile} {json_fp}')
|
logger.info(f'Save story to {langFile} {json_fp}')
|
||||||
json.dump(storyData, json_fp, indent=2)
|
json.dump(storyData, json_fp, indent=2)
|
||||||
|
|
||||||
# Reload language files for new instances
|
# Reload language files for new instances
|
||||||
central_command.loadLanguages()
|
central_command.loadLanguages()
|
||||||
self.finish()
|
self.finish()
|
||||||
|
|
||||||
return UploadHandler
|
return UploadHandler
|
||||||
|
|
||||||
def getVoiceHandler(voiceStorage):
|
def getVoiceHandler(voiceStorage):
|
||||||
|
@ -212,7 +212,7 @@ def getVoiceHandler(voiceStorage):
|
||||||
fn = await voiceStorage.requestFile(lang_code, text, isVariable)
|
fn = await voiceStorage.requestFile(lang_code, text, isVariable)
|
||||||
if not fn:
|
if not fn:
|
||||||
raise Exception(f"No Filename for text: {text}")
|
raise Exception(f"No Filename for text: {text}")
|
||||||
|
|
||||||
if int(self.get_argument('filename')) == 1:
|
if int(self.get_argument('filename')) == 1:
|
||||||
self.set_header("Content-Type","text/plain")
|
self.set_header("Content-Type","text/plain")
|
||||||
self.write(fn)
|
self.write(fn)
|
||||||
|
@ -221,25 +221,30 @@ def getVoiceHandler(voiceStorage):
|
||||||
with open(fn, 'rb') as fp:
|
with open(fn, 'rb') as fp:
|
||||||
self.write(fp.read())
|
self.write(fp.read())
|
||||||
self.finish()
|
self.finish()
|
||||||
return VoiceHandler
|
return VoiceHandler
|
||||||
|
|
||||||
|
class StaticFileWithHeaderHandler(tornado.web.StaticFileHandler):
|
||||||
|
def set_extra_headers(self, path):
|
||||||
|
"""For subclass to add extra headers to the response"""
|
||||||
|
if path[-5:] == '.html':
|
||||||
|
self.set_header("Access-Control-Allow-Origin", "*")
|
||||||
|
|
||||||
class Panopticon(object):
|
class Panopticon(object):
|
||||||
def __init__(self, central_command, config, voiceStorage):
|
def __init__(self, central_command, config, voiceStorage):
|
||||||
self.command = central_command
|
self.command = central_command
|
||||||
self.config = config
|
self.config = config
|
||||||
|
|
||||||
self.voiceStorage = voiceStorage
|
self.voiceStorage = voiceStorage
|
||||||
|
|
||||||
self.wsHandler = getWebSocketHandler(self.command)
|
self.wsHandler = getWebSocketHandler(self.command)
|
||||||
|
|
||||||
self.application = tornado.web.Application([
|
self.application = tornado.web.Application([
|
||||||
(r"/ws(.*)", self.wsHandler),
|
(r"/ws(.*)", self.wsHandler),
|
||||||
(r"/local/(.*)", NonCachingStaticFileHandler,
|
(r"/local/(.*)", NonCachingStaticFileHandler,
|
||||||
{"path": config['web']['files_dir']}),
|
{"path": config['web']['files_dir']}),
|
||||||
(r"/upload", getUploadHandler(self.command)),
|
(r"/upload", getUploadHandler(self.command)),
|
||||||
(r"/voice", getVoiceHandler(self.voiceStorage)),
|
(r"/voice", getVoiceHandler(self.voiceStorage)),
|
||||||
(r"/(.*)", tornado.web.StaticFileHandler,
|
(r"/(.*)", StaticFileWithHeaderHandler,
|
||||||
{"path": web_dir, "default_filename": 'index.html'}),
|
{"path": web_dir, "default_filename": 'index.html'}),
|
||||||
], debug=True)
|
], debug=True)
|
||||||
|
|
||||||
|
@ -251,17 +256,17 @@ class Panopticon(object):
|
||||||
asyncio.set_event_loop(evt_loop)
|
asyncio.set_event_loop(evt_loop)
|
||||||
|
|
||||||
self.loop = tornado.ioloop.IOLoop.current()
|
self.loop = tornado.ioloop.IOLoop.current()
|
||||||
|
|
||||||
thread = threading.Thread(
|
thread = threading.Thread(
|
||||||
target=self.broadcastLoggingQueueToWs, kwargs={'wsHandler': self.wsHandler, 'q': self.command.logQueue}, name=f"panopticon/logws")
|
target=self.broadcastLoggingQueueToWs, kwargs={'wsHandler': self.wsHandler, 'q': self.command.logQueue}, name=f"panopticon/logws")
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
logger.info(f"Start Panopticon on http://localhost:{self.config['web']['port']}")
|
logger.info(f"Start Panopticon on http://localhost:{self.config['web']['port']}")
|
||||||
self.loop.start()
|
self.loop.start()
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
self.loop.stop()
|
self.loop.stop()
|
||||||
|
|
||||||
def broadcastLoggingQueueToWs(self, wsHandler, q: Queue):
|
def broadcastLoggingQueueToWs(self, wsHandler, q: Queue):
|
||||||
while True:
|
while True:
|
||||||
record = q.get()
|
record = q.get()
|
||||||
|
@ -278,4 +283,3 @@ class Panopticon(object):
|
||||||
j = json.dumps(msg)
|
j = json.dumps(msg)
|
||||||
logger.debug(j)
|
logger.debug(j)
|
||||||
self.loop.add_callback(wsHandler.write_to_clients, j)
|
self.loop.add_callback(wsHandler.write_to_clients, j)
|
||||||
|
|
|
@ -40,8 +40,8 @@ class Utterance(object):
|
||||||
|
|
||||||
def isFinished(self):
|
def isFinished(self):
|
||||||
return self.endTime is not None
|
return self.endTime is not None
|
||||||
|
|
||||||
|
|
||||||
def __getstate__(self):
|
def __getstate__(self):
|
||||||
# print(f'get utterance {self}')
|
# print(f'get utterance {self}')
|
||||||
state = self.__dict__.copy()
|
state = self.__dict__.copy()
|
||||||
|
@ -68,7 +68,7 @@ class Message(object):
|
||||||
self.parseForVariables()
|
self.parseForVariables()
|
||||||
self.uuid = None # Have a unique id each time the message is played back.
|
self.uuid = None # Have a unique id each time the message is played back.
|
||||||
self.color = None
|
self.color = None
|
||||||
|
|
||||||
def __getstate__(self):
|
def __getstate__(self):
|
||||||
# Copy the object's state from self.__dict__ which contains
|
# Copy the object's state from self.__dict__ which contains
|
||||||
# all our instance attributes. Always use the dict.copy()
|
# all our instance attributes. Always use the dict.copy()
|
||||||
|
@ -78,7 +78,7 @@ class Message(object):
|
||||||
# Remove the unpicklable entries.
|
# Remove the unpicklable entries.
|
||||||
del state['filenameFetchLock']
|
del state['filenameFetchLock']
|
||||||
return state
|
return state
|
||||||
|
|
||||||
def __setstate__(self, state):
|
def __setstate__(self, state):
|
||||||
self.__dict__.update(state)
|
self.__dict__.update(state)
|
||||||
self.filenameFetchLock = asyncio.Lock()
|
self.filenameFetchLock = asyncio.Lock()
|
||||||
|
@ -103,9 +103,9 @@ class Message(object):
|
||||||
if not 'vol' in msg.params:
|
if not 'vol' in msg.params:
|
||||||
# prevent clipping on some Lyrebird tracks
|
# prevent clipping on some Lyrebird tracks
|
||||||
msg.params['vol'] = .8
|
msg.params['vol'] = .8
|
||||||
|
|
||||||
msg.params['vol'] = float(msg.params['vol'])
|
msg.params['vol'] = float(msg.params['vol'])
|
||||||
|
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
def parseForVariables(self):
|
def parseForVariables(self):
|
||||||
|
@ -215,8 +215,8 @@ class Reply(object):
|
||||||
self.forMessage = None
|
self.forMessage = None
|
||||||
self.utterances = []
|
self.utterances = []
|
||||||
self.setForMessage(message)
|
self.setForMessage(message)
|
||||||
|
|
||||||
|
|
||||||
def __getstate__(self):
|
def __getstate__(self):
|
||||||
# print(f'get reply {self}')
|
# print(f'get reply {self}')
|
||||||
state = self.__dict__.copy()
|
state = self.__dict__.copy()
|
||||||
|
@ -297,8 +297,8 @@ class Condition(object):
|
||||||
self.logInfo = None
|
self.logInfo = None
|
||||||
self.originalJsonString = None
|
self.originalJsonString = None
|
||||||
self.usedContainsDuration = None
|
self.usedContainsDuration = None
|
||||||
|
|
||||||
|
|
||||||
def __getstate__(self):
|
def __getstate__(self):
|
||||||
# print(f'get condition {self.id}')
|
# print(f'get condition {self.id}')
|
||||||
state = self.__dict__.copy()
|
state = self.__dict__.copy()
|
||||||
|
@ -500,8 +500,8 @@ class Direction(object):
|
||||||
self.conditions = []
|
self.conditions = []
|
||||||
self.conditionMet = None
|
self.conditionMet = None
|
||||||
self.isDiversionReturn = False
|
self.isDiversionReturn = False
|
||||||
|
|
||||||
|
|
||||||
def __getstate__(self):
|
def __getstate__(self):
|
||||||
# print(f'get direction {self.id}')
|
# print(f'get direction {self.id}')
|
||||||
state = self.__dict__.copy()
|
state = self.__dict__.copy()
|
||||||
|
@ -568,8 +568,8 @@ class Diversion(object):
|
||||||
|
|
||||||
if not self.method:
|
if not self.method:
|
||||||
raise Exception("No valid type given for diversion")
|
raise Exception("No valid type given for diversion")
|
||||||
|
|
||||||
|
|
||||||
def __getstate__(self):
|
def __getstate__(self):
|
||||||
# print(f'get diversion {self.id}')
|
# print(f'get diversion {self.id}')
|
||||||
state = self.__dict__.copy()
|
state = self.__dict__.copy()
|
||||||
|
@ -628,7 +628,7 @@ class Diversion(object):
|
||||||
}]
|
}]
|
||||||
"""
|
"""
|
||||||
self.counter +=1
|
self.counter +=1
|
||||||
|
# story.logger.warn(f"CREATING DIRECTIONS FOR {startMsg.id}")
|
||||||
finishMessageIds = story.getFinishesForMsg(startMsg)
|
finishMessageIds = story.getFinishesForMsg(startMsg)
|
||||||
finalTimeoutDuration = timeoutDuration
|
finalTimeoutDuration = timeoutDuration
|
||||||
finalContainsDurations = replyContainsDurations
|
finalContainsDurations = replyContainsDurations
|
||||||
|
@ -647,6 +647,7 @@ class Diversion(object):
|
||||||
finalContainsDurations = json.loads(condition.originalJsonString)['vars']['delays']
|
finalContainsDurations = json.loads(condition.originalJsonString)['vars']['delays']
|
||||||
|
|
||||||
i = 0
|
i = 0
|
||||||
|
# story.logger.warn(f"FINISHES: {finishMessageIds}")
|
||||||
for msgId in finishMessageIds:
|
for msgId in finishMessageIds:
|
||||||
# Some very ugly hack to add a direction & condition
|
# Some very ugly hack to add a direction & condition
|
||||||
i+=1
|
i+=1
|
||||||
|
@ -693,6 +694,7 @@ class Diversion(object):
|
||||||
story.logger.info(f"Created direction: {direction.id} {condition.id} with timeout {finalTimeoutDuration}s")
|
story.logger.info(f"Created direction: {direction.id} {condition.id} with timeout {finalTimeoutDuration}s")
|
||||||
story.add(condition)
|
story.add(condition)
|
||||||
story.add(direction)
|
story.add(direction)
|
||||||
|
# story.logger.warn(f"ADDED DIRECTION {direction.id}")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -944,7 +946,7 @@ class Configuration(object):
|
||||||
id = 'configuration'
|
id = 'configuration'
|
||||||
volume = 1 # Volume multiplier for 'play' command
|
volume = 1 # Volume multiplier for 'play' command
|
||||||
nothing_text = "nothing" # When variable is not set, but used in sentence, replace it with this word.
|
nothing_text = "nothing" # When variable is not set, but used in sentence, replace it with this word.
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def initFromJson(configClass, data, story):
|
def initFromJson(configClass, data, story):
|
||||||
config = Configuration()
|
config = Configuration()
|
||||||
|
@ -1007,22 +1009,22 @@ class Stopwatch(object):
|
||||||
def clearMark(self, name):
|
def clearMark(self, name):
|
||||||
if name in self.marks:
|
if name in self.marks:
|
||||||
self.marks.pop(name)
|
self.marks.pop(name)
|
||||||
|
|
||||||
def __getstate__(self):
|
def __getstate__(self):
|
||||||
# print(f'get stopwatch')
|
# print(f'get stopwatch')
|
||||||
state = self.__dict__.copy()
|
state = self.__dict__.copy()
|
||||||
state['isRunning'] = self.isRunning.is_set()
|
state['isRunning'] = self.isRunning.is_set()
|
||||||
return state
|
return state
|
||||||
|
|
||||||
def __setstate__(self, state):
|
def __setstate__(self, state):
|
||||||
self.__dict__.update(state)
|
self.__dict__.update(state)
|
||||||
|
|
||||||
self.isRunning = asyncio.Event()
|
self.isRunning = asyncio.Event()
|
||||||
if 'isRunning' in state and state['isRunning']:
|
if 'isRunning' in state and state['isRunning']:
|
||||||
self.isRunning.set()
|
self.isRunning.set()
|
||||||
else:
|
else:
|
||||||
self.isRunning.clear()
|
self.isRunning.clear()
|
||||||
|
|
||||||
|
|
||||||
class StoryState(object):
|
class StoryState(object):
|
||||||
"""
|
"""
|
||||||
|
@ -1065,7 +1067,7 @@ class StoryState(object):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
pass
|
pass
|
||||||
#
|
#
|
||||||
|
|
||||||
class Story(object):
|
class Story(object):
|
||||||
"""Story represents and manages a story/narrative flow"""
|
"""Story represents and manages a story/narrative flow"""
|
||||||
|
@ -1485,7 +1487,7 @@ class Story(object):
|
||||||
|
|
||||||
# TODO create timer event
|
# TODO create timer event
|
||||||
# self.commands.append({'msg':'TEST!'})
|
# self.commands.append({'msg':'TEST!'})
|
||||||
|
|
||||||
# Test stability of Central Command with deliberate crash
|
# Test stability of Central Command with deliberate crash
|
||||||
# if self.timer.getElapsed() > 10:
|
# if self.timer.getElapsed() > 10:
|
||||||
# raise Exception("Test exception")
|
# raise Exception("Test exception")
|
||||||
|
@ -1553,10 +1555,10 @@ class Story(object):
|
||||||
self.logger.critical(f"error: crash when reading wave file: {fn}")
|
self.logger.critical(f"error: crash when reading wave file: {fn}")
|
||||||
self.logger.exception(e)
|
self.logger.exception(e)
|
||||||
duration = 10 # some default duration to have something to fall back to
|
duration = 10 # some default duration to have something to fall back to
|
||||||
|
|
||||||
params = message.getParams().copy()
|
params = message.getParams().copy()
|
||||||
params['vol'] = params['vol'] * self.configuration.volume if 'vol' in params else self.configuration.volume
|
params['vol'] = params['vol'] * self.configuration.volume if 'vol' in params else self.configuration.volume
|
||||||
|
|
||||||
# self.hugvey.google.pause() # pause STT to avoid text events while decision is made
|
# self.hugvey.google.pause() # pause STT to avoid text events while decision is made
|
||||||
self.hugvey.sendCommand({
|
self.hugvey.sendCommand({
|
||||||
'action': 'play',
|
'action': 'play',
|
||||||
|
@ -1621,7 +1623,7 @@ class Story(object):
|
||||||
self.isRunning = True
|
self.isRunning = True
|
||||||
if not self.lastMsgFinishTime and self.currentMessage:
|
if not self.lastMsgFinishTime and self.currentMessage:
|
||||||
await self.setCurrentMessage(self.currentMessage)
|
await self.setCurrentMessage(self.currentMessage)
|
||||||
|
|
||||||
await self._renderer()
|
await self._renderer()
|
||||||
|
|
||||||
def isFinished(self):
|
def isFinished(self):
|
||||||
|
@ -1649,16 +1651,16 @@ class Story(object):
|
||||||
self.timer.pause()
|
self.timer.pause()
|
||||||
|
|
||||||
def calculateFinishesForMsg(self, msgId, depth = 0, checked = []):
|
def calculateFinishesForMsg(self, msgId, depth = 0, checked = []):
|
||||||
if msgId in checked:
|
# if msgId in checked:
|
||||||
return []
|
# return []
|
||||||
|
#
|
||||||
checked.append(msgId)
|
# checked.append(msgId)
|
||||||
|
|
||||||
if not msgId in self.directionsPerMsg or len(self.directionsPerMsg[msgId]) < 1:
|
if not msgId in self.directionsPerMsg or len(self.directionsPerMsg[msgId]) < 1:
|
||||||
# is finish
|
# is finish
|
||||||
return [msgId]
|
return [msgId]
|
||||||
|
|
||||||
if depth > 200:
|
if depth > 100:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
finishes = []
|
finishes = []
|
||||||
|
@ -1691,6 +1693,7 @@ class Story(object):
|
||||||
|
|
||||||
returns message ids
|
returns message ids
|
||||||
"""
|
"""
|
||||||
|
print(msg.id, self.strands)
|
||||||
if msg.id in self.strands:
|
if msg.id in self.strands:
|
||||||
return self.strands[msg.id]
|
return self.strands[msg.id]
|
||||||
|
|
||||||
|
@ -1710,22 +1713,22 @@ class Story(object):
|
||||||
|
|
||||||
# TODO: should the direction have at least a timeout condition set, or not perse?
|
# TODO: should the direction have at least a timeout condition set, or not perse?
|
||||||
return self.directionsPerMsg[msg.id][0]
|
return self.directionsPerMsg[msg.id][0]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def getStateDir(self):
|
def getStateDir(self):
|
||||||
return "/tmp"
|
return "/tmp"
|
||||||
# day = time.strftime("%Y%m%d")
|
# day = time.strftime("%Y%m%d")
|
||||||
# t = time.strftime("%H:%M:%S")
|
# t = time.strftime("%H:%M:%S")
|
||||||
#
|
#
|
||||||
# self.out_folder = os.path.join(self.main_folder, day, f"{self.hv_id}", t)
|
# self.out_folder = os.path.join(self.main_folder, day, f"{self.hv_id}", t)
|
||||||
# if not os.path.exists(self.out_folder):
|
# if not os.path.exists(self.out_folder):
|
||||||
# self.logger.debug(f"Create directory {self.out_folder}")
|
# self.logger.debug(f"Create directory {self.out_folder}")
|
||||||
# self.target_folder = os.makedirs(self.out_folder, exist_ok=True)
|
# self.target_folder = os.makedirs(self.out_folder, exist_ok=True)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def getStateFilename(cls, hv_id):
|
def getStateFilename(cls, hv_id):
|
||||||
return os.path.join(cls.getStateDir(), f"hugvey{hv_id}")
|
return os.path.join(cls.getStateDir(), f"hugvey{hv_id}")
|
||||||
|
|
||||||
def storeState(self):
|
def storeState(self):
|
||||||
# TODO: stop stopwatch
|
# TODO: stop stopwatch
|
||||||
fn = self.getStateFilename(self.hugvey.id)
|
fn = self.getStateFilename(self.hugvey.id)
|
||||||
|
@ -1735,49 +1738,48 @@ class Story(object):
|
||||||
pickle.dump(self, fp)
|
pickle.dump(self, fp)
|
||||||
# write atomic to disk: flush, close, rename
|
# write atomic to disk: flush, close, rename
|
||||||
fp.flush()
|
fp.flush()
|
||||||
os.fsync(fp.fileno())
|
os.fsync(fp.fileno())
|
||||||
|
|
||||||
os.rename(tmpfn, fn)
|
os.rename(tmpfn, fn)
|
||||||
self.logger.debug(f"saved state to {fn}")
|
self.logger.debug(f"saved state to {fn}")
|
||||||
|
|
||||||
def hasSavedState(self):
|
def hasSavedState(self):
|
||||||
return self.hugveyHasSavedState(self.hugvey.id)
|
return self.hugveyHasSavedState(self.hugvey.id)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def hugveyHasSavedState(cls, hv_id):
|
def hugveyHasSavedState(cls, hv_id):
|
||||||
return os.path.exists(cls.getStateFilename(hv_id))
|
return os.path.exists(cls.getStateFilename(hv_id))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def loadStoryFromState(cls, hugvey_state):
|
def loadStoryFromState(cls, hugvey_state):
|
||||||
# restart stopwatch
|
# restart stopwatch
|
||||||
with open(cls.getStateFilename(hugvey_state.id), 'rb') as fp:
|
with open(cls.getStateFilename(hugvey_state.id), 'rb') as fp:
|
||||||
story = pickle.load(fp)
|
story = pickle.load(fp)
|
||||||
|
|
||||||
story.hugvey = hugvey_state
|
story.hugvey = hugvey_state
|
||||||
story.logger = mainLogger.getChild(f"{story.hugvey.id}").getChild("story")
|
story.logger = mainLogger.getChild(f"{story.hugvey.id}").getChild("story")
|
||||||
return story
|
return story
|
||||||
# TODO: take running state etc.
|
# TODO: take running state etc.
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def clearSavedState(cls, hv_id):
|
def clearSavedState(cls, hv_id):
|
||||||
fn = cls.getStateFilename(hv_id)
|
fn = cls.getStateFilename(hv_id)
|
||||||
if os.path.exists(fn):
|
if os.path.exists(fn):
|
||||||
os.unlink(fn)
|
os.unlink(fn)
|
||||||
mainLogger.info(f"Removed state: {fn}")
|
mainLogger.info(f"Removed state: {fn}")
|
||||||
#
|
#
|
||||||
def __getstate__(self):
|
def __getstate__(self):
|
||||||
# Copy the object's state from self.__dict__ which contains
|
# Copy the object's state from self.__dict__ which contains
|
||||||
# all our instance attributes. Always use the dict.copy()
|
# all our instance attributes. Always use the dict.copy()
|
||||||
# method to avoid modifying the original state.
|
# method to avoid modifying the original state.
|
||||||
state = self.__dict__.copy()
|
state = self.__dict__.copy()
|
||||||
|
|
||||||
# Remove the unpicklable entries.
|
# Remove the unpicklable entries.
|
||||||
del state['hugvey']
|
del state['hugvey']
|
||||||
del state['logger']
|
del state['logger']
|
||||||
# del state['isRunning']
|
# del state['isRunning']
|
||||||
|
|
||||||
return state
|
return state
|
||||||
|
|
||||||
def __setstate__(self, state):
|
def __setstate__(self, state):
|
||||||
self.__dict__.update(state)
|
self.__dict__.update(state)
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,16 @@ echo "blacklist snd_bcm2835" > /etc/modprobe.d/internalsnd-blacklist.conf
|
||||||
echo "d /var/log/supervisor 0777 root root" > /etc/tmpfiles.d/supervisor.conf
|
echo "d /var/log/supervisor 0777 root root" > /etc/tmpfiles.d/supervisor.conf
|
||||||
cp installation/fstab /etc/fstab
|
cp installation/fstab /etc/fstab
|
||||||
|
|
||||||
|
# setup pulseaudio as system daemon and grant access: https://rudd-o.com/linux-and-free-software/how-to-make-pulseaudio-run-once-at-boot-for-all-your-users
|
||||||
|
cp installation/pulseaudio.service /etc/systemd/system/pulseaudio.service
|
||||||
|
# disable autospawn
|
||||||
|
cp installation/pulseaudio-client.conf /etc/pulse/client.conf
|
||||||
|
# put tsched to 0: https://wiki.archlinux.org/index.php/PulseAudio/Troubleshooting#Sound_stuttering_when_streaming_over_network
|
||||||
|
cp installation/pulse-default.pa /etc/pulse/default.pa
|
||||||
|
systemctl --system enable pulseaudio.service
|
||||||
|
systemctl --system start pulseaudio.service
|
||||||
|
usermod -a -G pulse-access pi
|
||||||
|
|
||||||
# Added chown=pi:pi
|
# Added chown=pi:pi
|
||||||
cp installation/supervisord.conf /etc/supervisor/supervisord.conf
|
cp installation/supervisord.conf /etc/supervisor/supervisord.conf
|
||||||
ln -s /home/pi/hugvey/supervisor.conf /etc/supervisor/conf.d/hugvey.conf
|
ln -s /home/pi/hugvey/supervisor.conf /etc/supervisor/conf.d/hugvey.conf
|
||||||
|
|
141
installation/pulse-default.pa
Normal file
141
installation/pulse-default.pa
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
#!/usr/bin/pulseaudio -nF
|
||||||
|
#
|
||||||
|
# This file is part of PulseAudio.
|
||||||
|
#
|
||||||
|
# PulseAudio is free software; you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU Lesser General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# PulseAudio is distributed in the hope that it will be useful, but
|
||||||
|
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
# General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
|
# along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# This startup script is used only if PulseAudio is started per-user
|
||||||
|
# (i.e. not in system mode)
|
||||||
|
|
||||||
|
.fail
|
||||||
|
|
||||||
|
### Automatically restore the volume of streams and devices
|
||||||
|
load-module module-device-restore
|
||||||
|
load-module module-stream-restore
|
||||||
|
load-module module-card-restore
|
||||||
|
|
||||||
|
### Automatically augment property information from .desktop files
|
||||||
|
### stored in /usr/share/application
|
||||||
|
load-module module-augment-properties
|
||||||
|
|
||||||
|
### Should be after module-*-restore but before module-*-detect
|
||||||
|
load-module module-switch-on-port-available
|
||||||
|
|
||||||
|
### Load audio drivers statically
|
||||||
|
### (it's probably better to not load these drivers manually, but instead
|
||||||
|
### use module-udev-detect -- see below -- for doing this automatically)
|
||||||
|
#load-module module-alsa-sink
|
||||||
|
#load-module module-alsa-source device=hw:1,0
|
||||||
|
#load-module module-oss device="/dev/dsp" sink_name=output source_name=input
|
||||||
|
#load-module module-oss-mmap device="/dev/dsp" sink_name=output source_name=input
|
||||||
|
#load-module module-null-sink
|
||||||
|
#load-module module-pipe-sink
|
||||||
|
|
||||||
|
### Automatically load driver modules depending on the hardware available
|
||||||
|
.ifexists module-udev-detect.so
|
||||||
|
# HUGVEY: put tsched to 0: https://wiki.archlinux.org/index.php/PulseAudio/Troubleshooting#Sound_stuttering_when_streaming_over_network
|
||||||
|
load-module module-udev-detect tsched=0
|
||||||
|
.else
|
||||||
|
### Use the static hardware detection module (for systems that lack udev support)
|
||||||
|
load-module module-detect
|
||||||
|
.endif
|
||||||
|
|
||||||
|
### Automatically connect sink and source if JACK server is present
|
||||||
|
.ifexists module-jackdbus-detect.so
|
||||||
|
.nofail
|
||||||
|
load-module module-jackdbus-detect channels=2
|
||||||
|
.fail
|
||||||
|
.endif
|
||||||
|
|
||||||
|
### Automatically load driver modules for Bluetooth hardware
|
||||||
|
.ifexists module-bluetooth-policy.so
|
||||||
|
load-module module-bluetooth-policy
|
||||||
|
.endif
|
||||||
|
|
||||||
|
.ifexists module-bluetooth-discover.so
|
||||||
|
load-module module-bluetooth-discover
|
||||||
|
.endif
|
||||||
|
|
||||||
|
### Load several protocols
|
||||||
|
.ifexists module-esound-protocol-unix.so
|
||||||
|
load-module module-esound-protocol-unix
|
||||||
|
.endif
|
||||||
|
load-module module-native-protocol-unix
|
||||||
|
|
||||||
|
### Network access (may be configured with paprefs, so leave this commented
|
||||||
|
### here if you plan to use paprefs)
|
||||||
|
#load-module module-esound-protocol-tcp
|
||||||
|
#load-module module-native-protocol-tcp
|
||||||
|
#load-module module-zeroconf-publish
|
||||||
|
|
||||||
|
### Load the RTP receiver module (also configured via paprefs, see above)
|
||||||
|
#load-module module-rtp-recv
|
||||||
|
|
||||||
|
### Load the RTP sender module (also configured via paprefs, see above)
|
||||||
|
#load-module module-null-sink sink_name=rtp format=s16be channels=2 rate=44100 sink_properties="device.description='RTP Multicast Sink'"
|
||||||
|
#load-module module-rtp-send source=rtp.monitor
|
||||||
|
|
||||||
|
### Load additional modules from GConf settings. This can be configured with the paprefs tool.
|
||||||
|
### Please keep in mind that the modules configured by paprefs might conflict with manually
|
||||||
|
### loaded modules.
|
||||||
|
.ifexists module-gconf.so
|
||||||
|
.nofail
|
||||||
|
load-module module-gconf
|
||||||
|
.fail
|
||||||
|
.endif
|
||||||
|
|
||||||
|
### Automatically restore the default sink/source when changed by the user
|
||||||
|
### during runtime
|
||||||
|
### NOTE: This should be loaded as early as possible so that subsequent modules
|
||||||
|
### that look up the default sink/source get the right value
|
||||||
|
load-module module-default-device-restore
|
||||||
|
|
||||||
|
### Automatically move streams to the default sink if the sink they are
|
||||||
|
### connected to dies, similar for sources
|
||||||
|
load-module module-rescue-streams
|
||||||
|
|
||||||
|
### Make sure we always have a sink around, even if it is a null sink.
|
||||||
|
load-module module-always-sink
|
||||||
|
|
||||||
|
### Honour intended role device property
|
||||||
|
load-module module-intended-roles
|
||||||
|
|
||||||
|
### Automatically suspend sinks/sources that become idle for too long
|
||||||
|
load-module module-suspend-on-idle
|
||||||
|
|
||||||
|
### If autoexit on idle is enabled we want to make sure we only quit
|
||||||
|
### when no local session needs us anymore.
|
||||||
|
.ifexists module-console-kit.so
|
||||||
|
load-module module-console-kit
|
||||||
|
.endif
|
||||||
|
.ifexists module-systemd-login.so
|
||||||
|
load-module module-systemd-login
|
||||||
|
.endif
|
||||||
|
|
||||||
|
### Enable positioned event sounds
|
||||||
|
load-module module-position-event-sounds
|
||||||
|
|
||||||
|
### Cork music/video streams when a phone stream is active
|
||||||
|
load-module module-role-cork
|
||||||
|
|
||||||
|
### Modules to allow autoloading of filters (such as echo cancellation)
|
||||||
|
### on demand. module-filter-heuristics tries to determine what filters
|
||||||
|
### make sense, and module-filter-apply does the heavy-lifting of
|
||||||
|
### loading modules and rerouting streams.
|
||||||
|
load-module module-filter-heuristics
|
||||||
|
load-module module-filter-apply
|
||||||
|
|
||||||
|
### Make some devices default
|
||||||
|
#set-default-sink output
|
||||||
|
#set-default-source input
|
18
installation/pulseaudio-client.conf
Normal file
18
installation/pulseaudio-client.conf
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# disable autospawn because we're in system wide mode
|
||||||
|
|
||||||
|
; default-sink =
|
||||||
|
; default-source =
|
||||||
|
; default-server =
|
||||||
|
; default-dbus-server =
|
||||||
|
|
||||||
|
autospawn = no
|
||||||
|
; daemon-binary = /usr/bin/pulseaudio
|
||||||
|
; extra-arguments = --log-target=syslog
|
||||||
|
|
||||||
|
; cookie-file =
|
||||||
|
|
||||||
|
; enable-shm = yes
|
||||||
|
; shm-size-bytes = 0 # setting this 0 will use the system-default, usually 64 MiB
|
||||||
|
|
||||||
|
; auto-connect-localhost = no
|
||||||
|
; auto-connect-display = no
|
9
installation/pulseaudio.service
Normal file
9
installation/pulseaudio.service
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
[Unit]
|
||||||
|
Description=PulseAudio system server
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=notify
|
||||||
|
ExecStart=/usr/bin/pulseaudio --daemonize=no --system --realtime --log-target=journal
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
|
@ -1,51 +1,52 @@
|
||||||
#N canvas 200 136 660 592 10;
|
#N canvas 976 211 660 592 10;
|
||||||
#X obj 131 277 dac~;
|
#X obj 131 277 dac~;
|
||||||
#X obj 131 227 readsf~;
|
#X obj 131 227 readsf~;
|
||||||
#X obj 209 209 bng 15 250 50 0 empty empty empty 17 7 0 10 -262144
|
#X obj 209 209 bng 15 250 50 0 empty empty empty 17 7 0 10 -262144
|
||||||
-1 -1;
|
-1 -1;
|
||||||
#X text 229 206 (re-)start loop;
|
#X text 229 206 (re-)start loop;
|
||||||
#X msg 132 170 open /home/a/projects/pd-play/testaudio.wav \, 1;
|
|
||||||
#X obj 208 459 netsend -u -b;
|
#X obj 208 459 netsend -u -b;
|
||||||
#X obj 208 481 tgl 15 0 empty empty empty 17 7 0 10 -262144 -1 -1 0
|
#X obj 208 481 tgl 15 0 empty empty empty 17 7 0 10 -262144 -1 -1 1
|
||||||
1;
|
1;
|
||||||
#X msg 355 464 disconnect;
|
#X msg 355 464 disconnect;
|
||||||
#X obj 208 393 list prepend send;
|
#X obj 208 393 list prepend send;
|
||||||
#X obj 208 415 list trim;
|
#X obj 208 415 list trim;
|
||||||
#X msg 357 434 connect localhost 5555;
|
|
||||||
#X obj 208 345 oscformat /trigger;
|
|
||||||
#X msg 51 193 0;
|
#X msg 51 193 0;
|
||||||
#X obj 227 83 bng 15 250 50 0 empty empty empty 17 7 0 10 -262144 -1
|
#X obj 227 83 bng 15 250 50 0 empty empty empty 17 7 0 10 -262144 -1
|
||||||
-1;
|
-1;
|
||||||
#X text 40 173 stop loop;
|
#X text 40 173 stop loop;
|
||||||
#X msg 210 292 1;
|
#X msg 208 320 1;
|
||||||
#X obj 318 84 bng 15 250 50 0 empty empty empty 17 7 0 10 -262144 -1
|
#X obj 318 84 bng 15 250 50 0 empty empty empty 17 7 0 10 -262144 -1
|
||||||
-1;
|
-1;
|
||||||
#X text 217 60 START;
|
#X text 217 60 START;
|
||||||
#X obj 436 84 tgl 15 0 empty empty empty 17 7 0 10 -262144 -1 -1 0
|
#X obj 436 84 tgl 15 0 empty empty empty 17 7 0 10 -262144 -1 -1 1
|
||||||
1;
|
1;
|
||||||
#X text 309 61 !STOP!;
|
#X text 309 61 !STOP!;
|
||||||
#X text 422 60 Playing indicator;
|
#X text 422 60 Playing indicator;
|
||||||
#X msg 261 118 1;
|
#X msg 261 118 1;
|
||||||
#X msg 330 117 0;
|
#X msg 330 117 0;
|
||||||
|
#X obj 208 345 oscformat /loop;
|
||||||
|
#X msg 356 434 connect 192.168.1.174 7400;
|
||||||
|
#X msg 132 170 open /mnt/stash/hugvey/score38_loop_40s_extra.wav \,
|
||||||
|
1;
|
||||||
#X connect 1 0 0 0;
|
#X connect 1 0 0 0;
|
||||||
#X connect 1 0 0 1;
|
#X connect 1 0 0 1;
|
||||||
#X connect 1 1 2 0;
|
#X connect 1 1 2 0;
|
||||||
#X connect 2 0 4 0;
|
#X connect 2 0 22 0;
|
||||||
#X connect 2 0 15 0;
|
#X connect 2 0 12 0;
|
||||||
#X connect 4 0 1 0;
|
#X connect 4 0 5 0;
|
||||||
#X connect 5 0 6 0;
|
#X connect 6 0 4 0;
|
||||||
#X connect 7 0 5 0;
|
#X connect 7 0 8 0;
|
||||||
#X connect 8 0 9 0;
|
#X connect 8 0 4 0;
|
||||||
#X connect 9 0 5 0;
|
#X connect 9 0 1 0;
|
||||||
#X connect 10 0 5 0;
|
#X connect 10 0 21 0;
|
||||||
#X connect 11 0 8 0;
|
#X connect 10 0 2 0;
|
||||||
#X connect 12 0 1 0;
|
#X connect 10 0 18 0;
|
||||||
#X connect 13 0 10 0;
|
#X connect 12 0 20 0;
|
||||||
#X connect 13 0 2 0;
|
#X connect 13 0 9 0;
|
||||||
#X connect 13 0 21 0;
|
#X connect 13 0 6 0;
|
||||||
#X connect 15 0 11 0;
|
#X connect 13 0 19 0;
|
||||||
#X connect 16 0 12 0;
|
#X connect 18 0 15 0;
|
||||||
#X connect 16 0 7 0;
|
#X connect 19 0 15 0;
|
||||||
#X connect 16 0 22 0;
|
#X connect 20 0 7 0;
|
||||||
#X connect 21 0 18 0;
|
#X connect 21 0 4 0;
|
||||||
#X connect 22 0 18 0;
|
#X connect 22 0 1 0;
|
||||||
|
|
|
@ -28,7 +28,7 @@ class Panopticon {
|
||||||
},
|
},
|
||||||
loadNarrative: function( code, file ) {
|
loadNarrative: function( code, file ) {
|
||||||
panopticon.hugveys.selectedId = null;
|
panopticon.hugveys.selectedId = null;
|
||||||
|
|
||||||
if(panopticon.hasGraph) {
|
if(panopticon.hasGraph) {
|
||||||
return panopticon.loadNarrative( code, file );
|
return panopticon.loadNarrative( code, file );
|
||||||
}
|
}
|
||||||
|
@ -79,16 +79,16 @@ class Panopticon {
|
||||||
|
|
||||||
|
|
||||||
this.socket = new ReconnectingWebSocket( "ws://localhost:8888/ws", null, { debug: false, reconnectInterval: 3000 } );
|
this.socket = new ReconnectingWebSocket( "ws://localhost:8888/ws", null, { debug: false, reconnectInterval: 3000 } );
|
||||||
|
|
||||||
if(this.hasGraph) {
|
if(this.hasGraph) {
|
||||||
this.graph = new Graph();
|
this.graph = new Graph();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
this.socket.addEventListener( 'open', ( e ) => {
|
this.socket.addEventListener( 'open', ( e ) => {
|
||||||
this.send( { action: 'init' } );
|
this.send( { action: 'init' } );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
// request close before unloading
|
// request close before unloading
|
||||||
window.addEventListener('beforeunload', function(){
|
window.addEventListener('beforeunload', function(){
|
||||||
panopticon.socket.close();
|
panopticon.socket.close();
|
||||||
|
@ -102,7 +102,7 @@ class Panopticon {
|
||||||
console.log("Websocket connected")
|
console.log("Websocket connected")
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let msg = JSON.parse( e.data );
|
let msg = JSON.parse( e.data );
|
||||||
if ( typeof msg['alert'] !== 'undefined' ) {
|
if ( typeof msg['alert'] !== 'undefined' ) {
|
||||||
alert( msg['alert'] );
|
alert( msg['alert'] );
|
||||||
|
@ -112,9 +112,9 @@ class Panopticon {
|
||||||
console.error( "not a valid message: " + e.data );
|
console.error( "not a valid message: " + e.data );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.debug(msg);
|
console.debug(msg);
|
||||||
|
|
||||||
switch ( msg['action'] ) {
|
switch ( msg['action'] ) {
|
||||||
|
|
||||||
case 'status':
|
case 'status':
|
||||||
|
@ -134,28 +134,28 @@ class Panopticon {
|
||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSelectedHugvey() {
|
updateSelectedHugvey() {
|
||||||
let hv = null;
|
let hv = null;
|
||||||
|
|
||||||
if(this.hugveys.selectedId) {
|
if(this.hugveys.selectedId) {
|
||||||
hv = this.getHugvey(this.hugveys.selectedId);
|
hv = this.getHugvey(this.hugveys.selectedId);
|
||||||
|
|
||||||
if(this.hasGraph) {
|
if(this.hasGraph) {
|
||||||
if(hv.language && this.graph.language_code != hv.language) {
|
if(hv.language && this.graph.language_code != hv.language) {
|
||||||
this.loadNarrative(hv.language);
|
this.loadNarrative(hv.language);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// let varEl = document.getElementById("variables");
|
// let varEl = document.getElementById("variables");
|
||||||
// varEl.innerHTML = "";
|
// varEl.innerHTML = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.hasGraph) {
|
if(this.hasGraph) {
|
||||||
this.graph.updateHugveyStatus(hv);
|
this.graph.updateHugveyStatus(hv);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getHugvey(id) {
|
getHugvey(id) {
|
||||||
for(let hv of this.hugveys.hugveys) {
|
for(let hv of this.hugveys.hugveys) {
|
||||||
if(hv.id == id) {
|
if(hv.id == id) {
|
||||||
|
@ -206,7 +206,7 @@ class Panopticon {
|
||||||
let graph = this.graph;
|
let graph = this.graph;
|
||||||
req.addEventListener( "load", function( e ) {
|
req.addEventListener( "load", function( e ) {
|
||||||
graph.loadData( JSON.parse( this.response ), code );
|
graph.loadData( JSON.parse( this.response ), code );
|
||||||
// console.log(, e);
|
// console.log(, e);
|
||||||
} );
|
} );
|
||||||
req.open( "GET", "/local/" + file );
|
req.open( "GET", "/local/" + file );
|
||||||
req.send();
|
req.send();
|
||||||
|
@ -236,7 +236,7 @@ class Panopticon {
|
||||||
console.log("Light", hv_id, light_id);
|
console.log("Light", hv_id, light_id);
|
||||||
this.send( { action: 'change_light', hugvey: hv_id, light_id: light_id } );
|
this.send( { action: 'change_light', hugvey: hv_id, light_id: light_id } );
|
||||||
}
|
}
|
||||||
|
|
||||||
playFromSelected(msg_id, reloadStory) {
|
playFromSelected(msg_id, reloadStory) {
|
||||||
if(!this.hugveys.selectedId) {
|
if(!this.hugveys.selectedId) {
|
||||||
alert('No hugvey selected');
|
alert('No hugvey selected');
|
||||||
|
@ -337,17 +337,17 @@ class Graph {
|
||||||
// used eg. after a condition creation.
|
// used eg. after a condition creation.
|
||||||
this.showMsg( this.selectedMsg );
|
this.showMsg( this.selectedMsg );
|
||||||
}
|
}
|
||||||
|
|
||||||
getAudioUrlForMsg(msg) {
|
getAudioUrlForMsg(msg) {
|
||||||
let isVariable = msg['text'].includes('$') ? '1' : '0';
|
let isVariable = msg['text'].includes('$') ? '1' : '0';
|
||||||
let lang = panopticon.graph.language_code;
|
let lang = panopticon.graph.language_code;
|
||||||
return `http://localhost:8888/voice?text=${encodeURIComponent(msg['text'])}&variable=${isVariable}&lang=${lang}&filename=0`;
|
return `http://localhost:8888/voice?text=${encodeURIComponent(msg['text'])}&variable=${isVariable}&lang=${lang}&filename=0`;
|
||||||
}
|
}
|
||||||
|
|
||||||
getConfig() {
|
getConfig() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getNumericId(prefix) {
|
getNumericId(prefix) {
|
||||||
let id, i = 0;
|
let id, i = 0;
|
||||||
let hasId= function(a, id) {
|
let hasId= function(a, id) {
|
||||||
|
@ -362,10 +362,10 @@ class Graph {
|
||||||
id = prefix + i;
|
id = prefix + i;
|
||||||
i++;
|
i++;
|
||||||
} while(hasId(this.data, id))
|
} while(hasId(this.data, id))
|
||||||
|
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
createDiversion(type) {
|
createDiversion(type) {
|
||||||
let div = {
|
let div = {
|
||||||
"@id": this.getNumericId(this.language_code.substring( 0, 2 ) + `-div-${type}#`),
|
"@id": this.getNumericId(this.language_code.substring( 0, 2 ) + `-div-${type}#`),
|
||||||
|
@ -373,7 +373,7 @@ class Graph {
|
||||||
'type': type,
|
'type': type,
|
||||||
'params': {}
|
'params': {}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(type == 'no_response') {
|
if(type == 'no_response') {
|
||||||
div['params']['consecutiveSilences'] = 3;
|
div['params']['consecutiveSilences'] = 3;
|
||||||
div['params']['timesOccured'] = 0;
|
div['params']['timesOccured'] = 0;
|
||||||
|
@ -399,33 +399,33 @@ class Graph {
|
||||||
div['params']['msgId'] = "";
|
div['params']['msgId'] = "";
|
||||||
}
|
}
|
||||||
else if(type == 'repeat') {
|
else if(type == 'repeat') {
|
||||||
div['params']['regex'] = "can you repeat that\\?";
|
div['params']['regex'] = "can you repeat that\\?";
|
||||||
} else {
|
} else {
|
||||||
console.log("invalid type", type);
|
console.log("invalid type", type);
|
||||||
alert('invalid type for diversion');
|
alert('invalid type for diversion');
|
||||||
}
|
}
|
||||||
|
|
||||||
if(type != 'repeat' && type != 'interrupt') {
|
if(type != 'repeat' && type != 'interrupt') {
|
||||||
div['params']['notAfterMsgId'] = "";
|
div['params']['notAfterMsgId'] = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
this.data.push( div );
|
this.data.push( div );
|
||||||
this.updateFromData();
|
this.updateFromData();
|
||||||
this.build();
|
this.build();
|
||||||
|
|
||||||
this.showDiversions();
|
this.showDiversions();
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteDiversion(div) {
|
deleteDiversion(div) {
|
||||||
this._rmNode( div );
|
this._rmNode( div );
|
||||||
this.showDiversions( );
|
this.showDiversions( );
|
||||||
}
|
}
|
||||||
|
|
||||||
showDiversions( ) {
|
showDiversions( ) {
|
||||||
let msgEl = document.getElementById( 'msg' );
|
let msgEl = document.getElementById( 'msg' );
|
||||||
msgEl.innerHTML = "";
|
msgEl.innerHTML = "";
|
||||||
|
|
||||||
let divsNoResponse =[], divsRepeat = [], divsReplyContains = [], divsTimeouts = [], divsInterrupts = [];
|
let divsNoResponse =[], divsRepeat = [], divsReplyContains = [], divsTimeouts = [], divsInterrupts = [];
|
||||||
for(let div of this.diversions) {
|
for(let div of this.diversions) {
|
||||||
|
|
||||||
|
@ -448,7 +448,7 @@ class Graph {
|
||||||
}}, ...notMsgOptions)
|
}}, ...notMsgOptions)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(div['type'] == 'no_response') {
|
if(div['type'] == 'no_response') {
|
||||||
let returnAttrs = {
|
let returnAttrs = {
|
||||||
'type': 'checkbox',
|
'type': 'checkbox',
|
||||||
|
@ -468,7 +468,7 @@ class Graph {
|
||||||
}
|
}
|
||||||
msgOptions.push(crel('option', optionParams , startMsg['@id']));
|
msgOptions.push(crel('option', optionParams , startMsg['@id']));
|
||||||
}
|
}
|
||||||
|
|
||||||
divsNoResponse.push(crel(
|
divsNoResponse.push(crel(
|
||||||
'div', {
|
'div', {
|
||||||
'class': 'diversion',
|
'class': 'diversion',
|
||||||
|
@ -539,7 +539,7 @@ class Graph {
|
||||||
}
|
}
|
||||||
msgOptions.push(crel('option', optionParams , startMsg['@id']));
|
msgOptions.push(crel('option', optionParams , startMsg['@id']));
|
||||||
}
|
}
|
||||||
|
|
||||||
divsReplyContains.push(crel(
|
divsReplyContains.push(crel(
|
||||||
'div', {
|
'div', {
|
||||||
'class': 'diversion',
|
'class': 'diversion',
|
||||||
|
@ -606,7 +606,7 @@ class Graph {
|
||||||
),
|
),
|
||||||
notAfterMsgIdEl
|
notAfterMsgIdEl
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if(div['type'] == 'timeout') {
|
if(div['type'] == 'timeout') {
|
||||||
let returnAttrs = {
|
let returnAttrs = {
|
||||||
'type': 'checkbox',
|
'type': 'checkbox',
|
||||||
|
@ -617,7 +617,7 @@ class Graph {
|
||||||
if(div['params']['returnAfterStrand']) {
|
if(div['params']['returnAfterStrand']) {
|
||||||
returnAttrs['checked'] = 'checked';
|
returnAttrs['checked'] = 'checked';
|
||||||
}
|
}
|
||||||
|
|
||||||
let totalOrLocalAttrs = {
|
let totalOrLocalAttrs = {
|
||||||
'type': 'checkbox',
|
'type': 'checkbox',
|
||||||
'on': {
|
'on': {
|
||||||
|
@ -627,7 +627,7 @@ class Graph {
|
||||||
if(div['params']['fromLastMessage']) {
|
if(div['params']['fromLastMessage']) {
|
||||||
totalOrLocalAttrs['checked'] = 'checked';
|
totalOrLocalAttrs['checked'] = 'checked';
|
||||||
}
|
}
|
||||||
|
|
||||||
let msgOptions = [crel('option',"")];
|
let msgOptions = [crel('option',"")];
|
||||||
let starts = this.messages.filter( m => m.hasOwnProperty('start') && m['start'] == true);
|
let starts = this.messages.filter( m => m.hasOwnProperty('start') && m['start'] == true);
|
||||||
for(let startMsg of starts) {
|
for(let startMsg of starts) {
|
||||||
|
@ -637,7 +637,7 @@ class Graph {
|
||||||
}
|
}
|
||||||
msgOptions.push(crel('option', optionParams , startMsg['@id']));
|
msgOptions.push(crel('option', optionParams , startMsg['@id']));
|
||||||
}
|
}
|
||||||
|
|
||||||
divsTimeouts.push(crel(
|
divsTimeouts.push(crel(
|
||||||
'div', {
|
'div', {
|
||||||
'class': 'diversion',
|
'class': 'diversion',
|
||||||
|
@ -734,7 +734,7 @@ class Graph {
|
||||||
}
|
}
|
||||||
msgOptions.push(crel('option', optionParams , startMsg['@id']));
|
msgOptions.push(crel('option', optionParams , startMsg['@id']));
|
||||||
}
|
}
|
||||||
|
|
||||||
divsInterrupts.push(crel(
|
divsInterrupts.push(crel(
|
||||||
'div', {'class': 'diversion'},
|
'div', {'class': 'diversion'},
|
||||||
crel('h3', div['@id']),
|
crel('h3', div['@id']),
|
||||||
|
@ -753,9 +753,9 @@ class Graph {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(divsReplyContains, divsNoResponse, divsRepeat, divsTimeouts, divsInterrupts);
|
console.log(divsReplyContains, divsNoResponse, divsRepeat, divsTimeouts, divsInterrupts);
|
||||||
|
|
||||||
let divEl = crel(
|
let divEl = crel(
|
||||||
'div',
|
'div',
|
||||||
{
|
{
|
||||||
|
@ -797,7 +797,7 @@ class Graph {
|
||||||
'on': {
|
'on': {
|
||||||
'click': (e) => this.createDiversion('repeat')
|
'click': (e) => this.createDiversion('repeat')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'New case for repeat'
|
'New case for repeat'
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
@ -810,7 +810,7 @@ class Graph {
|
||||||
'on': {
|
'on': {
|
||||||
'click': (e) => this.createDiversion('timeout')
|
'click': (e) => this.createDiversion('timeout')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'New case for timeout'
|
'New case for timeout'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -824,12 +824,12 @@ class Graph {
|
||||||
// 'on': {
|
// 'on': {
|
||||||
// 'click': (e) => this.createDiversion('interrupt')
|
// 'click': (e) => this.createDiversion('interrupt')
|
||||||
// }
|
// }
|
||||||
// },
|
// },
|
||||||
// 'New case for Interrupt'
|
// 'New case for Interrupt'
|
||||||
// )
|
// )
|
||||||
// )
|
// )
|
||||||
);
|
);
|
||||||
|
|
||||||
msgEl.appendChild(divEl);
|
msgEl.appendChild(divEl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -904,19 +904,19 @@ class Graph {
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
document.getElementById("interface").appendChild(configEl);
|
document.getElementById("interface").appendChild(configEl);
|
||||||
}
|
}
|
||||||
|
|
||||||
showMsg( msg ) {
|
showMsg( msg ) {
|
||||||
let msgEl = document.getElementById( 'msg' );
|
let msgEl = document.getElementById( 'msg' );
|
||||||
msgEl.innerHTML = "";
|
msgEl.innerHTML = "";
|
||||||
|
|
||||||
if(msg == null){
|
if(msg == null){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let startAttributes = {
|
let startAttributes = {
|
||||||
'name': msg['@id'] + '-start',
|
'name': msg['@id'] + '-start',
|
||||||
// 'readonly': 'readonly',
|
// 'readonly': 'readonly',
|
||||||
|
@ -939,7 +939,7 @@ class Graph {
|
||||||
if ( msg['beginning'] == true ) {
|
if ( msg['beginning'] == true ) {
|
||||||
beginningAttributes['checked'] = 'checked';
|
beginningAttributes['checked'] = 'checked';
|
||||||
}
|
}
|
||||||
|
|
||||||
// chapter marker:
|
// chapter marker:
|
||||||
let chapterAttributes = {
|
let chapterAttributes = {
|
||||||
'name': msg['@id'] + '-chapterStart',
|
'name': msg['@id'] + '-chapterStart',
|
||||||
|
@ -952,14 +952,14 @@ class Graph {
|
||||||
if ( typeof msg['chapterStart'] !== 'undefined' && msg['chapterStart'] == true ) {
|
if ( typeof msg['chapterStart'] !== 'undefined' && msg['chapterStart'] == true ) {
|
||||||
chapterAttributes['checked'] = 'checked';
|
chapterAttributes['checked'] = 'checked';
|
||||||
}
|
}
|
||||||
|
|
||||||
let params = {};
|
let params = {};
|
||||||
if(msg.hasOwnProperty('params')) {
|
if(msg.hasOwnProperty('params')) {
|
||||||
params = msg['params'];
|
params = msg['params'];
|
||||||
} else {
|
} else {
|
||||||
msg['params'] = {};
|
msg['params'] = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
let audioSrcEl = crel('source', {'src': msg['audio'] ? msg['audio']['file'] : this.getAudioUrlForMsg(msg)});
|
let audioSrcEl = crel('source', {'src': msg['audio'] ? msg['audio']['file'] : this.getAudioUrlForMsg(msg)});
|
||||||
let audioSpan = crel(
|
let audioSpan = crel(
|
||||||
'span',
|
'span',
|
||||||
|
@ -1105,7 +1105,7 @@ class Graph {
|
||||||
}
|
}
|
||||||
} )
|
} )
|
||||||
),
|
),
|
||||||
|
|
||||||
// color for beter overview
|
// color for beter overview
|
||||||
|
|
||||||
crel( 'label',
|
crel( 'label',
|
||||||
|
@ -1123,9 +1123,9 @@ class Graph {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
msgEl.appendChild( msgInfoEl );
|
msgEl.appendChild( msgInfoEl );
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if(panopticon.hugveys.selectedId) {
|
if(panopticon.hugveys.selectedId) {
|
||||||
msgEl.appendChild(crel(
|
msgEl.appendChild(crel(
|
||||||
'div',
|
'div',
|
||||||
|
@ -1161,9 +1161,9 @@ class Graph {
|
||||||
"Continue on #" + panopticon.hugveys.selectedId
|
"Continue on #" + panopticon.hugveys.selectedId
|
||||||
)
|
)
|
||||||
));
|
));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// let directionHEl = document.createElement('h2');
|
// let directionHEl = document.createElement('h2');
|
||||||
// directionHEl.innerHTML = "Directions";
|
// directionHEl.innerHTML = "Directions";
|
||||||
|
@ -1220,13 +1220,13 @@ class Graph {
|
||||||
'on': {
|
'on': {
|
||||||
'click': ( e ) => {
|
'click': ( e ) => {
|
||||||
if(confirm("Do you want to remove this direction and its conditions?")) {
|
if(confirm("Do you want to remove this direction and its conditions?")) {
|
||||||
g.rmDirection( direction );
|
g.rmDirection( direction );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 'disconnect')
|
}, 'disconnect')
|
||||||
);
|
);
|
||||||
|
|
||||||
for ( let conditionId of direction['conditions'] ) {
|
for ( let conditionId of direction['conditions'] ) {
|
||||||
let condition = this.getNodeById( conditionId );
|
let condition = this.getNodeById( conditionId );
|
||||||
directionEl.appendChild( this.getEditConditionFormEl( condition, direction ) );
|
directionEl.appendChild( this.getEditConditionFormEl( condition, direction ) );
|
||||||
|
@ -1246,7 +1246,7 @@ class Graph {
|
||||||
'click': ( e ) => {
|
'click': ( e ) => {
|
||||||
if(confirm("Do you want to remove this condition?")) {
|
if(confirm("Do you want to remove this condition?")) {
|
||||||
// console.log('remove condition for direction', condition, direction);
|
// console.log('remove condition for direction', condition, direction);
|
||||||
panopticon.graph.rmCondition( condition, direction );
|
panopticon.graph.rmCondition( condition, direction );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1264,7 +1264,7 @@ class Graph {
|
||||||
} );
|
} );
|
||||||
labelLabel.appendChild( labelInput );
|
labelLabel.appendChild( labelInput );
|
||||||
conditionEl.appendChild( labelLabel );
|
conditionEl.appendChild( labelLabel );
|
||||||
|
|
||||||
|
|
||||||
// for ( let v in condition['vars'] ) {
|
// for ( let v in condition['vars'] ) {
|
||||||
// let varLabel = document.createElement( 'label' );
|
// let varLabel = document.createElement( 'label' );
|
||||||
|
@ -1309,14 +1309,14 @@ class Graph {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getConditionInputsForType( type, conditionId, values ) {
|
getConditionInputsForType( type, conditionId, values ) {
|
||||||
let inputs = [];
|
let inputs = [];
|
||||||
let vars = this.getConditionTypes()[type];
|
let vars = this.getConditionTypes()[type];
|
||||||
for ( let v in vars ) {
|
for ( let v in vars ) {
|
||||||
let attr = vars[v];
|
let attr = vars[v];
|
||||||
let inputType = attr.hasOwnProperty('tag') ? attr['tag'] : 'input';
|
let inputType = attr.hasOwnProperty('tag') ? attr['tag'] : 'input';
|
||||||
|
|
||||||
attr['name'] = typeof conditionId == 'undefined' ? v : `${conditionId}-vars.${v}`;
|
attr['name'] = typeof conditionId == 'undefined' ? v : `${conditionId}-vars.${v}`;
|
||||||
if(typeof values != 'undefined') {
|
if(typeof values != 'undefined') {
|
||||||
let value = this._getValueForPath(v, values);
|
let value = this._getValueForPath(v, values);
|
||||||
|
@ -1326,12 +1326,12 @@ class Graph {
|
||||||
}
|
}
|
||||||
attr['value'] = typeof value == 'undefined' ? "": value;
|
attr['value'] = typeof value == 'undefined' ? "": value;
|
||||||
attr['on'] = {
|
attr['on'] = {
|
||||||
'change': this.getEditEventListener()
|
'change': this.getEditEventListener()
|
||||||
} ;
|
} ;
|
||||||
} else {
|
} else {
|
||||||
// console.log(attr);
|
// console.log(attr);
|
||||||
}
|
}
|
||||||
|
|
||||||
inputs.push(
|
inputs.push(
|
||||||
crel( 'label',
|
crel( 'label',
|
||||||
crel( 'span', {
|
crel( 'span', {
|
||||||
|
@ -1352,7 +1352,7 @@ class Graph {
|
||||||
conditionForm.appendChild(i);
|
conditionForm.appendChild(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_getValueForPath(path, vars) {
|
_getValueForPath(path, vars) {
|
||||||
path = path.split( '.' ); // use vars.test to set ['vars']['test'] = value
|
path = path.split( '.' ); // use vars.test to set ['vars']['test'] = value
|
||||||
let v = vars;
|
let v = vars;
|
||||||
|
@ -1372,7 +1372,7 @@ class Graph {
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save an array path (string) with a value to an object. Used to turn
|
* Save an array path (string) with a value to an object. Used to turn
|
||||||
* strings into nested arrays
|
* strings into nested arrays
|
||||||
|
@ -1421,7 +1421,7 @@ class Graph {
|
||||||
form.delete( 'type' );
|
form.delete( 'type' );
|
||||||
let label = form.get( 'label' );
|
let label = form.get( 'label' );
|
||||||
form.delete( 'label' );
|
form.delete( 'label' );
|
||||||
|
|
||||||
// checkboxes to true/false
|
// checkboxes to true/false
|
||||||
let defs = g.getConditionTypes()[type];
|
let defs = g.getConditionTypes()[type];
|
||||||
// console.log(defs);
|
// console.log(defs);
|
||||||
|
@ -1432,13 +1432,13 @@ class Graph {
|
||||||
form.set(field, form.has(field));
|
form.set(field, form.has(field));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let vars = {};
|
let vars = {};
|
||||||
for ( var pair of form.entries() ) {
|
for ( var pair of form.entries() ) {
|
||||||
// FormData only has strings & blobs, we want booleans:
|
// FormData only has strings & blobs, we want booleans:
|
||||||
if(pair[1] === 'true') pair[1] = true;
|
if(pair[1] === 'true') pair[1] = true;
|
||||||
if(pair[1] === 'false') pair[1] = false;
|
if(pair[1] === 'false') pair[1] = false;
|
||||||
|
|
||||||
vars = g._formPathToVars(pair[0], pair[1], vars);
|
vars = g._formPathToVars(pair[0], pair[1], vars);
|
||||||
}
|
}
|
||||||
// TODO: checkboxes
|
// TODO: checkboxes
|
||||||
|
@ -1481,7 +1481,7 @@ class Graph {
|
||||||
|
|
||||||
return addConditionEl;
|
return addConditionEl;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* remove condition from the graph or merely from the given direction
|
* remove condition from the graph or merely from the given direction
|
||||||
* @param {any} condition The condition to remove
|
* @param {any} condition The condition to remove
|
||||||
|
@ -1496,7 +1496,7 @@ class Graph {
|
||||||
if(pos > -1) {
|
if(pos > -1) {
|
||||||
direction['conditions'].splice(pos, 1);
|
direction['conditions'].splice(pos, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
for(let dir of this.directions) {
|
for(let dir of this.directions) {
|
||||||
// console.log('check if condition exists for dir', dir)
|
// console.log('check if condition exists for dir', dir)
|
||||||
if(dir['conditions'].indexOf(id) > -1) {
|
if(dir['conditions'].indexOf(id) > -1) {
|
||||||
|
@ -1547,14 +1547,14 @@ class Graph {
|
||||||
"afterrunTime": 0.5,
|
"afterrunTime": 0.5,
|
||||||
}
|
}
|
||||||
this.data.push( msg );
|
this.data.push( msg );
|
||||||
|
|
||||||
console.log("skip or not to skip?", skipRebuild);
|
console.log("skip or not to skip?", skipRebuild);
|
||||||
if(typeof skipRebuild == 'undefined' || !skipRebuild) {
|
if(typeof skipRebuild == 'undefined' || !skipRebuild) {
|
||||||
this.updateFromData();
|
this.updateFromData();
|
||||||
this.build();
|
this.build();
|
||||||
this.selectMsg(msg);
|
this.selectMsg(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1611,7 +1611,7 @@ class Graph {
|
||||||
"conditions": []
|
"conditions": []
|
||||||
}
|
}
|
||||||
this.data.push( dir );
|
this.data.push( dir );
|
||||||
|
|
||||||
let skipDistances;
|
let skipDistances;
|
||||||
// orphaned target and source has no other destinations. We can copy the vertical position:
|
// orphaned target and source has no other destinations. We can copy the vertical position:
|
||||||
if(this.getDirectionsFrom( source ).length < 1 && this.getDirectionsFrom( target ).length < 1 && this.getDirectionsTo( target ).length < 1) {
|
if(this.getDirectionsFrom( source ).length < 1 && this.getDirectionsFrom( target ).length < 1 && this.getDirectionsTo( target ).length < 1) {
|
||||||
|
@ -1620,11 +1620,11 @@ class Graph {
|
||||||
let d = [distance[0] + 1, distance[1]];
|
let d = [distance[0] + 1, distance[1]];
|
||||||
// create a distance based on source's position
|
// create a distance based on source's position
|
||||||
// this saves us from running the slow calculateDistancesFromStart
|
// this saves us from running the slow calculateDistancesFromStart
|
||||||
this.distances[target['@id']] = d;
|
this.distances[target['@id']] = d;
|
||||||
} else {
|
} else {
|
||||||
skipDistances = false;
|
skipDistances = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateFromData(skipDistances);
|
this.updateFromData(skipDistances);
|
||||||
this.build();
|
this.build();
|
||||||
return dir;
|
return dir;
|
||||||
|
@ -1639,18 +1639,18 @@ class Graph {
|
||||||
this.addMsg();
|
this.addMsg();
|
||||||
this.build();
|
this.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
createConnectedMsg(sourceMsg) {
|
createConnectedMsg(sourceMsg) {
|
||||||
console.time('createConnected');
|
console.time('createConnected');
|
||||||
console.time("Add");
|
console.time("Add");
|
||||||
let newMsg = this.addMsg(true); // skipRebuild = true, as addDirection() already rebuilds the graph
|
let newMsg = this.addMsg(true); // skipRebuild = true, as addDirection() already rebuilds the graph
|
||||||
this.getNodeById(newMsg['@id']).y = this.getNodeById(sourceMsg['@id']).y;
|
this.getNodeById(newMsg['@id']).y = this.getNodeById(sourceMsg['@id']).y;
|
||||||
|
|
||||||
if(this.getNodeById(sourceMsg['@id']).hasOwnProperty('color')){
|
if(this.getNodeById(sourceMsg['@id']).hasOwnProperty('color')){
|
||||||
this.getNodeById(newMsg['@id']).color = this.getNodeById(sourceMsg['@id']).color
|
this.getNodeById(newMsg['@id']).color = this.getNodeById(sourceMsg['@id']).color
|
||||||
}
|
}
|
||||||
console.timeEnd("Add");
|
console.timeEnd("Add");
|
||||||
|
|
||||||
console.time("direction");
|
console.time("direction");
|
||||||
this.addDirection(sourceMsg, newMsg);
|
this.addDirection(sourceMsg, newMsg);
|
||||||
console.timeEnd("direction");
|
console.timeEnd("direction");
|
||||||
|
@ -1677,15 +1677,15 @@ class Graph {
|
||||||
let graph = this;
|
let graph = this;
|
||||||
let el = function( e ) {
|
let el = function( e ) {
|
||||||
console.info("Changed", e);
|
console.info("Changed", e);
|
||||||
let parts = e.srcElement.name.split( '-' );
|
let parts = e.target.name.split( '-' );
|
||||||
let field = parts.pop();
|
let field = parts.pop();
|
||||||
let id = parts.join('-');
|
let id = parts.join('-');
|
||||||
let node = graph.getNodeById( id );
|
let node = graph.getNodeById( id );
|
||||||
let path = field.split( '.' ); // use vars.test to set ['vars']['test'] = value
|
let path = field.split( '.' ); // use vars.test to set ['vars']['test'] = value
|
||||||
var res = node;
|
var res = node;
|
||||||
let value = e.srcElement.value
|
let value = e.target.value
|
||||||
if(e.srcElement.type == 'checkbox') {
|
if(e.target.type == 'checkbox') {
|
||||||
value = e.srcElement.checked;
|
value = e.target.checked;
|
||||||
}
|
}
|
||||||
for ( var i = 0; i < path.length; i++ ) {
|
for ( var i = 0; i < path.length; i++ ) {
|
||||||
if ( i == ( path.length - 1 ) ) {
|
if ( i == ( path.length - 1 ) ) {
|
||||||
|
@ -1698,7 +1698,7 @@ class Graph {
|
||||||
// node[field] = e.srcElement.value;
|
// node[field] = e.srcElement.value;
|
||||||
|
|
||||||
graph.build();
|
graph.build();
|
||||||
|
|
||||||
if(typeof callback !== 'undefined'){
|
if(typeof callback !== 'undefined'){
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
|
@ -1769,11 +1769,11 @@ class Graph {
|
||||||
console.info("Save json", formData );
|
console.info("Save json", formData );
|
||||||
var request = new XMLHttpRequest();
|
var request = new XMLHttpRequest();
|
||||||
request.open( "POST", "http://localhost:8888/upload" );
|
request.open( "POST", "http://localhost:8888/upload" );
|
||||||
|
|
||||||
if(callback) {
|
if(callback) {
|
||||||
request.addEventListener( "load", callback);
|
request.addEventListener( "load", callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
request.send( formData );
|
request.send( formData );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1789,13 +1789,13 @@ class Graph {
|
||||||
this.directions = this.data.filter(( node ) => node['@type'] == 'Direction' );
|
this.directions = this.data.filter(( node ) => node['@type'] == 'Direction' );
|
||||||
this.conditions = this.data.filter(( node ) => node['@type'] == 'Condition' );
|
this.conditions = this.data.filter(( node ) => node['@type'] == 'Condition' );
|
||||||
this.diversions = this.data.filter(( node ) => node['@type'] == 'Diversion' );
|
this.diversions = this.data.filter(( node ) => node['@type'] == 'Diversion' );
|
||||||
|
|
||||||
let configurations = this.data.filter(( node ) => node['@type'] == 'Configuration' );
|
let configurations = this.data.filter(( node ) => node['@type'] == 'Configuration' );
|
||||||
this.configuration = configurations.length > 0 ? configurations[0] : {
|
this.configuration = configurations.length > 0 ? configurations[0] : {
|
||||||
"@id": "config",
|
"@id": "config",
|
||||||
"@type": "Configuration"
|
"@type": "Configuration"
|
||||||
};
|
};
|
||||||
|
|
||||||
document.getElementById('current_lang').innerHTML = "";
|
document.getElementById('current_lang').innerHTML = "";
|
||||||
document.getElementById('current_lang').appendChild(crel('span', {
|
document.getElementById('current_lang').appendChild(crel('span', {
|
||||||
'class': 'flag-icon ' + this.language_code
|
'class': 'flag-icon ' + this.language_code
|
||||||
|
@ -1803,7 +1803,7 @@ class Graph {
|
||||||
let storyEl = document.getElementById('story');
|
let storyEl = document.getElementById('story');
|
||||||
storyEl.classList.remove(... panopticon.languages.map((l) => l['code']))
|
storyEl.classList.remove(... panopticon.languages.map((l) => l['code']))
|
||||||
storyEl.classList.add(this.language_code);
|
storyEl.classList.add(this.language_code);
|
||||||
|
|
||||||
if(typeof skipDistances == 'undefined' || !skipDistances) {
|
if(typeof skipDistances == 'undefined' || !skipDistances) {
|
||||||
this.distances = this.calculateDistancesFromStart();
|
this.distances = this.calculateDistancesFromStart();
|
||||||
}
|
}
|
||||||
|
@ -1811,7 +1811,7 @@ class Graph {
|
||||||
// save state;
|
// save state;
|
||||||
this.saveState();
|
this.saveState();
|
||||||
}
|
}
|
||||||
|
|
||||||
updateHugveyStatus(hv) {
|
updateHugveyStatus(hv) {
|
||||||
let els = document.getElementsByClassName('beenHit');
|
let els = document.getElementsByClassName('beenHit');
|
||||||
while(els.length > 0) {
|
while(els.length > 0) {
|
||||||
|
@ -1820,11 +1820,11 @@ class Graph {
|
||||||
if(!hv || typeof hv['history'] == 'undefined') {
|
if(!hv || typeof hv['history'] == 'undefined') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(hv['history'].hasOwnProperty('messages')){
|
if(hv['history'].hasOwnProperty('messages')){
|
||||||
for(let msg of hv['history']['messages']) {
|
for(let msg of hv['history']['messages']) {
|
||||||
document.getElementById(msg[0]['id']).classList.add('beenHit');
|
document.getElementById(msg[0]['id']).classList.add('beenHit');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(hv['history'].hasOwnProperty('directions')){
|
if(hv['history'].hasOwnProperty('directions')){
|
||||||
for(let msg of hv['history']['directions']) {
|
for(let msg of hv['history']['directions']) {
|
||||||
|
@ -1857,7 +1857,7 @@ class Graph {
|
||||||
return fx;
|
return fx;
|
||||||
}).strength(50))
|
}).strength(50))
|
||||||
.force( "forceY", d3.forceY(function(m){
|
.force( "forceY", d3.forceY(function(m){
|
||||||
// if(panopticon.graph.distances[m['@id']] !== null )
|
// if(panopticon.graph.distances[m['@id']] !== null )
|
||||||
// console.log(panopticon.graph.distances[m['@id']][1]);
|
// 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
|
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);
|
// console.log('fx', m['@id'], panopticon.graph.distances[m['@id']], fx);
|
||||||
|
@ -1872,7 +1872,7 @@ class Graph {
|
||||||
.selectAll( "g" )
|
.selectAll( "g" )
|
||||||
.data( this.messages, n => n['@id'] )
|
.data( this.messages, n => n['@id'] )
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|
||||||
// Update existing nodes
|
// Update existing nodes
|
||||||
let newNode = node.enter();
|
let newNode = node.enter();
|
||||||
|
@ -1887,7 +1887,7 @@ class Graph {
|
||||||
.attr( 'r', this.nodeSize )
|
.attr( 'r', this.nodeSize )
|
||||||
// .text(d => d.id)
|
// .text(d => d.id)
|
||||||
;
|
;
|
||||||
|
|
||||||
let textId = newNodeG.append( "text" ).attr( 'class', 'msg_id' );
|
let textId = newNodeG.append( "text" ).attr( 'class', 'msg_id' );
|
||||||
let textContent = newNodeG.append( "text" ).attr( 'class', 'msg_txt' );
|
let textContent = newNodeG.append( "text" ).attr( 'class', 'msg_txt' );
|
||||||
let statusIcon = newNodeG.append( "image" )
|
let statusIcon = newNodeG.append( "image" )
|
||||||
|
@ -1901,7 +1901,7 @@ class Graph {
|
||||||
// remove
|
// remove
|
||||||
node.exit().remove();
|
node.exit().remove();
|
||||||
node = node.merge( newNodeG );
|
node = node.merge( newNodeG );
|
||||||
|
|
||||||
|
|
||||||
// for all existing nodes:
|
// for all existing nodes:
|
||||||
node.attr( 'class', msg => {
|
node.attr( 'class', msg => {
|
||||||
|
@ -1916,9 +1916,9 @@ class Graph {
|
||||||
|
|
||||||
return classes.join( ' ' );
|
return classes.join( ' ' );
|
||||||
} )
|
} )
|
||||||
|
|
||||||
.on(".drag", null)
|
.on(".drag", null)
|
||||||
.call(
|
.call(
|
||||||
d3.drag( this.simulation )
|
d3.drag( this.simulation )
|
||||||
.on("start", function(d){
|
.on("start", function(d){
|
||||||
if (!d3.event.active) panopticon.graph.simulation.alphaTarget(0.3).restart();
|
if (!d3.event.active) panopticon.graph.simulation.alphaTarget(0.3).restart();
|
||||||
|
@ -1935,9 +1935,9 @@ class Graph {
|
||||||
d.fy = null;
|
d.fy = null;
|
||||||
})
|
})
|
||||||
// .container(document.getElementById('container'))
|
// .container(document.getElementById('container'))
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
node.select('circle').attr('style', (d) => 'fill: ' + (d.hasOwnProperty('color') ? d['color'] : '#77618e'));
|
node.select('circle').attr('style', (d) => 'fill: ' + (d.hasOwnProperty('color') ? d['color'] : '#77618e'));
|
||||||
|
|
||||||
let link = this.linkG
|
let link = this.linkG
|
||||||
|
@ -1947,7 +1947,7 @@ class Graph {
|
||||||
let newLink = link.enter()
|
let newLink = link.enter()
|
||||||
.append( "line" )
|
.append( "line" )
|
||||||
;
|
;
|
||||||
|
|
||||||
//remove
|
//remove
|
||||||
link.exit().remove();
|
link.exit().remove();
|
||||||
link = link.merge( newLink );
|
link = link.merge( newLink );
|
||||||
|
@ -2032,7 +2032,7 @@ class Graph {
|
||||||
}
|
}
|
||||||
return this.svg.node();
|
return this.svg.node();
|
||||||
}
|
}
|
||||||
|
|
||||||
calculateDistancesFromStart() {
|
calculateDistancesFromStart() {
|
||||||
console.time('calculateDistancesFromStart');
|
console.time('calculateDistancesFromStart');
|
||||||
let starts = this.messages.filter( m => m.hasOwnProperty('start') && m['start'] == true);
|
let starts = this.messages.filter( m => m.hasOwnProperty('start') && m['start'] == true);
|
||||||
|
@ -2040,26 +2040,26 @@ class Graph {
|
||||||
console.error("No start set");
|
console.error("No start set");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//initiate distances
|
//initiate distances
|
||||||
let distances = {};
|
let distances = {};
|
||||||
for(let msg of this.messages) {
|
for(let msg of this.messages) {
|
||||||
// distances[msg['@id']] = msg === startMsg ? 0 : null;
|
// distances[msg['@id']] = msg === startMsg ? 0 : null;
|
||||||
distances[msg['@id']] = null;
|
distances[msg['@id']] = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
let targetsPerMsg = {};
|
let targetsPerMsg = {};
|
||||||
let sourcesPerMsg = {};
|
let sourcesPerMsg = {};
|
||||||
// console.log("dir", this.directions);
|
// console.log("dir", this.directions);
|
||||||
for(let direction of this.directions) {
|
for(let direction of this.directions) {
|
||||||
let from = typeof direction['source'] == "string" ? direction['source'] : direction['source']['@id'];
|
let from = typeof direction['source'] == "string" ? direction['source'] : direction['source']['@id'];
|
||||||
let to = typeof direction['target'] == "string" ? direction['target'] : direction['target']['@id'];
|
let to = typeof direction['target'] == "string" ? direction['target'] : direction['target']['@id'];
|
||||||
|
|
||||||
if(!targetsPerMsg.hasOwnProperty(from)) {
|
if(!targetsPerMsg.hasOwnProperty(from)) {
|
||||||
targetsPerMsg[from] = [];
|
targetsPerMsg[from] = [];
|
||||||
}
|
}
|
||||||
targetsPerMsg[from].push(to);
|
targetsPerMsg[from].push(to);
|
||||||
|
|
||||||
|
|
||||||
if(!sourcesPerMsg.hasOwnProperty(to)) {
|
if(!sourcesPerMsg.hasOwnProperty(to)) {
|
||||||
sourcesPerMsg[to] = [];
|
sourcesPerMsg[to] = [];
|
||||||
|
@ -2074,21 +2074,21 @@ class Graph {
|
||||||
// end of trail
|
// end of trail
|
||||||
return yPos;
|
return yPos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let i = 0, y =0;
|
let i = 0, y =0;
|
||||||
for(let childMsgId of msgsPerMsg[msgId]) {
|
for(let childMsgId of msgsPerMsg[msgId]) {
|
||||||
if(distances[childMsgId] !== null){
|
if(distances[childMsgId] !== null){
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if(distances[childMsgId] === null || (goingDown && distances[childMsgId][0] > depth)) {
|
if(distances[childMsgId] === null || (goingDown && distances[childMsgId][0] > depth)) {
|
||||||
|
|
||||||
if(distances[childMsgId] === null) {
|
if(distances[childMsgId] === null) {
|
||||||
if(i > 0){
|
if(i > 0){
|
||||||
yPos++;
|
yPos++;
|
||||||
}
|
}
|
||||||
i++;
|
i++;
|
||||||
|
|
||||||
console.log('set for id', childMsgId, goingDown, depth, yPos);
|
console.log('set for id', childMsgId, goingDown, depth, yPos);
|
||||||
distances[childMsgId] = [depth, yPos];
|
distances[childMsgId] = [depth, yPos];
|
||||||
|
|
||||||
|
@ -2106,7 +2106,7 @@ class Graph {
|
||||||
if(distances[childMsgId] === null) {
|
if(distances[childMsgId] === null) {
|
||||||
distances[childMsgId] = [depth, yPos];
|
distances[childMsgId] = [depth, yPos];
|
||||||
}
|
}
|
||||||
|
|
||||||
// console.log('a', depth);
|
// console.log('a', depth);
|
||||||
yPos = traverseMsg(childMsgId, depth - 1, goingDown, yPos);
|
yPos = traverseMsg(childMsgId, depth - 1, goingDown, yPos);
|
||||||
} else {
|
} else {
|
||||||
|
@ -2114,7 +2114,7 @@ class Graph {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if( i == 0 && y == 1) {
|
// if( i == 0 && y == 1) {
|
||||||
// // we reached an item that branches back into the tree
|
// // we reached an item that branches back into the tree
|
||||||
// return yPos -1;
|
// return yPos -1;
|
||||||
|
@ -2122,7 +2122,7 @@ class Graph {
|
||||||
// console.log('yPos',msgId,yPos);
|
// console.log('yPos',msgId,yPos);
|
||||||
return yPos;
|
return yPos;
|
||||||
}
|
}
|
||||||
|
|
||||||
let yPos = 0;
|
let yPos = 0;
|
||||||
console.time('step1');
|
console.time('step1');
|
||||||
for(let startMsg of starts) {
|
for(let startMsg of starts) {
|
||||||
|
@ -2148,7 +2148,7 @@ class Graph {
|
||||||
console.timeEnd('polish: '+ msgId);
|
console.timeEnd('polish: '+ msgId);
|
||||||
}
|
}
|
||||||
console.timeEnd('step2');
|
console.timeEnd('step2');
|
||||||
|
|
||||||
// let additionalsDepth = 0;
|
// let additionalsDepth = 0;
|
||||||
//// now the secondary strands:
|
//// now the secondary strands:
|
||||||
// for(let msgId in distances) {
|
// for(let msgId in distances) {
|
||||||
|
@ -2158,11 +2158,11 @@ class Graph {
|
||||||
// }
|
// }
|
||||||
// distances[msgId] = additionalsDepth;
|
// distances[msgId] = additionalsDepth;
|
||||||
// traverseMsg(msgId, additionalsDepth+1, true);
|
// traverseMsg(msgId, additionalsDepth+1, true);
|
||||||
//
|
//
|
||||||
// }
|
// }
|
||||||
console.timeEnd("calculateDistancesFromStart");
|
console.timeEnd("calculateDistancesFromStart");
|
||||||
return distances;
|
return distances;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
|
|
Loading…
Reference in a new issue