Updated cards from tuesch changes. Incorporated InteractionMapper memory leak fix.
This commit is contained in:
Vendored
+451
-239
@@ -3364,7 +3364,9 @@
|
||||
}
|
||||
|
||||
close() {
|
||||
console.log('SCATTER WAS CLOSED!');
|
||||
this._callCloseCallbacks();
|
||||
this._removeCallbacks();
|
||||
this._removeSelfFromScatterContainer();
|
||||
}
|
||||
|
||||
@@ -3374,6 +3376,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
_removeCallbacks() {
|
||||
this.onClose = [];
|
||||
this.onTransform = [];
|
||||
}
|
||||
|
||||
_removeSelfFromScatterContainer() {
|
||||
// Removes self from container when it's closed.
|
||||
if (this.container) {
|
||||
@@ -7638,8 +7645,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
/** To avoid problems with relative URL paths, we use inline data URI to load svg icons. */
|
||||
|
||||
/**
|
||||
* A class that collects static methods to maintain the states and parts of
|
||||
* EyeVisit like cards.
|
||||
@@ -7650,11 +7655,35 @@
|
||||
static setup(context, modules = []) {
|
||||
console.log('Setup Card...', modules);
|
||||
context.modules = [];
|
||||
|
||||
context.module = {};
|
||||
|
||||
context.classList.add('info-card');
|
||||
context.setAttribute('data-id', Card.id++);
|
||||
|
||||
modules.forEach(module => {
|
||||
if (module.apply(context)) context.modules.push(module.constructor.name);
|
||||
if (module.apply(context)) {
|
||||
const moduleName = module.constructor.name;
|
||||
context.modules.push(moduleName);
|
||||
context.module[moduleName] = module;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static remove(context) {
|
||||
for (let module of Object.values(context.module)) {
|
||||
module.remove();
|
||||
}
|
||||
}
|
||||
|
||||
static eventClose(event) {
|
||||
let context = this.getContext(event.target);
|
||||
|
||||
if (context) {
|
||||
this.constructor.close(context);
|
||||
} else console.error('Could not find context!', event.target);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
@@ -7662,13 +7691,12 @@
|
||||
* @param {*} event
|
||||
* @memberof Card
|
||||
*/
|
||||
static close(event) {
|
||||
let context = this.getContext(event.target);
|
||||
if (context) {
|
||||
if (context.onClose) {
|
||||
context.onClose(event);
|
||||
} else context.parentNode.removeChild(context);
|
||||
} else console.error('Could not find context!', event.target);
|
||||
static close(context) {
|
||||
console.log('CLOSE CARD!!!');
|
||||
this.unregisterAllEvents(context);
|
||||
if (context.onClose) {
|
||||
context.onClose(event);
|
||||
} else context.parentNode.removeChild(context);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -7680,12 +7708,12 @@
|
||||
* @param {*} replaceFunc
|
||||
* @memberof Card
|
||||
*/
|
||||
static _replaceAttributes(html, attribute, replaceFunc) {
|
||||
static _replaceAttributes(context, html, attribute, replaceFunc) {
|
||||
let clickables = html.querySelectorAll(`[${attribute}]`);
|
||||
clickables.forEach(element => {
|
||||
let attributeVal = element.getAttribute(attribute);
|
||||
element.removeAttribute(attribute);
|
||||
replaceFunc.call(this, element, attributeVal);
|
||||
replaceFunc.call(this, context, element, attributeVal);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -7699,7 +7727,7 @@
|
||||
* @returns
|
||||
* @memberof Card
|
||||
*/
|
||||
static _replaceCallback(element, attributeVal) {
|
||||
static _replaceCallback(context, element, attributeVal) {
|
||||
if (element.tagName == 'A') {
|
||||
element.addEventListener('click', event => {
|
||||
event.preventDefault();
|
||||
@@ -7731,7 +7759,7 @@
|
||||
// These are 'hardcoded' inside the convert.js.
|
||||
if (element.tagName == 'circle') return false
|
||||
|
||||
InteractionMapper.on(interactionType, element, event => {
|
||||
this.registerEvent(context, interactionType, element, event => {
|
||||
/**
|
||||
* Replaces the strings from the listener with the cooresponding variables.
|
||||
*/
|
||||
@@ -7783,6 +7811,13 @@
|
||||
/<\s*(a|video|img|image|circle)\s(.*?)(xlink:href|href|src)\s*=\s*["'](\..*?)["']\s*(.*?)>/g,
|
||||
function(data) {
|
||||
let path = that._getRelativePath(arguments[4]);
|
||||
|
||||
console.log('REPLACE ', arguments[1]);
|
||||
if (arguments[1] == 'a') {
|
||||
console.error('NOT REPLACING LINKS');
|
||||
return ''
|
||||
}
|
||||
|
||||
const tag = `<${arguments[1]} ${arguments[2]} ${arguments[3]}="${path}" ${arguments[5]}>`;
|
||||
/* if (that.debug) */ console.log('Adjusted: ', tag);
|
||||
return tag
|
||||
@@ -7979,6 +8014,7 @@
|
||||
if (this.debug) console.log('Close Popup.', context, popup);
|
||||
window.popup = popup;
|
||||
popup.close();
|
||||
InteractionMapper.off(popup.element);
|
||||
this._unsetPopup(context);
|
||||
} else {
|
||||
console.error('Requested to close popup, but popup was not found.');
|
||||
@@ -8348,7 +8384,7 @@
|
||||
// but for simplicity it's just done here for now.
|
||||
// TODO: Adjust to load while animating (Problem: Unload when cancelled).
|
||||
console.log('loadHighlightPopup', src, position, local);
|
||||
this._loadPopupContent(src)
|
||||
this._loadPopupContent(context, src)
|
||||
.then(content => {
|
||||
this._openPopup(context, src, local, content, {
|
||||
highlight: node,
|
||||
@@ -8375,14 +8411,14 @@
|
||||
* @returns {Promise} - Returns a promise, that's resolved when the data is loaded.
|
||||
* @memberof Card
|
||||
*/
|
||||
static _loadPopupContent(source) {
|
||||
static _loadPopupContent(context, source) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open('get', source, true);
|
||||
xhr.onreadystatechange = () => {
|
||||
if (xhr.readyState == 4) {
|
||||
if (xhr.status == 200 || xhr.status == 0) {
|
||||
let html = this.postProcessResponseText(xhr.responseText);
|
||||
let html = this.postProcessResponseText(context, xhr.responseText);
|
||||
let selector = Card.popupHtmlSelector;
|
||||
let content = { html: html.body.innerHTML, selector };
|
||||
resolve(content);
|
||||
@@ -8477,6 +8513,9 @@
|
||||
let wrapper = this.getContext(node);
|
||||
|
||||
let zoomable = node.closest('figure');
|
||||
if (zoomable == null) {
|
||||
return
|
||||
}
|
||||
|
||||
// load mainimg - if none exists, there is nothing to open
|
||||
let img = zoomable.querySelector('.mainimg');
|
||||
@@ -8735,18 +8774,18 @@
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes a zoomable object with animation
|
||||
* Closes an zoomable object with an animation.
|
||||
*
|
||||
* @static
|
||||
* @param {any} wrapper - the wrapper containing the index card
|
||||
* @param {any} div - the figure containing the relevant elements
|
||||
* @param {any} zoomable - the zoomable element, from which the zoomed figure originates
|
||||
* @param {any} rect - the target rect for the tween (typically the top left width height of the zoomable)
|
||||
* @param {DOMElement} context - Context of the zoomable.
|
||||
* @param {*} zoomable
|
||||
* @param {*} zoomedFig
|
||||
* @memberof Card
|
||||
*/
|
||||
static closeZoomable(context, zoomable, zoomedFig) {
|
||||
if (this.debug) console.log('Close Zoomable', context, zoomable, zoomedFig);
|
||||
|
||||
//TODO: Why do I need this check. Shouldn't it be always present?! - SO
|
||||
if (zoomable) {
|
||||
this._unsetZoomable(context);
|
||||
let caption = zoomable.querySelector('figcaption.cap');
|
||||
@@ -8767,12 +8806,15 @@
|
||||
TweenLite.set(zoomable, {
|
||||
opacity: 1
|
||||
});
|
||||
|
||||
let div = zoomedFig.parentNode;
|
||||
let videoElement = div.querySelector('video');
|
||||
if (videoElement) videoElement.pause();
|
||||
div.parentNode.removeChild(div);
|
||||
}
|
||||
});
|
||||
|
||||
InteractionMapper.off(zoomedFig);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8862,6 +8904,7 @@
|
||||
rotation: angle
|
||||
});
|
||||
indexbox.prepend(clone);
|
||||
clone.setAttribute('data-source', src);
|
||||
|
||||
let titlebar = clone.querySelector('.titlebar');
|
||||
let title = titlebar.querySelector('h2');
|
||||
@@ -8886,7 +8929,7 @@
|
||||
}
|
||||
|
||||
//jquery hyphenate below
|
||||
if (typeof $ != 'undefined') {
|
||||
if (this.constructor._jQueryIsPresent()) {
|
||||
$('.column')
|
||||
.not('.overview')
|
||||
.children('p')
|
||||
@@ -8941,86 +8984,6 @@
|
||||
|
||||
Card._disableCardCloseButton(context);
|
||||
|
||||
const closeAnimation = () => {
|
||||
//logging
|
||||
if (src) {
|
||||
let strparts = src.split('/');
|
||||
let cardID = strparts[strparts.length - 2];
|
||||
let cardName = strparts[strparts.length - 1];
|
||||
strparts = card.className.split(' ');
|
||||
let cardType = strparts[1];
|
||||
let msg = 'Card: ' + cardID + ': closeTopic: ' + cardType + ', ' + cardName;
|
||||
console.log('Logging:', msg);
|
||||
Logging.log(msg);
|
||||
}
|
||||
|
||||
Card._cleanup(context);
|
||||
Card._unsetSubcard(context);
|
||||
|
||||
this._enableCardCloseButton(context);
|
||||
|
||||
let previewTitlebar = card.querySelector('.titlebar');
|
||||
let titlebarStyle = window.getComputedStyle(previewTitlebar);
|
||||
let titlebar = clone.querySelector('.titlebar');
|
||||
|
||||
TweenLite.to(titlebar, this.animation.articleTransition, {
|
||||
height: parseInt(titlebarStyle.height)
|
||||
});
|
||||
|
||||
TweenLite.to(articleClone, this.animation.articleTransition / 2, {
|
||||
autoAlpha: 0
|
||||
});
|
||||
|
||||
let title = titlebar.querySelector('h2');
|
||||
let original = {
|
||||
height: parseInt(titlebarStyle.height)
|
||||
};
|
||||
|
||||
if (this.dynamicHeight) {
|
||||
TweenLite.to(subcardContent, this.animation.articleTransition, {
|
||||
height: '100%'
|
||||
});
|
||||
}
|
||||
|
||||
TweenLite.set(card, { autoAlpha: 1, css: { maxWidth } });
|
||||
TweenLite.to(clone, this.animation.articleTransition, {
|
||||
x: localOrigin.x - padding,
|
||||
y: localOrigin.y - padding,
|
||||
scaleX,
|
||||
scaleY,
|
||||
ease: ExpoScaleEase.config(1, scaleX),
|
||||
rotation: angle,
|
||||
onComplete: () => {
|
||||
// article.remove()
|
||||
TweenLite.to(clone, this.animation.fade, {
|
||||
//delay: 0.2,
|
||||
autoAlpha: 0,
|
||||
onComplete: () => {
|
||||
if (editable) {
|
||||
mainController.popController();
|
||||
}
|
||||
clone.remove();
|
||||
}
|
||||
});
|
||||
},
|
||||
onUpdateParams: ['{self}'],
|
||||
onUpdate: function(self) {
|
||||
let transform = self.target._gsTransform;
|
||||
|
||||
TweenLite.set(title, {
|
||||
scale: 1 / transform.scaleX
|
||||
});
|
||||
|
||||
TweenLite.set(titlebar, {
|
||||
height: (original.height * 1) / transform.scaleY
|
||||
});
|
||||
|
||||
// Retain the border at same visual thickness.
|
||||
titlebar.style.borderBottomWidth = desiredBorderBottomWidth / transform.scaleY + 'px';
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
//TODO consider renaming it to something more intuitive.
|
||||
let iconClone = clone.querySelector('.card-icon');
|
||||
|
||||
@@ -9042,6 +9005,9 @@
|
||||
if (this.dynamicHeight) {
|
||||
article.appendChild(iconClone);
|
||||
}
|
||||
|
||||
const eventElements = [indexbox, iconClone, clone];
|
||||
|
||||
// Use the 'tap' event for closing.
|
||||
// Otherwise the subcard cannot be closed,
|
||||
// when another subcard is touched.
|
||||
@@ -9051,17 +9017,113 @@
|
||||
if (isDirty) {
|
||||
mainController.saveNode(html.innerHTML, url => {
|
||||
callback(url);
|
||||
closeAnimation();
|
||||
this._closeIndexCard(context, card,{
|
||||
eventElements,
|
||||
src
|
||||
});
|
||||
});
|
||||
} else {
|
||||
closeAnimation();
|
||||
this._closeIndexCard(context, card);
|
||||
}
|
||||
} else {
|
||||
closeAnimation();
|
||||
this._closeIndexCard(context, card);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static _closeIndexCard(context, card, {
|
||||
eventElements = [],
|
||||
src = null
|
||||
} = []) {
|
||||
//logging
|
||||
if (src) {
|
||||
let strparts = src.split('/');
|
||||
let cardID = strparts[strparts.length - 2];
|
||||
let cardName = strparts[strparts.length - 1];
|
||||
strparts = card.className.split(' ');
|
||||
let cardType = strparts[1];
|
||||
let msg = 'Card: ' + cardID + ': closeTopic: ' + cardType + ', ' + cardName;
|
||||
console.log('Logging:', msg);
|
||||
Logging.log(msg);
|
||||
}
|
||||
|
||||
Card._cleanup(context);
|
||||
Card._unsetSubcard(context);
|
||||
this._subcardChanged(context, true);
|
||||
this._enableCardCloseButton(context);
|
||||
|
||||
let previewTitlebar = card.querySelector('.titlebar');
|
||||
let titlebarStyle = window.getComputedStyle(previewTitlebar);
|
||||
let titlebar = clone.querySelector('.titlebar');
|
||||
|
||||
TweenLite.to(titlebar, this.animation.articleTransition, {
|
||||
height: parseInt(titlebarStyle.height)
|
||||
});
|
||||
|
||||
TweenLite.to(articleClone, this.animation.articleTransition / 2, {
|
||||
autoAlpha: 0
|
||||
});
|
||||
|
||||
let title = titlebar.querySelector('h2');
|
||||
let original = {
|
||||
height: parseInt(titlebarStyle.height)
|
||||
};
|
||||
|
||||
if (this.dynamicHeight) {
|
||||
TweenLite.to(subcardContent, this.animation.articleTransition, {
|
||||
height: '100%'
|
||||
});
|
||||
}
|
||||
|
||||
TweenLite.set(card, { autoAlpha: 1, css: { maxWidth } });
|
||||
TweenLite.to(clone, this.animation.articleTransition, {
|
||||
x: localOrigin.x - padding,
|
||||
y: localOrigin.y - padding,
|
||||
scaleX,
|
||||
scaleY,
|
||||
ease: ExpoScaleEase.config(1, scaleX),
|
||||
rotation: angle,
|
||||
onComplete: () => {
|
||||
// article.remove()
|
||||
TweenLite.to(clone, this.animation.fade, {
|
||||
//delay: 0.2,
|
||||
autoAlpha: 0,
|
||||
onComplete: () => {
|
||||
if (editable) {
|
||||
mainController.popController();
|
||||
}
|
||||
clone.remove();
|
||||
}
|
||||
});
|
||||
},
|
||||
onUpdateParams: ['{self}'],
|
||||
onUpdate: function(self) {
|
||||
let transform = self.target._gsTransform;
|
||||
|
||||
TweenLite.set(title, {
|
||||
scale: 1 / transform.scaleX
|
||||
});
|
||||
|
||||
TweenLite.set(titlebar, {
|
||||
height: (original.height * 1) / transform.scaleY
|
||||
});
|
||||
|
||||
// Retain the border at same visual thickness.
|
||||
titlebar.style.borderBottomWidth = desiredBorderBottomWidth / transform.scaleY + 'px';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if jQuery is properly included in the project.
|
||||
* Otherwise specific features may not work correctly (e.g. hyphenation)
|
||||
*/
|
||||
_jQueryIsPresent() {
|
||||
let jQueryInitialized = typeof $ != 'undefined';
|
||||
if (!jQueryInitialized) console.error('No jQuery is provided. Specific features may fail.');
|
||||
return jQueryInitialized
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the index card. Called by the zoom icon click handler.
|
||||
* The assumed card structure is as follows:
|
||||
@@ -9124,14 +9186,10 @@
|
||||
xhr.onreadystatechange = () => {
|
||||
if (xhr.readyState == 4 && (xhr.status == 200 || xhr.status == 0)) {
|
||||
let html = xhr.responseText;
|
||||
let parsedHTML = this.postProcessResponseText(html);
|
||||
|
||||
// TODO: What is this good for?
|
||||
// let article = parsedHTML.querySelector('article')
|
||||
// card.insertAdjacentElement('afterbegin', article)
|
||||
// TweenLite.set(article, { autoAlpha: 0 })
|
||||
|
||||
let parsedHTML = this.postProcessResponseText(context, html);
|
||||
Card.expandIndexCard(card, parsedHTML, 'article', relativeSource, saveCallback);
|
||||
|
||||
this._subcardChanged(context);
|
||||
}
|
||||
};
|
||||
xhr.onerror = () => {
|
||||
@@ -9257,7 +9315,8 @@
|
||||
* @returns
|
||||
* @memberof Card
|
||||
*/
|
||||
static postProcessResponseText(htmlString) {
|
||||
static postProcessResponseText(context, htmlString) {
|
||||
console.error('RUN POSTPROCESS');
|
||||
let editable = this.isEditable();
|
||||
|
||||
htmlString = this._adjustRelativeLinks(htmlString);
|
||||
@@ -9265,7 +9324,7 @@
|
||||
let parser = new DOMParser();
|
||||
let html = parser.parseFromString(htmlString, 'text/html');
|
||||
if (!editable) {
|
||||
this._replaceAttributes(html, 'onclick', this._replaceCallback);
|
||||
this._replaceAttributes(context, html, 'onclick', this._replaceCallback);
|
||||
}
|
||||
let zoomableWrappers = html.querySelectorAll('.svg-wrapper');
|
||||
zoomableWrappers.forEach(wrapper => {
|
||||
@@ -9386,7 +9445,7 @@
|
||||
this._setPopupSource(popup, source);
|
||||
context.popup = popup;
|
||||
|
||||
if (typeof $ != 'undefined') {
|
||||
if (this.constructor._jQueryIsPresent()) {
|
||||
//jquery hyphenate below
|
||||
console.log('hyphenated popup:', $('span').hyphenate('de'));
|
||||
}
|
||||
@@ -9485,8 +9544,44 @@
|
||||
static get relativePath() {
|
||||
return Card._relativePath
|
||||
}
|
||||
|
||||
static getRegisteredEvents(context) {
|
||||
return context._registeredEvents == null ? [] : context._registeredEvents
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method that registers InteractionMapper events on the info card.
|
||||
* Those events are saved on the context element. An they are unregistered when the
|
||||
* card is closed again.
|
||||
*
|
||||
* @static
|
||||
* @param {DOMElement} context - Context of the Card.
|
||||
* @param {string} types
|
||||
* @param {DOMElement} element -Element on which the event is registered.
|
||||
* @param {Function} callback - Function thats called when the event occurs.
|
||||
* @memberof Card
|
||||
*/
|
||||
static registerEvent(context, types, element, callback) {
|
||||
InteractionMapper.on(types, element, callback);
|
||||
if (context._registeredEvents == null) context._registeredEvents = [];
|
||||
if (context._registeredEvents.indexOf(element) == -1) context._registeredEvents.push(element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters all events on the infocard.
|
||||
*
|
||||
*
|
||||
* @static
|
||||
* @param {DomElement} context - Context of the card.
|
||||
* @memberof Card
|
||||
*/
|
||||
static unregisterAllEvents(context) {
|
||||
let eventElements = this.getRegisteredEvents(context);
|
||||
InteractionMapper.off(eventElements);
|
||||
}
|
||||
}
|
||||
|
||||
Card.id = 0;
|
||||
Card.debug = true;
|
||||
Card._relativePath = '';
|
||||
Card.scatterContainer = null;
|
||||
@@ -10091,7 +10186,7 @@
|
||||
*
|
||||
* @class ScatterCard
|
||||
*/
|
||||
class ScatterCard extends Card {
|
||||
class ScatterCard$1 extends Card {
|
||||
/**
|
||||
* TODO: Find a more suitable name.
|
||||
* Adjusts the HTML to work in the new context.
|
||||
@@ -10101,7 +10196,7 @@
|
||||
* @param {*} htmlString
|
||||
* @param {*} basePath
|
||||
* @param {*} [opts={}]
|
||||
* @memberof Card
|
||||
* @memberof ScatterCard
|
||||
*/
|
||||
static setup(context, htmlString, { basePath = './', modules = [] } = {}) {
|
||||
context.classList.add('info-card');
|
||||
@@ -10115,7 +10210,7 @@
|
||||
/**
|
||||
* Conflicts with the FindTarget method of the Abstract scatter.
|
||||
*/
|
||||
this._replaceAttributes(html, 'onclick', this._replaceCallback);
|
||||
this._replaceAttributes(context, html, 'onclick', this._replaceCallback);
|
||||
|
||||
let content = html.querySelector('.mainview');
|
||||
context.appendChild(content);
|
||||
@@ -10137,7 +10232,16 @@
|
||||
element.onClose = callback;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the close listener from a card element.
|
||||
*
|
||||
* @static
|
||||
* @param {HTMLElement} element - Context of the Card.
|
||||
* @memberof ScatterCard
|
||||
*/
|
||||
static removeOnCloseListener(element) {
|
||||
element.onClose = null;
|
||||
}
|
||||
/**
|
||||
* Creates a scatter for the card and applies the card to it,
|
||||
*
|
||||
@@ -10147,7 +10251,7 @@
|
||||
* @param {string} [basePath=""]
|
||||
* @param {*} [opts={}]
|
||||
* @returns
|
||||
* @memberof Card
|
||||
* @memberof ScatterCard
|
||||
*/
|
||||
static createCardScatter(html, scatterContainer, { basePath = './', modules = [] } = {}) {
|
||||
let element = document.createElement('div');
|
||||
@@ -10165,6 +10269,42 @@
|
||||
return element
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes but NOT removes the scatter element.
|
||||
*
|
||||
* The remove must be called separately, it may be used in the close callback
|
||||
* of the scatter.
|
||||
*/
|
||||
static close(context) {
|
||||
|
||||
Card.close(context);
|
||||
|
||||
if (context['scatter']) {
|
||||
console.error('CLOSED CARD');
|
||||
context.scatter.close();
|
||||
} else {
|
||||
console.error('Expected a scatter element to close!', this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up the card.
|
||||
*
|
||||
* @static
|
||||
* @override
|
||||
* @memberof ScatterCard
|
||||
*/
|
||||
static remove(context) {
|
||||
if (context['scatter']) {
|
||||
context.scatter = null;
|
||||
console.error('REMOVED CARD');
|
||||
} else {
|
||||
console.error('Expected a scatter element to remove!', this);
|
||||
}
|
||||
|
||||
Card.remove(context);
|
||||
}
|
||||
|
||||
/**
|
||||
*Utility function to create a fully functional card scatter.
|
||||
*
|
||||
@@ -10204,13 +10344,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
ScatterCard.selectedLanguage = 0;
|
||||
ScatterCard.languages = ['Deutsch', 'English'];
|
||||
ScatterCard.languageTags = {
|
||||
ScatterCard$1.selectedLanguage = 0;
|
||||
ScatterCard$1.languages = ['Deutsch', 'English'];
|
||||
ScatterCard$1.languageTags = {
|
||||
Deutsch: 'de',
|
||||
English: 'en'
|
||||
};
|
||||
ScatterCard.scatterContainer = null;
|
||||
ScatterCard$1.scatterContainer = null;
|
||||
|
||||
var CardPlugin = CardPlugin || {};
|
||||
|
||||
@@ -10360,7 +10500,6 @@
|
||||
this.fadeAnimationDuration = fadeAnimationDuration;
|
||||
this.interactionType = interactionType;
|
||||
}
|
||||
|
||||
get require() {
|
||||
return [CardPlugin.LightBox]
|
||||
}
|
||||
@@ -10606,128 +10745,201 @@
|
||||
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();
|
||||
});
|
||||
|
||||
// Binding the function beforehand ensures, that the end function is always the same.
|
||||
this._end = this._end.bind(this);
|
||||
|
||||
this._setupUtterance();
|
||||
this.utterance.addEventListener('end', event => {
|
||||
this._end();
|
||||
});
|
||||
}
|
||||
|
||||
get require() {
|
||||
return [CardPlugin.Ui]
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
get active() {
|
||||
return this.constructor.active
|
||||
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');
|
||||
}
|
||||
|
||||
set active(val) {
|
||||
this.constructor.active = val;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
get currentText() {
|
||||
return this.constructor.text
|
||||
}
|
||||
_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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_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');
|
||||
}
|
||||
|
||||
set currentText(val) {
|
||||
this.constructor.text = val;
|
||||
}
|
||||
};
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
@@ -10842,7 +11054,7 @@
|
||||
window.Card = Card;
|
||||
window.CardPlugin = CardPlugin;
|
||||
window.CardPluginBase = CardPluginBase;
|
||||
window.ScatterCard = ScatterCard;
|
||||
window.ScatterCard = ScatterCard$1;
|
||||
window.Highlight = Highlight$1;
|
||||
window.Theme = Theme;
|
||||
|
||||
|
||||
Vendored
+7
@@ -6979,7 +6979,9 @@
|
||||
}
|
||||
|
||||
close() {
|
||||
console.log('SCATTER WAS CLOSED!');
|
||||
this._callCloseCallbacks();
|
||||
this._removeCallbacks();
|
||||
this._removeSelfFromScatterContainer();
|
||||
}
|
||||
|
||||
@@ -6989,6 +6991,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
_removeCallbacks() {
|
||||
this.onClose = [];
|
||||
this.onTransform = [];
|
||||
}
|
||||
|
||||
_removeSelfFromScatterContainer() {
|
||||
// Removes self from container when it's closed.
|
||||
if (this.container) {
|
||||
|
||||
Reference in New Issue
Block a user