2013-11-07 22:55:39 +00:00
|
|
|
/*\
|
|
|
|
title: $:/core/modules/storyviews/zoomin.js
|
|
|
|
type: application/javascript
|
|
|
|
module-type: storyview
|
|
|
|
|
|
|
|
Zooms between individual tiddlers
|
|
|
|
|
|
|
|
\*/
|
|
|
|
(function(){
|
|
|
|
|
|
|
|
/*jslint node: true, browser: true */
|
|
|
|
/*global $tw: false */
|
|
|
|
"use strict";
|
|
|
|
|
2014-07-08 09:07:17 +00:00
|
|
|
var easing = "cubic-bezier(0.645, 0.045, 0.355, 1)"; // From http://easings.net/#easeInOutCubic
|
|
|
|
|
2013-11-07 22:55:39 +00:00
|
|
|
var ZoominListView = function(listWidget) {
|
|
|
|
var self = this;
|
|
|
|
this.listWidget = listWidget;
|
2023-03-01 16:18:41 +00:00
|
|
|
this.textNodeLogger = new $tw.utils.Logger("zoomin story river view", {
|
|
|
|
enable: true,
|
|
|
|
colour: 'red'
|
|
|
|
});
|
2014-05-02 18:21:32 +00:00
|
|
|
// Get the index of the tiddler that is at the top of the history
|
2015-07-10 15:43:50 +00:00
|
|
|
var history = this.listWidget.wiki.getTiddlerDataCached(this.listWidget.historyTitle,[]),
|
2014-05-02 18:21:32 +00:00
|
|
|
targetTiddler;
|
|
|
|
if(history.length > 0) {
|
|
|
|
targetTiddler = history[history.length-1].title;
|
|
|
|
}
|
|
|
|
// Make all the tiddlers position absolute, and hide all but the top (or first) one
|
2013-11-07 22:55:39 +00:00
|
|
|
$tw.utils.each(this.listWidget.children,function(itemWidget,index) {
|
|
|
|
var domNode = itemWidget.findFirstDomNode();
|
2014-09-27 09:34:59 +00:00
|
|
|
// Abandon if the list entry isn't a DOM element (it might be a text node)
|
|
|
|
if(!(domNode instanceof Element)) {
|
|
|
|
return;
|
|
|
|
}
|
2015-02-10 22:40:38 +00:00
|
|
|
if((targetTiddler && targetTiddler !== itemWidget.parseTreeNode.itemTitle) || (!targetTiddler && index)) {
|
2013-11-07 22:55:39 +00:00
|
|
|
domNode.style.display = "none";
|
|
|
|
} else {
|
|
|
|
self.currentTiddlerDomNode = domNode;
|
|
|
|
}
|
2014-10-06 08:18:29 +00:00
|
|
|
$tw.utils.addClass(domNode,"tc-storyview-zoomin-tiddler");
|
2013-11-07 22:55:39 +00:00
|
|
|
});
|
2014-03-24 21:35:48 +00:00
|
|
|
};
|
2013-11-07 22:55:39 +00:00
|
|
|
|
|
|
|
ZoominListView.prototype.navigateTo = function(historyInfo) {
|
|
|
|
var duration = $tw.utils.getAnimationDuration(),
|
|
|
|
listElementIndex = this.listWidget.findListItem(0,historyInfo.title);
|
|
|
|
if(listElementIndex === undefined) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
var listItemWidget = this.listWidget.children[listElementIndex],
|
|
|
|
targetElement = listItemWidget.findFirstDomNode();
|
2014-09-27 09:34:59 +00:00
|
|
|
// Abandon if the list entry isn't a DOM element (it might be a text node)
|
2023-03-01 16:18:41 +00:00
|
|
|
if(!targetElement) {
|
|
|
|
return;
|
|
|
|
} else if (targetElement.nodeType === Node.TEXT_NODE) {
|
|
|
|
this.logTextNodeRoot(targetElement);
|
2014-09-27 09:34:59 +00:00
|
|
|
return;
|
|
|
|
}
|
2013-11-07 22:55:39 +00:00
|
|
|
// Make the new tiddler be position absolute and visible so that we can measure it
|
2014-10-06 08:18:29 +00:00
|
|
|
$tw.utils.addClass(targetElement,"tc-storyview-zoomin-tiddler");
|
2013-11-07 22:55:39 +00:00
|
|
|
$tw.utils.setStyle(targetElement,[
|
2014-10-07 13:07:55 +00:00
|
|
|
{display: "block"},
|
2013-11-07 22:55:39 +00:00
|
|
|
{transformOrigin: "0 0"},
|
|
|
|
{transform: "translateX(0px) translateY(0px) scale(1)"},
|
|
|
|
{transition: "none"},
|
|
|
|
{opacity: "0.0"}
|
|
|
|
]);
|
|
|
|
// Get the position of the source node, or use the centre of the window as the source position
|
|
|
|
var sourceBounds = historyInfo.fromPageRect || {
|
|
|
|
left: window.innerWidth/2 - 2,
|
|
|
|
top: window.innerHeight/2 - 2,
|
|
|
|
width: window.innerWidth/8,
|
|
|
|
height: window.innerHeight/8
|
|
|
|
};
|
|
|
|
// Try to find the title node in the target tiddler
|
2013-11-08 12:36:30 +00:00
|
|
|
var titleDomNode = findTitleDomNode(listItemWidget) || listItemWidget.findFirstDomNode(),
|
|
|
|
zoomBounds = titleDomNode.getBoundingClientRect();
|
2013-11-07 22:55:39 +00:00
|
|
|
// Compute the transform for the target tiddler to make the title lie over the source rectange
|
|
|
|
var targetBounds = targetElement.getBoundingClientRect(),
|
|
|
|
scale = sourceBounds.width / zoomBounds.width,
|
|
|
|
x = sourceBounds.left - targetBounds.left - (zoomBounds.left - targetBounds.left) * scale,
|
|
|
|
y = sourceBounds.top - targetBounds.top - (zoomBounds.top - targetBounds.top) * scale;
|
|
|
|
// Transform the target tiddler to its starting position
|
|
|
|
$tw.utils.setStyle(targetElement,[
|
|
|
|
{transform: "translateX(" + x + "px) translateY(" + y + "px) scale(" + scale + ")"}
|
|
|
|
]);
|
|
|
|
// Force layout
|
|
|
|
$tw.utils.forceLayout(targetElement);
|
|
|
|
// Apply the ending transitions with a timeout to ensure that the previously applied transformations are applied first
|
|
|
|
var self = this,
|
|
|
|
prevCurrentTiddler = this.currentTiddlerDomNode;
|
|
|
|
this.currentTiddlerDomNode = targetElement;
|
|
|
|
// Transform the target tiddler to its natural size
|
|
|
|
$tw.utils.setStyle(targetElement,[
|
2014-07-08 09:07:17 +00:00
|
|
|
{transition: $tw.utils.roundTripPropertyName("transform") + " " + duration + "ms " + easing + ", opacity " + duration + "ms " + easing},
|
2013-11-07 22:55:39 +00:00
|
|
|
{opacity: "1.0"},
|
|
|
|
{transform: "translateX(0px) translateY(0px) scale(1)"},
|
|
|
|
{zIndex: "500"},
|
|
|
|
]);
|
|
|
|
// Transform the previous tiddler out of the way and then hide it
|
|
|
|
if(prevCurrentTiddler && prevCurrentTiddler !== targetElement) {
|
2014-08-30 19:44:26 +00:00
|
|
|
scale = zoomBounds.width / sourceBounds.width;
|
2013-11-07 22:55:39 +00:00
|
|
|
x = zoomBounds.left - targetBounds.left - (sourceBounds.left - targetBounds.left) * scale;
|
|
|
|
y = zoomBounds.top - targetBounds.top - (sourceBounds.top - targetBounds.top) * scale;
|
|
|
|
$tw.utils.setStyle(prevCurrentTiddler,[
|
2014-07-08 09:07:17 +00:00
|
|
|
{transition: $tw.utils.roundTripPropertyName("transform") + " " + duration + "ms " + easing + ", opacity " + duration + "ms " + easing},
|
2013-11-07 22:55:39 +00:00
|
|
|
{opacity: "0.0"},
|
|
|
|
{transformOrigin: "0 0"},
|
|
|
|
{transform: "translateX(" + x + "px) translateY(" + y + "px) scale(" + scale + ")"},
|
|
|
|
{zIndex: "0"}
|
|
|
|
]);
|
|
|
|
// Hide the tiddler when the transition has finished
|
|
|
|
setTimeout(function() {
|
|
|
|
if(self.currentTiddlerDomNode !== prevCurrentTiddler) {
|
|
|
|
prevCurrentTiddler.style.display = "none";
|
|
|
|
}
|
|
|
|
},duration);
|
|
|
|
}
|
|
|
|
// Scroll the target into view
|
|
|
|
// $tw.pageScroller.scrollIntoView(targetElement);
|
|
|
|
};
|
|
|
|
|
2013-11-08 12:36:30 +00:00
|
|
|
/*
|
2014-08-28 16:25:07 +00:00
|
|
|
Find the first child DOM node of a widget that has the class "tc-title"
|
2013-11-08 12:36:30 +00:00
|
|
|
*/
|
|
|
|
function findTitleDomNode(widget,targetClass) {
|
2014-08-28 16:25:07 +00:00
|
|
|
targetClass = targetClass || "tc-title";
|
2013-11-08 12:36:30 +00:00
|
|
|
var domNode = widget.findFirstDomNode();
|
|
|
|
if(domNode && domNode.querySelector) {
|
2023-05-06 10:54:54 +00:00
|
|
|
return $tw.utils.querySelectorSafe("." + targetClass,domNode);
|
2013-11-08 12:36:30 +00:00
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2013-11-07 22:55:39 +00:00
|
|
|
ZoominListView.prototype.insert = function(widget) {
|
|
|
|
var targetElement = widget.findFirstDomNode();
|
2014-09-27 09:34:59 +00:00
|
|
|
// Abandon if the list entry isn't a DOM element (it might be a text node)
|
2023-03-01 16:18:41 +00:00
|
|
|
if(!targetElement) {
|
|
|
|
return;
|
|
|
|
} else if (targetElement.nodeType === Node.TEXT_NODE) {
|
|
|
|
this.logTextNodeRoot(targetElement);
|
2014-09-27 09:34:59 +00:00
|
|
|
return;
|
|
|
|
}
|
2013-11-07 22:55:39 +00:00
|
|
|
// Make the newly inserted node position absolute and hidden
|
2014-10-06 08:18:29 +00:00
|
|
|
$tw.utils.addClass(targetElement,"tc-storyview-zoomin-tiddler");
|
2013-11-07 22:55:39 +00:00
|
|
|
$tw.utils.setStyle(targetElement,[
|
2014-10-06 08:18:29 +00:00
|
|
|
{display: "none"}
|
2013-11-07 22:55:39 +00:00
|
|
|
]);
|
|
|
|
};
|
|
|
|
|
|
|
|
ZoominListView.prototype.remove = function(widget) {
|
|
|
|
var targetElement = widget.findFirstDomNode(),
|
2014-09-27 09:34:59 +00:00
|
|
|
duration = $tw.utils.getAnimationDuration(),
|
|
|
|
removeElement = function() {
|
2023-05-18 16:27:05 +00:00
|
|
|
widget.removeChildDomNodes();
|
2014-09-27 09:34:59 +00:00
|
|
|
};
|
|
|
|
// Abandon if the list entry isn't a DOM element (it might be a text node)
|
2021-06-09 09:18:15 +00:00
|
|
|
if(!targetElement || targetElement.nodeType === Node.TEXT_NODE) {
|
2014-09-27 09:34:59 +00:00
|
|
|
removeElement();
|
|
|
|
return;
|
|
|
|
}
|
2015-05-06 06:42:55 +00:00
|
|
|
// Abandon if hidden
|
|
|
|
if(targetElement.style.display != "block" ) {
|
|
|
|
removeElement();
|
|
|
|
return;
|
|
|
|
}
|
2013-11-07 22:55:39 +00:00
|
|
|
// Set up the tiddler that is being closed
|
2014-10-06 08:18:29 +00:00
|
|
|
$tw.utils.addClass(targetElement,"tc-storyview-zoomin-tiddler");
|
2013-11-07 22:55:39 +00:00
|
|
|
$tw.utils.setStyle(targetElement,[
|
2014-10-07 13:07:55 +00:00
|
|
|
{display: "block"},
|
2013-11-07 22:55:39 +00:00
|
|
|
{transformOrigin: "50% 50%"},
|
|
|
|
{transform: "translateX(0px) translateY(0px) scale(1)"},
|
|
|
|
{transition: "none"},
|
|
|
|
{zIndex: "0"}
|
|
|
|
]);
|
|
|
|
// We'll move back to the previous or next element in the story
|
|
|
|
var toWidget = widget.previousSibling();
|
|
|
|
if(!toWidget) {
|
|
|
|
toWidget = widget.nextSibling();
|
|
|
|
}
|
|
|
|
var toWidgetDomNode = toWidget && toWidget.findFirstDomNode();
|
|
|
|
// Set up the tiddler we're moving back in
|
|
|
|
if(toWidgetDomNode) {
|
2023-03-01 16:18:41 +00:00
|
|
|
if (toWidgetDomNode.nodeType === Node.TEXT_NODE) {
|
|
|
|
this.logTextNodeRoot(toWidgetDomNode);
|
|
|
|
toWidgetDomNode = null;
|
|
|
|
} else {
|
|
|
|
$tw.utils.addClass(toWidgetDomNode,"tc-storyview-zoomin-tiddler");
|
|
|
|
$tw.utils.setStyle(toWidgetDomNode,[
|
|
|
|
{display: "block"},
|
|
|
|
{transformOrigin: "50% 50%"},
|
|
|
|
{transform: "translateX(0px) translateY(0px) scale(10)"},
|
|
|
|
{transition: $tw.utils.roundTripPropertyName("transform") + " " + duration + "ms " + easing + ", opacity " + duration + "ms " + easing},
|
|
|
|
{opacity: "0"},
|
|
|
|
{zIndex: "500"}
|
|
|
|
]);
|
|
|
|
this.currentTiddlerDomNode = toWidgetDomNode;
|
|
|
|
}
|
2013-11-07 22:55:39 +00:00
|
|
|
}
|
|
|
|
// Animate them both
|
|
|
|
// Force layout
|
|
|
|
$tw.utils.forceLayout(this.listWidget.parentDomNode);
|
|
|
|
// First, the tiddler we're closing
|
|
|
|
$tw.utils.setStyle(targetElement,[
|
|
|
|
{transformOrigin: "50% 50%"},
|
|
|
|
{transform: "translateX(0px) translateY(0px) scale(0.1)"},
|
2014-07-08 09:07:17 +00:00
|
|
|
{transition: $tw.utils.roundTripPropertyName("transform") + " " + duration + "ms " + easing + ", opacity " + duration + "ms " + easing},
|
2013-11-07 22:55:39 +00:00
|
|
|
{opacity: "0"},
|
|
|
|
{zIndex: "0"}
|
|
|
|
]);
|
2014-09-27 09:34:59 +00:00
|
|
|
setTimeout(removeElement,duration);
|
2013-11-07 22:55:39 +00:00
|
|
|
// Now the tiddler we're going back to
|
|
|
|
if(toWidgetDomNode) {
|
|
|
|
$tw.utils.setStyle(toWidgetDomNode,[
|
|
|
|
{transform: "translateX(0px) translateY(0px) scale(1)"},
|
|
|
|
{opacity: "1"}
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
return true; // Indicate that we'll delete the DOM node
|
|
|
|
};
|
|
|
|
|
2023-03-01 16:18:41 +00:00
|
|
|
ZoominListView.prototype.logTextNodeRoot = function(node) {
|
|
|
|
this.textNodeLogger.log($tw.language.getString("Error/ZoominTextNode") + " " + node.textContent);
|
|
|
|
};
|
|
|
|
|
2013-11-07 22:55:39 +00:00
|
|
|
exports.zoomin = ZoominListView;
|
|
|
|
|
2015-05-06 06:42:55 +00:00
|
|
|
})();
|