mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2024-11-07 18:39:57 +00:00
Add element spotlight to dynannotate plugin
Useful for highlighting on screen elements for the user
This commit is contained in:
parent
75af83174b
commit
1d32ef44e5
@ -5,6 +5,7 @@ The ''Dynannotate'' plugin allows annotations on textual content to be created a
|
||||
* The dynannotate widget draws clickable textual annotations, search highlights and search snippets as overlays over the top of the content that it contains
|
||||
* The selection tracker keeps track of changes to the selected text in the main browser window. It triggers an action string when the selection changes, passing it the details of the selection. It can be used to display a popup menu
|
||||
** The original legacy selection tracker is also provided for backwards compatibility. It is much more limited, and not recommended for new projects
|
||||
* The element spotlight highlights on screen elements using a spotlight animation
|
||||
|
||||
!! Dynannotate Widget
|
||||
|
||||
@ -170,3 +171,12 @@ Notes:
|
||||
|
||||
* The selection popup will disappear if the selection is cancelled; this will happen if the user clicks on any other element apart than a button. Thus it is not possible to have any interactive controls within the popup apart from buttons
|
||||
|
||||
!! Element Spotlight
|
||||
|
||||
The `tm-spotlight-element` message causes a spotlight effect to briefly appear to highlight a specified element. The message accepts the following parameters:
|
||||
|
||||
|!Parameter |!Description |
|
||||
|`selector` |CSS selector of the element to highlight |
|
||||
|{//Any parameter names starting with `selector-`}// |Fallback CSS selectors to be used if the primary selector does not resolve to an element |
|
||||
|
||||
The fallback CSS selectors are case-insensitively sorted by title before use, with uppercase letters sorting before lower case letters. The usual convention is to use numeric suffixes: `selector-00`, `selector-01` etc.
|
||||
|
71
plugins/tiddlywiki/dynannotate/examples/spotlight.tid
Normal file
71
plugins/tiddlywiki/dynannotate/examples/spotlight.tid
Normal file
@ -0,0 +1,71 @@
|
||||
title: $:/plugins/tiddlywiki/dynannotate/examples/spotlight
|
||||
tags: $:/tags/dynannotateExamples
|
||||
caption: Spotlight
|
||||
|
||||
\define show-example(example)
|
||||
<$codeblock code=<<__example__>>/>
|
||||
|
||||
//''Displays as:''//
|
||||
|
||||
$example$
|
||||
\end
|
||||
|
||||
<div class="tc-dynannotation-example-info">
|
||||
|
||||
!! Spotlighting an Image
|
||||
|
||||
</div>
|
||||
|
||||
<<show-example """
|
||||
<$button>
|
||||
<$action-sendmessage $message="tm-spotlight-element" selector=".tc-dynannotate-spotlight-image-example"/>
|
||||
Spotlight this image
|
||||
</$button>
|
||||
<div class="tc-dynannotate-spotlight-image-example" style="display:inline-block;">
|
||||
{{$:/core/images/globe}}
|
||||
</div>
|
||||
""">>
|
||||
|
||||
<div class="tc-dynannotation-example-info">
|
||||
|
||||
!! Spotlighting a Button
|
||||
|
||||
</div>
|
||||
|
||||
<<show-example """
|
||||
<$button class="tc-dynannotate-spotlight-button-example">
|
||||
<$action-sendmessage $message="tm-spotlight-element" selector=".tc-dynannotate-spotlight-button-example"/>
|
||||
Spotlight this button
|
||||
</$button>
|
||||
""">>
|
||||
|
||||
<div class="tc-dynannotation-example-info">
|
||||
|
||||
!! Spotlighting a Text Area
|
||||
|
||||
</div>
|
||||
|
||||
<<show-example """
|
||||
<$button>
|
||||
<$action-sendmessage $message="tm-spotlight-element" selector=".tc-dynannotate-spotlight-textarea-example"/>
|
||||
Spotlight this text area
|
||||
</$button>
|
||||
|
||||
<$edit-text class="tc-dynannotate-spotlight-textarea-example" tag="textarea" tiddler="$:/temp/dynannotate/spotlight/demo/text"/>
|
||||
|
||||
""">>
|
||||
|
||||
<div class="tc-dynannotation-example-info">
|
||||
|
||||
!! Spotlighting the Sidebar Search Input
|
||||
|
||||
This button will spotlight the sidebar search, but if the sidebar is hidden then it will spotlight the button for showing the sidebar.
|
||||
|
||||
</div>
|
||||
|
||||
<<show-example """
|
||||
<$button>
|
||||
<$action-sendmessage $message="tm-spotlight-element" selector=".tc-sidebar-search .tc-popup-handle" selector-fallback=".tc-menubar .tc-show-sidebar-btn"/>
|
||||
Spotlight the sidebar search input
|
||||
</$button>
|
||||
""">>
|
136
plugins/tiddlywiki/dynannotate/modules/element-spotlight.js
Normal file
136
plugins/tiddlywiki/dynannotate/modules/element-spotlight.js
Normal file
@ -0,0 +1,136 @@
|
||||
/*\
|
||||
title: $:/plugins/tiddlywiki/dynannotate/element-spotlight.js
|
||||
type: application/javascript
|
||||
module-type: library
|
||||
|
||||
Manages the element spotlight effect
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
function ElementSpotlight() {
|
||||
this.animationStartTime; // Undefined if no animation is in progress
|
||||
// Create DOM nodes
|
||||
this.spotlightElement = $tw.utils.domMaker("div",{
|
||||
"class": "tc-dynannotate-spotlight"
|
||||
});
|
||||
this.spotlightWrapper = $tw.utils.domMaker("div",{
|
||||
"class": "tc-dynannotate-spotlight-wrapper",
|
||||
children: [
|
||||
this.spotlightElement
|
||||
]
|
||||
});
|
||||
document.body.appendChild(this.spotlightWrapper);
|
||||
}
|
||||
|
||||
/*
|
||||
Return the first visible element that matches a selector
|
||||
*/
|
||||
ElementSpotlight.prototype.querySelectorSafe = function(selector) {
|
||||
var targetNodes;
|
||||
// Get the matching elements
|
||||
try {
|
||||
targetNodes = document.querySelectorAll(selector);
|
||||
} catch(e) {
|
||||
console.log("Error with selector: " + selector);
|
||||
}
|
||||
if(!targetNodes) {
|
||||
return undefined;
|
||||
}
|
||||
// Remove any elements from the start of the list that are hidden, or have hidden ancestors
|
||||
var didRemoveFirstEntry;
|
||||
do {
|
||||
didRemoveFirstEntry = false;
|
||||
var hasHiddenAncestor = false,
|
||||
n = targetNodes[0];
|
||||
while(n) {
|
||||
if(n.hidden || (n instanceof Element && window.getComputedStyle(n).display === "none")) {
|
||||
hasHiddenAncestor = true;
|
||||
break;
|
||||
}
|
||||
n = n.parentNode;
|
||||
}
|
||||
if(hasHiddenAncestor) {
|
||||
// Remove first entry from targetNodes array
|
||||
targetNodes = [].slice.call(targetNodes, 1);
|
||||
didRemoveFirstEntry = true;
|
||||
}
|
||||
} while(didRemoveFirstEntry)
|
||||
// Return the first result
|
||||
return targetNodes[0];
|
||||
};
|
||||
|
||||
ElementSpotlight.prototype.positionSpotlight = function(x,y,innerRadius,outerRadius,opacity) {
|
||||
this.spotlightElement.style.display = "block";
|
||||
this.spotlightElement.style.backgroundImage = "radial-gradient(circle at " + (x / window.innerWidth * 100) + "% " + (y / window.innerHeight * 100) + "%, transparent " + innerRadius + "px, rgba(0, 0, 0, " + opacity + ") " + outerRadius + "px)";
|
||||
};
|
||||
|
||||
ElementSpotlight.prototype.easeInOut = function(v) {
|
||||
return (Math.sin((v - 0.5) * Math.PI) + 1) / 2;
|
||||
};
|
||||
|
||||
/*
|
||||
Shine a spotlight on the first element that matches an array of selectors
|
||||
*/
|
||||
ElementSpotlight.prototype.shineSpotlight = function(selectors) {
|
||||
var self = this;
|
||||
function animationLoop(selectors) {
|
||||
// Calculate how far through the animation we are
|
||||
// 0...1 = zoom in
|
||||
// 1...2 = hold
|
||||
// 2...3 = fade out
|
||||
var now = new Date(),
|
||||
t = (now - self.animationStartTime) / ($tw.utils.getAnimationDuration() * 2);
|
||||
t = t >= 3 ? 3 : t;
|
||||
// Query the selector for the target element
|
||||
var targetNode, selectorIndex = 0;
|
||||
while(!targetNode && selectorIndex < selectors.length) {
|
||||
targetNode = self.querySelectorSafe(selectors[selectorIndex]);
|
||||
selectorIndex += 1;
|
||||
}
|
||||
// Position the spotlight if we've got the target
|
||||
if(targetNode) {
|
||||
var rect = targetNode.getBoundingClientRect();
|
||||
var innerRadius, outerRadius, opacity;
|
||||
if(t <= 1) {
|
||||
t = self.easeInOut(t);
|
||||
innerRadius = rect.width / 2 + (window.innerWidth * 2 * (1 - t));
|
||||
outerRadius = rect.width + (window.innerWidth * 3 * (1 - t));
|
||||
opacity = 0.2 + t / 4;
|
||||
} else if(t <= 2) {
|
||||
innerRadius = rect.width / 2;
|
||||
outerRadius = rect.width;
|
||||
opacity = 0.45;
|
||||
} else {
|
||||
t = self.easeInOut(3 - t);
|
||||
innerRadius = rect.width / 2 + (window.innerWidth * 2 * (1 - t));
|
||||
outerRadius = rect.width + (window.innerWidth * 3 * (1 - t));
|
||||
opacity = t / 3;
|
||||
}
|
||||
self.positionSpotlight((rect.left + rect.right) / 2,(rect.top + rect.bottom) / 2,innerRadius,outerRadius,opacity);
|
||||
} else {
|
||||
self.spotlightElement.style.display = "none";
|
||||
}
|
||||
// Call the next frame unless we're at the end
|
||||
if(t <= 3) {
|
||||
window.requestAnimationFrame(function () {
|
||||
animationLoop(selectors);
|
||||
});
|
||||
} else {
|
||||
// End the animation if we've exceeded the time limit
|
||||
self.animationStartTime = undefined;
|
||||
}
|
||||
}
|
||||
this.animationStartTime = new Date();
|
||||
window.requestAnimationFrame(function () {
|
||||
animationLoop(selectors);
|
||||
});
|
||||
};
|
||||
|
||||
exports.ElementSpotlight = ElementSpotlight;
|
||||
|
||||
})();
|
@ -1,3 +1,5 @@
|
||||
const { ElementSpotlight } = require("./element-spotlight");
|
||||
|
||||
/*\
|
||||
title: $:/plugins/tiddlywiki/dynannotate/startup.js
|
||||
type: application/javascript
|
||||
@ -22,18 +24,39 @@ var CONFIG_SELECTION_TRACKER_TITLE = "$:/config/Dynannotate/SelectionTracker/Ena
|
||||
CONFIG_LEGACY_SELECTION_TRACKER_TITLE = "$:/config/Dynannotate/LegacySelectionTracker/Enable";
|
||||
|
||||
var SelectionTracker = require("$:/plugins/tiddlywiki/dynannotate/selection-tracker.js").SelectionTracker,
|
||||
LegacySelectionTracker = require("$:/plugins/tiddlywiki/dynannotate/legacy-selection-tracker.js").LegacySelectionTracker;
|
||||
LegacySelectionTracker = require("$:/plugins/tiddlywiki/dynannotate/legacy-selection-tracker.js").LegacySelectionTracker,
|
||||
ElementSpotlight = require("$:/plugins/tiddlywiki/dynannotate/element-spotlight.js").ElementSpotlight;
|
||||
|
||||
exports.startup = function() {
|
||||
$tw.dynannotate = {};
|
||||
// Setup selection tracker
|
||||
if($tw.wiki.getTiddlerText(CONFIG_SELECTION_TRACKER_TITLE,"yes") === "yes") {
|
||||
$tw.dynannotate.selectionTracker = new SelectionTracker($tw.wiki);
|
||||
}
|
||||
// Setup legacy selection tracker
|
||||
if($tw.wiki.getTiddlerText(CONFIG_LEGACY_SELECTION_TRACKER_TITLE,"yes") === "yes") {
|
||||
$tw.dynannotate.legacySelectionTracker = new LegacySelectionTracker($tw.wiki,{
|
||||
allowBlankSelectionPopup: true
|
||||
});
|
||||
}
|
||||
// Set up the element spotlight
|
||||
$tw.dynannotate.elementSpotlight = new ElementSpotlight();
|
||||
$tw.rootWidget.addEventListener("tm-spotlight-element",function(event) {
|
||||
var selectors = [];
|
||||
if(event.paramObject.selector) {
|
||||
selectors.push(event.paramObject.selector);
|
||||
}
|
||||
$tw.utils.each(Object.keys(event.paramObject).sort(),function(name) {
|
||||
var SELECTOR_PROPERTY_PREFIX = "selector-";
|
||||
if($tw.utils.startsWith(name,SELECTOR_PROPERTY_PREFIX)) {
|
||||
selectors.push(event.paramObject[name]);
|
||||
}
|
||||
});
|
||||
if(event.paramObject["selector-fallback"]) {
|
||||
selectors.push(event.paramObject["selector-fallback"]);
|
||||
}
|
||||
$tw.dynannotate.elementSpotlight.shineSpotlight(selectors);
|
||||
});
|
||||
};
|
||||
|
||||
})();
|
||||
|
@ -42,3 +42,20 @@ tags: [[$:/tags/Stylesheet]]
|
||||
background: #ffa;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.tc-dynannotate-spotlight-wrapper {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: 1000;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tc-dynannotate-spotlight {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: none;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user