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):
|
||||
CORS_ORIGINS = ['localhost']
|
||||
connections = set()
|
||||
|
||||
|
||||
def check_origin(self, origin):
|
||||
parsed_origin = urlparse(origin)
|
||||
# parsed_origin.netloc.lower() gives localhost:3333
|
||||
|
@ -79,7 +79,7 @@ def getWebSocketHandler(central_command):
|
|||
logger.exception(e)
|
||||
|
||||
def send(self, message):
|
||||
# Possible useless method: use self.write_message()
|
||||
# Possible useless method: use self.write_message()
|
||||
j = json.dumps(message)
|
||||
self.write_message(j)
|
||||
|
||||
|
@ -100,40 +100,40 @@ def getWebSocketHandler(central_command):
|
|||
def msgInit(self):
|
||||
msg = self.getStatusMsg()
|
||||
self.send(msg)
|
||||
|
||||
|
||||
def msgBlock(self, hv_id):
|
||||
central_command.hugveys[hv_id].eventQueue.put_nowait({'event': 'block'})
|
||||
|
||||
|
||||
def msgUnblock(self, hv_id):
|
||||
central_command.hugveys[hv_id].eventQueue.put_nowait({'event': 'unblock'})
|
||||
|
||||
|
||||
def msgResume(self, hv_id):
|
||||
central_command.hugveys[hv_id].eventQueue.put_nowait({'event': 'resume'})
|
||||
|
||||
|
||||
def msgPause(self, hv_id):
|
||||
central_command.hugveys[hv_id].eventQueue.put_nowait({'event': 'pause'})
|
||||
|
||||
|
||||
def msgRestart(self, hv_id):
|
||||
central_command.hugveys[hv_id].eventQueue.put_nowait({'event': 'restart'})
|
||||
|
||||
|
||||
def msgFinish(self, hv_id):
|
||||
central_command.hugveys[hv_id].eventQueue.put_nowait({'event': 'finish'})
|
||||
|
||||
|
||||
def msgChangeLanguage(self, hv_id, lang_code):
|
||||
central_command.hugveys[hv_id].eventQueue.put_nowait({'event': 'change_language', 'lang_code': lang_code})
|
||||
|
||||
|
||||
def msgChangeLightId(self, hv_id, lightId):
|
||||
central_command.hugveys[hv_id].eventQueue.put_nowait({'event': 'change_light', 'light_id': lightId})
|
||||
|
||||
|
||||
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)})
|
||||
|
||||
|
||||
@classmethod
|
||||
def write_to_clients(wsHandlerClass, msg):
|
||||
if msg is None:
|
||||
logger.critical("Tried to send 'none' to Panopticon")
|
||||
return
|
||||
|
||||
|
||||
for client in wsHandlerClass.connections:
|
||||
client.write_message(msg)
|
||||
|
||||
|
@ -143,7 +143,7 @@ class NonCachingStaticFileHandler(tornado.web.StaticFileHandler):
|
|||
def set_extra_headers(self, path):
|
||||
# Disable cache
|
||||
self.set_header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
|
||||
|
||||
|
||||
def getUploadHandler(central_command):
|
||||
class UploadHandler(tornado.web.RequestHandler):
|
||||
def set_default_headers(self):
|
||||
|
@ -160,12 +160,12 @@ def getUploadHandler(central_command):
|
|||
logger.info('upload')
|
||||
langCode = self.get_argument("language")
|
||||
langFile = os.path.join(central_command.config['web']['files_dir'] , central_command.languageFiles[langCode])
|
||||
|
||||
|
||||
storyData = json.loads(self.request.files['json'][0]['body'])
|
||||
# print(json.dumps(storyData))
|
||||
# self.finish()
|
||||
# return
|
||||
|
||||
|
||||
if 'audio' in self.request.files:
|
||||
msgId = self.get_argument("message_id")
|
||||
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']):
|
||||
logger.info(f"Remove previous file {storyData[i]['audio']['file']} ({storyData[i]['audio']['original_name']})")
|
||||
os.unlink(storyData[i]['audio']['file'])
|
||||
|
||||
|
||||
storyData[i]['audio'] = {
|
||||
'file': audioFilename,
|
||||
'original_name': original_fname
|
||||
|
@ -188,17 +188,17 @@ def getUploadHandler(central_command):
|
|||
logger.info(f'Save {original_fname} to {audioFilename}')
|
||||
fp.write(audioFile['body'])
|
||||
break
|
||||
|
||||
|
||||
# logger.info(os.path.abspath(langFile))
|
||||
langFile = os.path.abspath(langFile)
|
||||
with open(langFile, 'w') as json_fp:
|
||||
logger.info(f'Save story to {langFile} {json_fp}')
|
||||
json.dump(storyData, json_fp, indent=2)
|
||||
|
||||
|
||||
# Reload language files for new instances
|
||||
central_command.loadLanguages()
|
||||
self.finish()
|
||||
|
||||
|
||||
return UploadHandler
|
||||
|
||||
def getVoiceHandler(voiceStorage):
|
||||
|
@ -212,7 +212,7 @@ def getVoiceHandler(voiceStorage):
|
|||
fn = await voiceStorage.requestFile(lang_code, text, isVariable)
|
||||
if not fn:
|
||||
raise Exception(f"No Filename for text: {text}")
|
||||
|
||||
|
||||
if int(self.get_argument('filename')) == 1:
|
||||
self.set_header("Content-Type","text/plain")
|
||||
self.write(fn)
|
||||
|
@ -221,25 +221,30 @@ def getVoiceHandler(voiceStorage):
|
|||
with open(fn, 'rb') as fp:
|
||||
self.write(fp.read())
|
||||
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):
|
||||
def __init__(self, central_command, config, voiceStorage):
|
||||
self.command = central_command
|
||||
self.config = config
|
||||
|
||||
|
||||
self.voiceStorage = voiceStorage
|
||||
|
||||
|
||||
self.wsHandler = getWebSocketHandler(self.command)
|
||||
|
||||
|
||||
self.application = tornado.web.Application([
|
||||
(r"/ws(.*)", self.wsHandler),
|
||||
(r"/local/(.*)", NonCachingStaticFileHandler,
|
||||
{"path": config['web']['files_dir']}),
|
||||
(r"/upload", getUploadHandler(self.command)),
|
||||
(r"/voice", getVoiceHandler(self.voiceStorage)),
|
||||
(r"/(.*)", tornado.web.StaticFileHandler,
|
||||
(r"/(.*)", StaticFileWithHeaderHandler,
|
||||
{"path": web_dir, "default_filename": 'index.html'}),
|
||||
], debug=True)
|
||||
|
||||
|
@ -251,17 +256,17 @@ class Panopticon(object):
|
|||
asyncio.set_event_loop(evt_loop)
|
||||
|
||||
self.loop = tornado.ioloop.IOLoop.current()
|
||||
|
||||
|
||||
thread = threading.Thread(
|
||||
target=self.broadcastLoggingQueueToWs, kwargs={'wsHandler': self.wsHandler, 'q': self.command.logQueue}, name=f"panopticon/logws")
|
||||
thread.start()
|
||||
|
||||
|
||||
logger.info(f"Start Panopticon on http://localhost:{self.config['web']['port']}")
|
||||
self.loop.start()
|
||||
|
||||
def stop(self):
|
||||
self.loop.stop()
|
||||
|
||||
|
||||
def broadcastLoggingQueueToWs(self, wsHandler, q: Queue):
|
||||
while True:
|
||||
record = q.get()
|
||||
|
@ -278,4 +283,3 @@ class Panopticon(object):
|
|||
j = json.dumps(msg)
|
||||
logger.debug(j)
|
||||
self.loop.add_callback(wsHandler.write_to_clients, j)
|
||||
|
|
@ -40,8 +40,8 @@ class Utterance(object):
|
|||
|
||||
def isFinished(self):
|
||||
return self.endTime is not None
|
||||
|
||||
|
||||
|
||||
|
||||
def __getstate__(self):
|
||||
# print(f'get utterance {self}')
|
||||
state = self.__dict__.copy()
|
||||
|
@ -68,7 +68,7 @@ class Message(object):
|
|||
self.parseForVariables()
|
||||
self.uuid = None # Have a unique id each time the message is played back.
|
||||
self.color = None
|
||||
|
||||
|
||||
def __getstate__(self):
|
||||
# Copy the object's state from self.__dict__ which contains
|
||||
# all our instance attributes. Always use the dict.copy()
|
||||
|
@ -78,7 +78,7 @@ class Message(object):
|
|||
# Remove the unpicklable entries.
|
||||
del state['filenameFetchLock']
|
||||
return state
|
||||
|
||||
|
||||
def __setstate__(self, state):
|
||||
self.__dict__.update(state)
|
||||
self.filenameFetchLock = asyncio.Lock()
|
||||
|
@ -103,9 +103,9 @@ class Message(object):
|
|||
if not 'vol' in msg.params:
|
||||
# prevent clipping on some Lyrebird tracks
|
||||
msg.params['vol'] = .8
|
||||
|
||||
|
||||
msg.params['vol'] = float(msg.params['vol'])
|
||||
|
||||
|
||||
return msg
|
||||
|
||||
def parseForVariables(self):
|
||||
|
@ -215,8 +215,8 @@ class Reply(object):
|
|||
self.forMessage = None
|
||||
self.utterances = []
|
||||
self.setForMessage(message)
|
||||
|
||||
|
||||
|
||||
|
||||
def __getstate__(self):
|
||||
# print(f'get reply {self}')
|
||||
state = self.__dict__.copy()
|
||||
|
@ -297,8 +297,8 @@ class Condition(object):
|
|||
self.logInfo = None
|
||||
self.originalJsonString = None
|
||||
self.usedContainsDuration = None
|
||||
|
||||
|
||||
|
||||
|
||||
def __getstate__(self):
|
||||
# print(f'get condition {self.id}')
|
||||
state = self.__dict__.copy()
|
||||
|
@ -500,8 +500,8 @@ class Direction(object):
|
|||
self.conditions = []
|
||||
self.conditionMet = None
|
||||
self.isDiversionReturn = False
|
||||
|
||||
|
||||
|
||||
|
||||
def __getstate__(self):
|
||||
# print(f'get direction {self.id}')
|
||||
state = self.__dict__.copy()
|
||||
|
@ -568,8 +568,8 @@ class Diversion(object):
|
|||
|
||||
if not self.method:
|
||||
raise Exception("No valid type given for diversion")
|
||||
|
||||
|
||||
|
||||
|
||||
def __getstate__(self):
|
||||
# print(f'get diversion {self.id}')
|
||||
state = self.__dict__.copy()
|
||||
|
@ -628,7 +628,7 @@ class Diversion(object):
|
|||
}]
|
||||
"""
|
||||
self.counter +=1
|
||||
|
||||
# story.logger.warn(f"CREATING DIRECTIONS FOR {startMsg.id}")
|
||||
finishMessageIds = story.getFinishesForMsg(startMsg)
|
||||
finalTimeoutDuration = timeoutDuration
|
||||
finalContainsDurations = replyContainsDurations
|
||||
|
@ -647,6 +647,7 @@ class Diversion(object):
|
|||
finalContainsDurations = json.loads(condition.originalJsonString)['vars']['delays']
|
||||
|
||||
i = 0
|
||||
# story.logger.warn(f"FINISHES: {finishMessageIds}")
|
||||
for msgId in finishMessageIds:
|
||||
# Some very ugly hack to add a direction & condition
|
||||
i+=1
|
||||
|
@ -693,6 +694,7 @@ class Diversion(object):
|
|||
story.logger.info(f"Created direction: {direction.id} {condition.id} with timeout {finalTimeoutDuration}s")
|
||||
story.add(condition)
|
||||
story.add(direction)
|
||||
# story.logger.warn(f"ADDED DIRECTION {direction.id}")
|
||||
|
||||
|
||||
|
||||
|
@ -944,7 +946,7 @@ class Configuration(object):
|
|||
id = 'configuration'
|
||||
volume = 1 # Volume multiplier for 'play' command
|
||||
nothing_text = "nothing" # When variable is not set, but used in sentence, replace it with this word.
|
||||
|
||||
|
||||
@classmethod
|
||||
def initFromJson(configClass, data, story):
|
||||
config = Configuration()
|
||||
|
@ -1007,22 +1009,22 @@ class Stopwatch(object):
|
|||
def clearMark(self, name):
|
||||
if name in self.marks:
|
||||
self.marks.pop(name)
|
||||
|
||||
|
||||
def __getstate__(self):
|
||||
# print(f'get stopwatch')
|
||||
state = self.__dict__.copy()
|
||||
state['isRunning'] = self.isRunning.is_set()
|
||||
return state
|
||||
|
||||
|
||||
def __setstate__(self, state):
|
||||
self.__dict__.update(state)
|
||||
|
||||
|
||||
self.isRunning = asyncio.Event()
|
||||
if 'isRunning' in state and state['isRunning']:
|
||||
self.isRunning.set()
|
||||
else:
|
||||
self.isRunning.clear()
|
||||
|
||||
|
||||
|
||||
class StoryState(object):
|
||||
"""
|
||||
|
@ -1065,7 +1067,7 @@ class StoryState(object):
|
|||
|
||||
def __init__(self):
|
||||
pass
|
||||
#
|
||||
#
|
||||
|
||||
class Story(object):
|
||||
"""Story represents and manages a story/narrative flow"""
|
||||
|
@ -1485,7 +1487,7 @@ class Story(object):
|
|||
|
||||
# TODO create timer event
|
||||
# self.commands.append({'msg':'TEST!'})
|
||||
|
||||
|
||||
# Test stability of Central Command with deliberate crash
|
||||
# if self.timer.getElapsed() > 10:
|
||||
# raise Exception("Test exception")
|
||||
|
@ -1553,10 +1555,10 @@ class Story(object):
|
|||
self.logger.critical(f"error: crash when reading wave file: {fn}")
|
||||
self.logger.exception(e)
|
||||
duration = 10 # some default duration to have something to fall back to
|
||||
|
||||
|
||||
params = message.getParams().copy()
|
||||
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.sendCommand({
|
||||
'action': 'play',
|
||||
|
@ -1621,7 +1623,7 @@ class Story(object):
|
|||
self.isRunning = True
|
||||
if not self.lastMsgFinishTime and self.currentMessage:
|
||||
await self.setCurrentMessage(self.currentMessage)
|
||||
|
||||
|
||||
await self._renderer()
|
||||
|
||||
def isFinished(self):
|
||||
|
@ -1649,16 +1651,16 @@ class Story(object):
|
|||
self.timer.pause()
|
||||
|
||||
def calculateFinishesForMsg(self, msgId, depth = 0, checked = []):
|
||||
if msgId in checked:
|
||||
return []
|
||||
|
||||
checked.append(msgId)
|
||||
|
||||
# if msgId in checked:
|
||||
# return []
|
||||
#
|
||||
# checked.append(msgId)
|
||||
|
||||
if not msgId in self.directionsPerMsg or len(self.directionsPerMsg[msgId]) < 1:
|
||||
# is finish
|
||||
return [msgId]
|
||||
|
||||
if depth > 200:
|
||||
if depth > 100:
|
||||
return []
|
||||
|
||||
finishes = []
|
||||
|
@ -1691,6 +1693,7 @@ class Story(object):
|
|||
|
||||
returns message ids
|
||||
"""
|
||||
print(msg.id, self.strands)
|
||||
if msg.id in self.strands:
|
||||
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?
|
||||
return self.directionsPerMsg[msg.id][0]
|
||||
|
||||
|
||||
@classmethod
|
||||
def getStateDir(self):
|
||||
return "/tmp"
|
||||
# day = time.strftime("%Y%m%d")
|
||||
# t = time.strftime("%H:%M:%S")
|
||||
#
|
||||
#
|
||||
# self.out_folder = os.path.join(self.main_folder, day, f"{self.hv_id}", t)
|
||||
# if not os.path.exists(self.out_folder):
|
||||
# self.logger.debug(f"Create directory {self.out_folder}")
|
||||
# self.target_folder = os.makedirs(self.out_folder, exist_ok=True)
|
||||
|
||||
@classmethod
|
||||
|
||||
@classmethod
|
||||
def getStateFilename(cls, hv_id):
|
||||
return os.path.join(cls.getStateDir(), f"hugvey{hv_id}")
|
||||
|
||||
|
||||
def storeState(self):
|
||||
# TODO: stop stopwatch
|
||||
fn = self.getStateFilename(self.hugvey.id)
|
||||
|
@ -1735,49 +1738,48 @@ class Story(object):
|
|||
pickle.dump(self, fp)
|
||||
# write atomic to disk: flush, close, rename
|
||||
fp.flush()
|
||||
os.fsync(fp.fileno())
|
||||
|
||||
os.fsync(fp.fileno())
|
||||
|
||||
os.rename(tmpfn, fn)
|
||||
self.logger.debug(f"saved state to {fn}")
|
||||
|
||||
|
||||
def hasSavedState(self):
|
||||
return self.hugveyHasSavedState(self.hugvey.id)
|
||||
|
||||
|
||||
@classmethod
|
||||
def hugveyHasSavedState(cls, hv_id):
|
||||
return os.path.exists(cls.getStateFilename(hv_id))
|
||||
|
||||
|
||||
@classmethod
|
||||
def loadStoryFromState(cls, hugvey_state):
|
||||
# restart stopwatch
|
||||
with open(cls.getStateFilename(hugvey_state.id), 'rb') as fp:
|
||||
story = pickle.load(fp)
|
||||
|
||||
|
||||
story.hugvey = hugvey_state
|
||||
story.logger = mainLogger.getChild(f"{story.hugvey.id}").getChild("story")
|
||||
return story
|
||||
# TODO: take running state etc.
|
||||
|
||||
|
||||
@classmethod
|
||||
def clearSavedState(cls, hv_id):
|
||||
fn = cls.getStateFilename(hv_id)
|
||||
if os.path.exists(fn):
|
||||
os.unlink(fn)
|
||||
mainLogger.info(f"Removed state: {fn}")
|
||||
#
|
||||
#
|
||||
def __getstate__(self):
|
||||
# Copy the object's state from self.__dict__ which contains
|
||||
# all our instance attributes. Always use the dict.copy()
|
||||
# method to avoid modifying the original state.
|
||||
state = self.__dict__.copy()
|
||||
|
||||
|
||||
# Remove the unpicklable entries.
|
||||
del state['hugvey']
|
||||
del state['logger']
|
||||
# del state['isRunning']
|
||||
|
||||
|
||||
return state
|
||||
|
||||
|
||||
def __setstate__(self, 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
|
||||
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
|
||||
cp installation/supervisord.conf /etc/supervisor/supervisord.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 227 readsf~;
|
||||
#X obj 209 209 bng 15 250 50 0 empty empty empty 17 7 0 10 -262144
|
||||
-1 -1;
|
||||
#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 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;
|
||||
#X msg 355 464 disconnect;
|
||||
#X obj 208 393 list prepend send;
|
||||
#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 obj 227 83 bng 15 250 50 0 empty empty empty 17 7 0 10 -262144 -1
|
||||
-1;
|
||||
#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
|
||||
-1;
|
||||
#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;
|
||||
#X text 309 61 !STOP!;
|
||||
#X text 422 60 Playing indicator;
|
||||
#X msg 261 118 1;
|
||||
#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 1;
|
||||
#X connect 1 1 2 0;
|
||||
#X connect 2 0 4 0;
|
||||
#X connect 2 0 15 0;
|
||||
#X connect 4 0 1 0;
|
||||
#X connect 5 0 6 0;
|
||||
#X connect 7 0 5 0;
|
||||
#X connect 8 0 9 0;
|
||||
#X connect 9 0 5 0;
|
||||
#X connect 10 0 5 0;
|
||||
#X connect 11 0 8 0;
|
||||
#X connect 12 0 1 0;
|
||||
#X connect 13 0 10 0;
|
||||
#X connect 13 0 2 0;
|
||||
#X connect 13 0 21 0;
|
||||
#X connect 15 0 11 0;
|
||||
#X connect 16 0 12 0;
|
||||
#X connect 16 0 7 0;
|
||||
#X connect 16 0 22 0;
|
||||
#X connect 21 0 18 0;
|
||||
#X connect 22 0 18 0;
|
||||
#X connect 2 0 22 0;
|
||||
#X connect 2 0 12 0;
|
||||
#X connect 4 0 5 0;
|
||||
#X connect 6 0 4 0;
|
||||
#X connect 7 0 8 0;
|
||||
#X connect 8 0 4 0;
|
||||
#X connect 9 0 1 0;
|
||||
#X connect 10 0 21 0;
|
||||
#X connect 10 0 2 0;
|
||||
#X connect 10 0 18 0;
|
||||
#X connect 12 0 20 0;
|
||||
#X connect 13 0 9 0;
|
||||
#X connect 13 0 6 0;
|
||||
#X connect 13 0 19 0;
|
||||
#X connect 18 0 15 0;
|
||||
#X connect 19 0 15 0;
|
||||
#X connect 20 0 7 0;
|
||||
#X connect 21 0 4 0;
|
||||
#X connect 22 0 1 0;
|
||||
|
|
|
@ -28,7 +28,7 @@ class Panopticon {
|
|||
},
|
||||
loadNarrative: function( code, file ) {
|
||||
panopticon.hugveys.selectedId = null;
|
||||
|
||||
|
||||
if(panopticon.hasGraph) {
|
||||
return panopticon.loadNarrative( code, file );
|
||||
}
|
||||
|
@ -79,16 +79,16 @@ class Panopticon {
|
|||
|
||||
|
||||
this.socket = new ReconnectingWebSocket( "ws://localhost:8888/ws", null, { debug: false, reconnectInterval: 3000 } );
|
||||
|
||||
|
||||
if(this.hasGraph) {
|
||||
this.graph = new Graph();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
this.socket.addEventListener( 'open', ( e ) => {
|
||||
this.send( { action: 'init' } );
|
||||
} );
|
||||
|
||||
|
||||
// request close before unloading
|
||||
window.addEventListener('beforeunload', function(){
|
||||
panopticon.socket.close();
|
||||
|
@ -102,7 +102,7 @@ class Panopticon {
|
|||
console.log("Websocket connected")
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
let msg = JSON.parse( e.data );
|
||||
if ( typeof msg['alert'] !== 'undefined' ) {
|
||||
alert( msg['alert'] );
|
||||
|
@ -112,9 +112,9 @@ class Panopticon {
|
|||
console.error( "not a valid message: " + e.data );
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
console.debug(msg);
|
||||
|
||||
|
||||
switch ( msg['action'] ) {
|
||||
|
||||
case 'status':
|
||||
|
@ -134,28 +134,28 @@ class Panopticon {
|
|||
}
|
||||
} );
|
||||
}
|
||||
|
||||
|
||||
updateSelectedHugvey() {
|
||||
let hv = null;
|
||||
|
||||
|
||||
if(this.hugveys.selectedId) {
|
||||
hv = this.getHugvey(this.hugveys.selectedId);
|
||||
|
||||
|
||||
if(this.hasGraph) {
|
||||
if(hv.language && this.graph.language_code != hv.language) {
|
||||
this.loadNarrative(hv.language);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// let varEl = document.getElementById("variables");
|
||||
// varEl.innerHTML = "";
|
||||
}
|
||||
|
||||
|
||||
if(this.hasGraph) {
|
||||
this.graph.updateHugveyStatus(hv);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
getHugvey(id) {
|
||||
for(let hv of this.hugveys.hugveys) {
|
||||
if(hv.id == id) {
|
||||
|
@ -206,7 +206,7 @@ class Panopticon {
|
|||
let graph = this.graph;
|
||||
req.addEventListener( "load", function( e ) {
|
||||
graph.loadData( JSON.parse( this.response ), code );
|
||||
// console.log(, e);
|
||||
// console.log(, e);
|
||||
} );
|
||||
req.open( "GET", "/local/" + file );
|
||||
req.send();
|
||||
|
@ -236,7 +236,7 @@ class Panopticon {
|
|||
console.log("Light", hv_id, light_id);
|
||||
this.send( { action: 'change_light', hugvey: hv_id, light_id: light_id } );
|
||||
}
|
||||
|
||||
|
||||
playFromSelected(msg_id, reloadStory) {
|
||||
if(!this.hugveys.selectedId) {
|
||||
alert('No hugvey selected');
|
||||
|
@ -337,17 +337,17 @@ class Graph {
|
|||
// used eg. after a condition creation.
|
||||
this.showMsg( this.selectedMsg );
|
||||
}
|
||||
|
||||
|
||||
getAudioUrlForMsg(msg) {
|
||||
let isVariable = msg['text'].includes('$') ? '1' : '0';
|
||||
let lang = panopticon.graph.language_code;
|
||||
return `http://localhost:8888/voice?text=${encodeURIComponent(msg['text'])}&variable=${isVariable}&lang=${lang}&filename=0`;
|
||||
}
|
||||
|
||||
|
||||
getConfig() {
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
getNumericId(prefix) {
|
||||
let id, i = 0;
|
||||
let hasId= function(a, id) {
|
||||
|
@ -362,10 +362,10 @@ class Graph {
|
|||
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}#`),
|
||||
|
@ -373,7 +373,7 @@ class Graph {
|
|||
'type': type,
|
||||
'params': {}
|
||||
}
|
||||
|
||||
|
||||
if(type == 'no_response') {
|
||||
div['params']['consecutiveSilences'] = 3;
|
||||
div['params']['timesOccured'] = 0;
|
||||
|
@ -399,33 +399,33 @@ class Graph {
|
|||
div['params']['msgId'] = "";
|
||||
}
|
||||
else if(type == 'repeat') {
|
||||
div['params']['regex'] = "can you repeat that\\?";
|
||||
div['params']['regex'] = "can you repeat that\\?";
|
||||
} else {
|
||||
console.log("invalid type", type);
|
||||
alert('invalid type for diversion');
|
||||
}
|
||||
|
||||
|
||||
if(type != 'repeat' && type != 'interrupt') {
|
||||
div['params']['notAfterMsgId'] = "";
|
||||
}
|
||||
|
||||
|
||||
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 = [], divsReplyContains = [], divsTimeouts = [], divsInterrupts = [];
|
||||
for(let div of this.diversions) {
|
||||
|
||||
|
@ -448,7 +448,7 @@ class Graph {
|
|||
}}, ...notMsgOptions)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
if(div['type'] == 'no_response') {
|
||||
let returnAttrs = {
|
||||
'type': 'checkbox',
|
||||
|
@ -468,7 +468,7 @@ class Graph {
|
|||
}
|
||||
msgOptions.push(crel('option', optionParams , startMsg['@id']));
|
||||
}
|
||||
|
||||
|
||||
divsNoResponse.push(crel(
|
||||
'div', {
|
||||
'class': 'diversion',
|
||||
|
@ -539,7 +539,7 @@ class Graph {
|
|||
}
|
||||
msgOptions.push(crel('option', optionParams , startMsg['@id']));
|
||||
}
|
||||
|
||||
|
||||
divsReplyContains.push(crel(
|
||||
'div', {
|
||||
'class': 'diversion',
|
||||
|
@ -606,7 +606,7 @@ class Graph {
|
|||
),
|
||||
notAfterMsgIdEl
|
||||
));
|
||||
}
|
||||
}
|
||||
if(div['type'] == 'timeout') {
|
||||
let returnAttrs = {
|
||||
'type': 'checkbox',
|
||||
|
@ -617,7 +617,7 @@ class Graph {
|
|||
if(div['params']['returnAfterStrand']) {
|
||||
returnAttrs['checked'] = 'checked';
|
||||
}
|
||||
|
||||
|
||||
let totalOrLocalAttrs = {
|
||||
'type': 'checkbox',
|
||||
'on': {
|
||||
|
@ -627,7 +627,7 @@ class Graph {
|
|||
if(div['params']['fromLastMessage']) {
|
||||
totalOrLocalAttrs['checked'] = 'checked';
|
||||
}
|
||||
|
||||
|
||||
let msgOptions = [crel('option',"")];
|
||||
let starts = this.messages.filter( m => m.hasOwnProperty('start') && m['start'] == true);
|
||||
for(let startMsg of starts) {
|
||||
|
@ -637,7 +637,7 @@ class Graph {
|
|||
}
|
||||
msgOptions.push(crel('option', optionParams , startMsg['@id']));
|
||||
}
|
||||
|
||||
|
||||
divsTimeouts.push(crel(
|
||||
'div', {
|
||||
'class': 'diversion',
|
||||
|
@ -734,7 +734,7 @@ class Graph {
|
|||
}
|
||||
msgOptions.push(crel('option', optionParams , startMsg['@id']));
|
||||
}
|
||||
|
||||
|
||||
divsInterrupts.push(crel(
|
||||
'div', {'class': 'diversion'},
|
||||
crel('h3', div['@id']),
|
||||
|
@ -753,9 +753,9 @@ class Graph {
|
|||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
console.log(divsReplyContains, divsNoResponse, divsRepeat, divsTimeouts, divsInterrupts);
|
||||
|
||||
|
||||
let divEl = crel(
|
||||
'div',
|
||||
{
|
||||
|
@ -797,7 +797,7 @@ class Graph {
|
|||
'on': {
|
||||
'click': (e) => this.createDiversion('repeat')
|
||||
}
|
||||
},
|
||||
},
|
||||
'New case for repeat'
|
||||
)
|
||||
),
|
||||
|
@ -810,7 +810,7 @@ class Graph {
|
|||
'on': {
|
||||
'click': (e) => this.createDiversion('timeout')
|
||||
}
|
||||
},
|
||||
},
|
||||
'New case for timeout'
|
||||
)
|
||||
)
|
||||
|
@ -824,12 +824,12 @@ class Graph {
|
|||
// 'on': {
|
||||
// 'click': (e) => this.createDiversion('interrupt')
|
||||
// }
|
||||
// },
|
||||
// },
|
||||
// 'New case for Interrupt'
|
||||
// )
|
||||
// )
|
||||
);
|
||||
|
||||
|
||||
msgEl.appendChild(divEl);
|
||||
}
|
||||
|
||||
|
@ -904,19 +904,19 @@ class Graph {
|
|||
})
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
document.getElementById("interface").appendChild(configEl);
|
||||
}
|
||||
|
||||
showMsg( msg ) {
|
||||
let msgEl = document.getElementById( 'msg' );
|
||||
msgEl.innerHTML = "";
|
||||
|
||||
|
||||
if(msg == null){
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
let startAttributes = {
|
||||
'name': msg['@id'] + '-start',
|
||||
// 'readonly': 'readonly',
|
||||
|
@ -939,7 +939,7 @@ class Graph {
|
|||
if ( msg['beginning'] == true ) {
|
||||
beginningAttributes['checked'] = 'checked';
|
||||
}
|
||||
|
||||
|
||||
// chapter marker:
|
||||
let chapterAttributes = {
|
||||
'name': msg['@id'] + '-chapterStart',
|
||||
|
@ -952,14 +952,14 @@ class Graph {
|
|||
if ( typeof msg['chapterStart'] !== 'undefined' && msg['chapterStart'] == true ) {
|
||||
chapterAttributes['checked'] = 'checked';
|
||||
}
|
||||
|
||||
|
||||
let params = {};
|
||||
if(msg.hasOwnProperty('params')) {
|
||||
params = msg['params'];
|
||||
} else {
|
||||
msg['params'] = {};
|
||||
}
|
||||
|
||||
|
||||
let audioSrcEl = crel('source', {'src': msg['audio'] ? msg['audio']['file'] : this.getAudioUrlForMsg(msg)});
|
||||
let audioSpan = crel(
|
||||
'span',
|
||||
|
@ -1105,7 +1105,7 @@ class Graph {
|
|||
}
|
||||
} )
|
||||
),
|
||||
|
||||
|
||||
// color for beter overview
|
||||
|
||||
crel( 'label',
|
||||
|
@ -1123,9 +1123,9 @@ class Graph {
|
|||
)
|
||||
);
|
||||
msgEl.appendChild( msgInfoEl );
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if(panopticon.hugveys.selectedId) {
|
||||
msgEl.appendChild(crel(
|
||||
'div',
|
||||
|
@ -1161,9 +1161,9 @@ class Graph {
|
|||
"Continue on #" + panopticon.hugveys.selectedId
|
||||
)
|
||||
));
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// let directionHEl = document.createElement('h2');
|
||||
// directionHEl.innerHTML = "Directions";
|
||||
|
@ -1220,13 +1220,13 @@ class Graph {
|
|||
'on': {
|
||||
'click': ( e ) => {
|
||||
if(confirm("Do you want to remove this direction and its conditions?")) {
|
||||
g.rmDirection( direction );
|
||||
g.rmDirection( direction );
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 'disconnect')
|
||||
);
|
||||
|
||||
|
||||
for ( let conditionId of direction['conditions'] ) {
|
||||
let condition = this.getNodeById( conditionId );
|
||||
directionEl.appendChild( this.getEditConditionFormEl( condition, direction ) );
|
||||
|
@ -1246,7 +1246,7 @@ class Graph {
|
|||
'click': ( e ) => {
|
||||
if(confirm("Do you want to remove this condition?")) {
|
||||
// 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 );
|
||||
conditionEl.appendChild( labelLabel );
|
||||
|
||||
|
||||
|
||||
// for ( let v in condition['vars'] ) {
|
||||
// let varLabel = document.createElement( 'label' );
|
||||
|
@ -1309,14 +1309,14 @@ class Graph {
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
getConditionInputsForType( type, conditionId, values ) {
|
||||
let inputs = [];
|
||||
let vars = this.getConditionTypes()[type];
|
||||
for ( let v in vars ) {
|
||||
let attr = vars[v];
|
||||
let inputType = attr.hasOwnProperty('tag') ? attr['tag'] : 'input';
|
||||
|
||||
|
||||
attr['name'] = typeof conditionId == 'undefined' ? v : `${conditionId}-vars.${v}`;
|
||||
if(typeof values != 'undefined') {
|
||||
let value = this._getValueForPath(v, values);
|
||||
|
@ -1326,12 +1326,12 @@ class Graph {
|
|||
}
|
||||
attr['value'] = typeof value == 'undefined' ? "": value;
|
||||
attr['on'] = {
|
||||
'change': this.getEditEventListener()
|
||||
'change': this.getEditEventListener()
|
||||
} ;
|
||||
} else {
|
||||
// console.log(attr);
|
||||
}
|
||||
|
||||
|
||||
inputs.push(
|
||||
crel( 'label',
|
||||
crel( 'span', {
|
||||
|
@ -1352,7 +1352,7 @@ class Graph {
|
|||
conditionForm.appendChild(i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
_getValueForPath(path, vars) {
|
||||
path = path.split( '.' ); // use vars.test to set ['vars']['test'] = value
|
||||
let v = vars;
|
||||
|
@ -1372,7 +1372,7 @@ class Graph {
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Save an array path (string) with a value to an object. Used to turn
|
||||
* strings into nested arrays
|
||||
|
@ -1421,7 +1421,7 @@ 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);
|
||||
|
@ -1432,13 +1432,13 @@ class Graph {
|
|||
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;
|
||||
|
||||
if(pair[1] === 'false') pair[1] = false;
|
||||
|
||||
vars = g._formPathToVars(pair[0], pair[1], vars);
|
||||
}
|
||||
// TODO: checkboxes
|
||||
|
@ -1481,7 +1481,7 @@ class Graph {
|
|||
|
||||
return addConditionEl;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* remove condition from the graph or merely from the given direction
|
||||
* @param {any} condition The condition to remove
|
||||
|
@ -1496,7 +1496,7 @@ class Graph {
|
|||
if(pos > -1) {
|
||||
direction['conditions'].splice(pos, 1);
|
||||
}
|
||||
|
||||
|
||||
for(let dir of this.directions) {
|
||||
// console.log('check if condition exists for dir', dir)
|
||||
if(dir['conditions'].indexOf(id) > -1) {
|
||||
|
@ -1547,14 +1547,14 @@ class Graph {
|
|||
"afterrunTime": 0.5,
|
||||
}
|
||||
this.data.push( msg );
|
||||
|
||||
|
||||
console.log("skip or not to skip?", skipRebuild);
|
||||
if(typeof skipRebuild == 'undefined' || !skipRebuild) {
|
||||
this.updateFromData();
|
||||
this.build();
|
||||
this.selectMsg(msg);
|
||||
}
|
||||
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
|
@ -1611,7 +1611,7 @@ class Graph {
|
|||
"conditions": []
|
||||
}
|
||||
this.data.push( dir );
|
||||
|
||||
|
||||
let skipDistances;
|
||||
// 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) {
|
||||
|
@ -1620,11 +1620,11 @@ class Graph {
|
|||
let d = [distance[0] + 1, distance[1]];
|
||||
// create a distance based on source's position
|
||||
// this saves us from running the slow calculateDistancesFromStart
|
||||
this.distances[target['@id']] = d;
|
||||
this.distances[target['@id']] = d;
|
||||
} else {
|
||||
skipDistances = false;
|
||||
}
|
||||
|
||||
|
||||
this.updateFromData(skipDistances);
|
||||
this.build();
|
||||
return dir;
|
||||
|
@ -1639,18 +1639,18 @@ class Graph {
|
|||
this.addMsg();
|
||||
this.build();
|
||||
}
|
||||
|
||||
|
||||
createConnectedMsg(sourceMsg) {
|
||||
console.time('createConnected');
|
||||
console.time("Add");
|
||||
let newMsg = this.addMsg(true); // skipRebuild = true, as addDirection() already rebuilds the graph
|
||||
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
|
||||
this.getNodeById(newMsg['@id']).color = this.getNodeById(sourceMsg['@id']).color
|
||||
}
|
||||
console.timeEnd("Add");
|
||||
|
||||
|
||||
console.time("direction");
|
||||
this.addDirection(sourceMsg, newMsg);
|
||||
console.timeEnd("direction");
|
||||
|
@ -1677,15 +1677,15 @@ class Graph {
|
|||
let graph = this;
|
||||
let el = function( e ) {
|
||||
console.info("Changed", e);
|
||||
let parts = e.srcElement.name.split( '-' );
|
||||
let parts = e.target.name.split( '-' );
|
||||
let field = parts.pop();
|
||||
let id = parts.join('-');
|
||||
let node = graph.getNodeById( id );
|
||||
let path = field.split( '.' ); // use vars.test to set ['vars']['test'] = value
|
||||
var res = node;
|
||||
let value = e.srcElement.value
|
||||
if(e.srcElement.type == 'checkbox') {
|
||||
value = e.srcElement.checked;
|
||||
let value = e.target.value
|
||||
if(e.target.type == 'checkbox') {
|
||||
value = e.target.checked;
|
||||
}
|
||||
for ( var i = 0; i < path.length; i++ ) {
|
||||
if ( i == ( path.length - 1 ) ) {
|
||||
|
@ -1698,7 +1698,7 @@ class Graph {
|
|||
// node[field] = e.srcElement.value;
|
||||
|
||||
graph.build();
|
||||
|
||||
|
||||
if(typeof callback !== 'undefined'){
|
||||
callback();
|
||||
}
|
||||
|
@ -1769,11 +1769,11 @@ class Graph {
|
|||
console.info("Save json", formData );
|
||||
var request = new XMLHttpRequest();
|
||||
request.open( "POST", "http://localhost:8888/upload" );
|
||||
|
||||
|
||||
if(callback) {
|
||||
request.addEventListener( "load", callback);
|
||||
}
|
||||
|
||||
|
||||
request.send( formData );
|
||||
}
|
||||
|
||||
|
@ -1789,13 +1789,13 @@ class Graph {
|
|||
this.directions = this.data.filter(( node ) => node['@type'] == 'Direction' );
|
||||
this.conditions = this.data.filter(( node ) => node['@type'] == 'Condition' );
|
||||
this.diversions = this.data.filter(( node ) => node['@type'] == 'Diversion' );
|
||||
|
||||
|
||||
let configurations = this.data.filter(( node ) => node['@type'] == 'Configuration' );
|
||||
this.configuration = configurations.length > 0 ? configurations[0] : {
|
||||
"@id": "config",
|
||||
"@type": "Configuration"
|
||||
};
|
||||
|
||||
|
||||
document.getElementById('current_lang').innerHTML = "";
|
||||
document.getElementById('current_lang').appendChild(crel('span', {
|
||||
'class': 'flag-icon ' + this.language_code
|
||||
|
@ -1803,7 +1803,7 @@ class Graph {
|
|||
let storyEl = document.getElementById('story');
|
||||
storyEl.classList.remove(... panopticon.languages.map((l) => l['code']))
|
||||
storyEl.classList.add(this.language_code);
|
||||
|
||||
|
||||
if(typeof skipDistances == 'undefined' || !skipDistances) {
|
||||
this.distances = this.calculateDistancesFromStart();
|
||||
}
|
||||
|
@ -1811,7 +1811,7 @@ class Graph {
|
|||
// save state;
|
||||
this.saveState();
|
||||
}
|
||||
|
||||
|
||||
updateHugveyStatus(hv) {
|
||||
let els = document.getElementsByClassName('beenHit');
|
||||
while(els.length > 0) {
|
||||
|
@ -1820,11 +1820,11 @@ class Graph {
|
|||
if(!hv || typeof hv['history'] == 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if(hv['history'].hasOwnProperty('messages')){
|
||||
for(let msg of hv['history']['messages']) {
|
||||
document.getElementById(msg[0]['id']).classList.add('beenHit');
|
||||
}
|
||||
}
|
||||
}
|
||||
if(hv['history'].hasOwnProperty('directions')){
|
||||
for(let msg of hv['history']['directions']) {
|
||||
|
@ -1857,7 +1857,7 @@ class Graph {
|
|||
return fx;
|
||||
}).strength(50))
|
||||
.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]);
|
||||
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);
|
||||
|
@ -1872,7 +1872,7 @@ class Graph {
|
|||
.selectAll( "g" )
|
||||
.data( this.messages, n => n['@id'] )
|
||||
;
|
||||
|
||||
|
||||
|
||||
// Update existing nodes
|
||||
let newNode = node.enter();
|
||||
|
@ -1887,7 +1887,7 @@ class Graph {
|
|||
.attr( 'r', this.nodeSize )
|
||||
// .text(d => d.id)
|
||||
;
|
||||
|
||||
|
||||
let textId = newNodeG.append( "text" ).attr( 'class', 'msg_id' );
|
||||
let textContent = newNodeG.append( "text" ).attr( 'class', 'msg_txt' );
|
||||
let statusIcon = newNodeG.append( "image" )
|
||||
|
@ -1901,7 +1901,7 @@ class Graph {
|
|||
// remove
|
||||
node.exit().remove();
|
||||
node = node.merge( newNodeG );
|
||||
|
||||
|
||||
|
||||
// for all existing nodes:
|
||||
node.attr( 'class', msg => {
|
||||
|
@ -1916,9 +1916,9 @@ class Graph {
|
|||
|
||||
return classes.join( ' ' );
|
||||
} )
|
||||
|
||||
|
||||
.on(".drag", null)
|
||||
.call(
|
||||
.call(
|
||||
d3.drag( this.simulation )
|
||||
.on("start", function(d){
|
||||
if (!d3.event.active) panopticon.graph.simulation.alphaTarget(0.3).restart();
|
||||
|
@ -1935,9 +1935,9 @@ class Graph {
|
|||
d.fy = null;
|
||||
})
|
||||
// .container(document.getElementById('container'))
|
||||
|
||||
|
||||
);
|
||||
|
||||
|
||||
node.select('circle').attr('style', (d) => 'fill: ' + (d.hasOwnProperty('color') ? d['color'] : '#77618e'));
|
||||
|
||||
let link = this.linkG
|
||||
|
@ -1947,7 +1947,7 @@ class Graph {
|
|||
let newLink = link.enter()
|
||||
.append( "line" )
|
||||
;
|
||||
|
||||
|
||||
//remove
|
||||
link.exit().remove();
|
||||
link = link.merge( newLink );
|
||||
|
@ -2032,7 +2032,7 @@ class Graph {
|
|||
}
|
||||
return this.svg.node();
|
||||
}
|
||||
|
||||
|
||||
calculateDistancesFromStart() {
|
||||
console.time('calculateDistancesFromStart');
|
||||
let starts = this.messages.filter( m => m.hasOwnProperty('start') && m['start'] == true);
|
||||
|
@ -2040,26 +2040,26 @@ class Graph {
|
|||
console.error("No start set");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
//initiate distances
|
||||
let distances = {};
|
||||
for(let msg of this.messages) {
|
||||
// distances[msg['@id']] = msg === startMsg ? 0 : null;
|
||||
distances[msg['@id']] = null;
|
||||
}
|
||||
|
||||
|
||||
let targetsPerMsg = {};
|
||||
let sourcesPerMsg = {};
|
||||
// console.log("dir", this.directions);
|
||||
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'];
|
||||
|
||||
|
||||
if(!targetsPerMsg.hasOwnProperty(from)) {
|
||||
targetsPerMsg[from] = [];
|
||||
}
|
||||
targetsPerMsg[from].push(to);
|
||||
|
||||
|
||||
|
||||
if(!sourcesPerMsg.hasOwnProperty(to)) {
|
||||
sourcesPerMsg[to] = [];
|
||||
|
@ -2074,21 +2074,21 @@ class Graph {
|
|||
// end of trail
|
||||
return yPos;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
let i = 0, y =0;
|
||||
for(let childMsgId of msgsPerMsg[msgId]) {
|
||||
if(distances[childMsgId] !== null){
|
||||
continue;
|
||||
}
|
||||
if(distances[childMsgId] === null || (goingDown && distances[childMsgId][0] > depth)) {
|
||||
|
||||
|
||||
if(distances[childMsgId] === null) {
|
||||
if(i > 0){
|
||||
yPos++;
|
||||
}
|
||||
i++;
|
||||
|
||||
|
||||
console.log('set for id', childMsgId, goingDown, depth, yPos);
|
||||
distances[childMsgId] = [depth, yPos];
|
||||
|
||||
|
@ -2106,7 +2106,7 @@ class Graph {
|
|||
if(distances[childMsgId] === null) {
|
||||
distances[childMsgId] = [depth, yPos];
|
||||
}
|
||||
|
||||
|
||||
// console.log('a', depth);
|
||||
yPos = traverseMsg(childMsgId, depth - 1, goingDown, yPos);
|
||||
} else {
|
||||
|
@ -2114,7 +2114,7 @@ class Graph {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// if( i == 0 && y == 1) {
|
||||
// // we reached an item that branches back into the tree
|
||||
// return yPos -1;
|
||||
|
@ -2122,7 +2122,7 @@ class Graph {
|
|||
// console.log('yPos',msgId,yPos);
|
||||
return yPos;
|
||||
}
|
||||
|
||||
|
||||
let yPos = 0;
|
||||
console.time('step1');
|
||||
for(let startMsg of starts) {
|
||||
|
@ -2148,7 +2148,7 @@ class Graph {
|
|||
console.timeEnd('polish: '+ msgId);
|
||||
}
|
||||
console.timeEnd('step2');
|
||||
|
||||
|
||||
// let additionalsDepth = 0;
|
||||
//// now the secondary strands:
|
||||
// for(let msgId in distances) {
|
||||
|
@ -2158,11 +2158,11 @@ class Graph {
|
|||
// }
|
||||
// distances[msgId] = additionalsDepth;
|
||||
// traverseMsg(msgId, additionalsDepth+1, true);
|
||||
//
|
||||
//
|
||||
// }
|
||||
console.timeEnd("calculateDistancesFromStart");
|
||||
return distances;
|
||||
}
|
||||
}
|
||||
//
|
||||
//
|
||||
//
|
||||
|
|
Loading…
Reference in a new issue