Updated cards from tuesch changes. Incorporated InteractionMapper memory leak fix.

This commit is contained in:
2019-08-13 14:31:03 +02:00
parent 03be6673c5
commit 30c7113713
193 changed files with 21151 additions and 474 deletions
+189 -115
View File
@@ -146,7 +146,6 @@ CardPlugin.EnlargeableThumbnail = class EnlargeableThumbnail extends CardPluginB
this.fadeAnimationDuration = fadeAnimationDuration
this.interactionType = interactionType
}
get require() {
return [CardPlugin.LightBox]
}
@@ -392,126 +391,201 @@ CardPlugin.Speech = class SpeechPlugin extends CardPluginBase {
this.className = className
this.parentSelector = parentSelector
this.interactionType = interactionType
}
get require() {
return [CardPlugin.Ui]
}
append(context) {
let container = context.querySelector(this.parentSelector)
this.button = document.createElement('div')
this.button.className = 'icon button ' + this.className
container.appendChild(this.button)
InteractionMapper.on(this.interactionType, this.button, () => {
let subcard = context.querySelector('.mainview > .subcard')
let target = subcard ? subcard : context
this.speak(target)
})
}
_activate() {
this._disableActive()
this.active = this
this._activateButton()
}
_activateButton() {
if (this.button) this.button.classList.add('active')
}
_deactivate() {
this._deactivateButton()
}
_deactivateButton() {
if (this.button) this.button.classList.remove('active')
}
_isSameNode(node) {
//console.log(this.currentText, node.innerText)
return this.currentText == node.innerText
}
speak(node) {
console.log(this._isSameNode(node))
if (!window.speechSynthesis.speaking) {
console.log('Noone talking!')
this._start(node)
} else if (this._isSameNode(node)) {
console.log('Requested same!')
this._stop()
} else {
console.log('Requested Different!')
this._stop()
this._start(node)
}
}
_disableActive() {
console.log('disableActive:', this.active)
if (this.active) {
this.active._deactivate()
}
}
_start(node) {
this.currentText = node.innerText
let utterance = new SpeechSynthesisUtterance(node.innerText)
let voices = window.speechSynthesis.getVoices()
console.log(voices)
let voice = voices.filter(val => {
//console.log(val)
return val.name == 'Microsoft Hedda Desktop - German'
})[0]
//console.log(voice)
utterance.voice = voice
console.log('TALK: ', utterance)
window.speechSynthesis.speak(utterance)
this._activate()
window.speechSynthesis.resume()
utterance.onboundary = () => {
console.log('onboundary', node.innerText)
if (this.currentText.substring(0, 5) != node.innerText.substring(0, 5)) {
console.log('text for speech synth changed!', this.currentText, node.innerText)
this._stop()
}
}
utterance.onend = () => console.log('onend', node.innerText)
utterance.onerror = () => console.log('onerror', node.innerText)
utterance.onmark = () => console.log('onmark', node.innerText)
utterance.onpause = () => console.log('onpause', node.innerText)
utterance.onresume = () => console.log('onresume', node.innerText)
utterance.onstart = () => console.log('onstart', node.innerText)
utterance.onerror = () => console.log('onerror', node.innerText)
}
_stop() {
/*
Speech doesn't stop when page is navigated.
Therefore we do it manually here.
*/
window.addEventListener('beforeunload', () => {
window.speechSynthesis.cancel()
this.currentText = null
this._deactivate()
}
})
get active() {
return this.constructor.active
}
// Binding the function beforehand ensures, that the end function is always the same.
this._end = this._end.bind(this)
set active(val) {
this.constructor.active = val
}
this._setupUtterance()
this.utterance.addEventListener('end', event => {
this._end()
})
}
get currentText() {
return this.constructor.text
}
get require() {
return [CardPlugin.Ui]
}
set currentText(val) {
this.constructor.text = val
subcardChanged(closed) {
if (this.cardActive) {
this._updateText(closed)
}
}
get cardActive() {
return this.activeUtterance == this.utterance
}
_updateText(ignoreSubcard = false) {
let node = this.context
let subcard = this.context.querySelector('.mainview > .subcard')
if (ignoreSubcard) {
if (subcard != null) {
let clone = node.cloneNode(true)
let clonedSubcard = clone.querySelector('.mainview > .subcard')
clonedSubcard.parentNode.removeChild(clonedSubcard)
node = clone
}
} else {
if (subcard) {
let clone = subcard.cloneNode(true)
clone.querySelectorAll('figure').forEach(figure => {
figure.parentNode.removeChild(figure)
})
node = clone
}
}
let id = this.context.getAttribute('data-id')
let src = this.context.getAttribute('data-source')
let subcardSource = null
if (subcard != null) {
subcardSource = subcard.getAttribute('data-source')
}
if (!window.speechSynthesis.speaking) {
this._start(node)
Logging.log(`Started speech on card: id:${id} - source: ${src} - subcard: ${subcardSource}`)
} else if (this.cardActive && this._sameText(node)) {
Logging.log(`Stopped speech on card: id:${id} - source: ${src} - subcard: ${subcardSource}`)
this._stop()
} else {
Logging.log(`Updated Text on card: id:${id} - source: ${src} - subcard: ${subcardSource}`)
this._stop()
.then(() => {
this._start(node)
})
.catch(console.error)
}
}
_sameText(node) {
return this.utterance.text == this._cleanupText(node)
}
_setupUtterance() {
this.utterance = new SpeechSynthesisUtterance()
this.utterance.lang = 'de-DE'
}
get require() {
return [CardPlugin.Ui]
}
remove() {
this.button = null
super.remove()
}
append(context) {
let container = context.querySelector(this.parentSelector)
this.button = document.createElement('div')
this.button.className = 'icon button ' + this.className
container.appendChild(this.button)
InteractionMapper.on(this.interactionType, this.button, () => {
this.speak()
})
this.context.addEventListener('DOMNodeRemoved', event => {
if (
this.context['lastSpeechNode'] == window.speechSynthesis['speechPluginNode'] &&
event.target == this.context
) {
this._stop()
}
})
ScatterCard
}
_isSameNode(node) {
return this.currentText == node.textContent
}
speak() {
/**
* This is a little bit ugly, but imho the most elegant of all dirty solutions.
*
5ht * Within the plugins we have no knowledge of other cards and such. But must differentiate the
* clicks by their corresponding owner. The SpeechUtterance just takes a text and has no knowledge
* about the node that is currently read to the user.
*
* This means, that we can identify same text, but not differentiate same text on different nodes.
* To account for that, we add the node to the speechSynthesis object (#benefitsOfJavaScript) and
* have access to the node, by - let's say - expanding the functionality of the SpeechSynthesis object.
*
* SO -17.07.19
*/
let activeNode = window.speechSynthesis['speechPluginNode']
this._updateText()
}
async _stop() {
return new Promise(resolve => {
if (this.activeUtterance) {
this.activeUtterance.addEventListener('end', resolve, {
once: true
})
}
window.speechSynthesis.cancel()
})
}
get activeUtterance() {
return window.speechSynthesis['speechPluginUtterance']
}
_end() {
window.speechSynthesis['speechPluginNode'] = null
window.speechSynthesis['speechPluginUtterance'] = null
this._deactivateButton()
this.context.classList.remove('speech-plugin-is-reading')
}
_start(node) {
window.speechSynthesis.cancel()
window.speechSynthesis['speechPluginUtterance'] = this.utterance
window.speechSynthesis['speechPluginNode'] = node
this.context['lastSpeechNode'] = node
let cleanText = this._cleanupText(node)
this.utterance.text = cleanText
window.speechSynthesis.speak(this.utterance)
this._activateButton()
this.context.classList.add('speech-plugin-is-reading')
}
_cleanupText(node) {
let text = node.textContent
text = this._removeShy(text)
return text
}
_removeShy(text) {
return text.replace(/\u00AD/g, '')
}
_activateButton() {
if (this.button) this.button.classList.add('active')
}
_deactivateButton() {
if (this.button) this.button.classList.remove('active')
}
}