Delay hide option (#774)

* Add hideDelay option

* Add _clearSelection

* Use hideDelay

* Prevent repeated delayed selection clears

* Fix popup hide timer being cleared when the cursor is moved into the frame
This commit is contained in:
toasted-nutbread 2020-09-08 19:40:15 -04:00 committed by GitHub
parent ab4dbacc4c
commit b687870a55
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 87 additions and 11 deletions

View File

@ -324,6 +324,7 @@
"alphanumeric", "alphanumeric",
"autoHideResults", "autoHideResults",
"delay", "delay",
"hideDelay",
"length", "length",
"modifier", "modifier",
"deepDomScan", "deepDomScan",
@ -360,6 +361,11 @@
"minimum": 0, "minimum": 0,
"default": 20 "default": 20
}, },
"hideDelay": {
"type": "number",
"minimum": 0,
"default": 0
},
"length": { "length": {
"type": "integer", "type": "integer",
"minimum": 1, "minimum": 1,

View File

@ -473,6 +473,7 @@ class OptionsUtil {
// Options conditions converted to string representations. // Options conditions converted to string representations.
// Added usePopupWindow. // Added usePopupWindow.
// Updated handlebars templates to include "clipboard-image" definition. // Updated handlebars templates to include "clipboard-image" definition.
// Added hideDelay.
for (const {conditionGroups} of options.profiles) { for (const {conditionGroups} of options.profiles) {
for (const {conditions} of conditionGroups) { for (const {conditions} of conditionGroups) {
for (const condition of conditions) { for (const condition of conditions) {
@ -487,6 +488,7 @@ class OptionsUtil {
} }
for (const {options: profileOptions} of options.profiles) { for (const {options: profileOptions} of options.profiles) {
profileOptions.general.usePopupWindow = false; profileOptions.general.usePopupWindow = false;
profileOptions.scanning.hideDelay = 0;
} }
await this._addFieldTemplatesToOptions(options, '/bg/data/anki-field-templates-upgrade-v4.handlebars'); await this._addFieldTemplatesToOptions(options, '/bg/data/anki-field-templates-upgrade-v4.handlebars');
return options; return options;

View File

@ -415,8 +415,16 @@
</div> </div>
<div class="form-group options-advanced"> <div class="form-group options-advanced">
<label for="scan-delay">Scan delay <span class="label-light">(in milliseconds)</span></label> <div class="row">
<input type="number" min="0" id="scan-delay" class="form-control" data-setting="scanning.delay"> <div class="col-xs-6">
<label for="scan-delay">Scan delay <span class="label-light">(in milliseconds)</span></label>
<input type="number" min="0" id="scan-delay" class="form-control" data-setting="scanning.delay">
</div>
<div class="col-xs-6">
<label for="scan-close-delay">Auto-hide delay <span class="label-light">(in milliseconds)</span></label>
<input type="number" min="0" id="scan-close-delay" class="form-control" data-setting="scanning.hideDelay">
</div>
</div>
</div> </div>
<div class="form-group options-advanced"> <div class="form-group options-advanced">

View File

@ -61,7 +61,10 @@ class Frontend {
this._popupFactory = popupFactory; this._popupFactory = popupFactory;
this._allowRootFramePopupProxy = allowRootFramePopupProxy; this._allowRootFramePopupProxy = allowRootFramePopupProxy;
this._popupCache = new Map(); this._popupCache = new Map();
this._popupEventListeners = new EventListenerCollection();
this._updatePopupToken = null; this._updatePopupToken = null;
this._clearSelectionTimer = null;
this._isPointerOverPopup = false;
this._runtimeMessageHandlers = new Map([ this._runtimeMessageHandlers = new Map([
['requestFrontendReadyBroadcast', {async: false, handler: this._onMessageRequestFrontendReadyBroadcast.bind(this)}] ['requestFrontendReadyBroadcast', {async: false, handler: this._onMessageRequestFrontendReadyBroadcast.bind(this)}]
@ -175,7 +178,7 @@ class Frontend {
} }
_onApiClosePopup() { _onApiClosePopup() {
this._textScanner.clearSelection(false); this._clearSelection(false);
} }
_onApiCopySelection() { _onApiCopySelection() {
@ -232,9 +235,11 @@ class Frontend {
} }
_onClearSelection({passive}) { _onClearSelection({passive}) {
this._stopClearSelectionDelayed();
if (this._popup !== null) { if (this._popup !== null) {
this._popup.hide(!passive); this._popup.hide(!passive);
this._popup.clearAutoPlayTimer(); this._popup.clearAutoPlayTimer();
this._isPointerOverPopup = false;
} }
this._updatePendingOptions(); this._updatePendingOptions();
} }
@ -249,24 +254,61 @@ class Frontend {
await this.updateOptions(); await this.updateOptions();
} }
_onSearched({textScanner, type, definitions, sentence, input: {cause}, textSource, optionsContext, error}) { _onSearched({type, definitions, sentence, input: {cause}, textSource, optionsContext, error}) {
const scanningOptions = this._options.scanning;
if (error !== null) { if (error !== null) {
if (yomichan.isExtensionUnloaded) { if (yomichan.isExtensionUnloaded) {
if (textSource !== null && this._options.scanning.modifier !== 'none') { if (textSource !== null && scanningOptions.modifier !== 'none') {
this._showExtensionUnloaded(textSource); this._showExtensionUnloaded(textSource);
} }
} else { } else {
yomichan.logError(error); yomichan.logError(error);
} }
} if (type !== null) {
this._stopClearSelectionDelayed();
const focus = (cause === 'mouse');
this._showContent(textSource, focus, definitions, type, sentence, optionsContext);
} else { } else {
if (type !== null) { if (scanningOptions.autoHideResults) {
const focus = (cause === 'mouse'); this._clearSelectionDelayed(scanningOptions.hideDelay, false);
this._showContent(textSource, focus, definitions, type, sentence, optionsContext);
} }
} }
}
if (type === null && this._options.scanning.autoHideResults) { _onPopupFramePointerOver() {
textScanner.clearSelection(false); this._isPointerOverPopup = true;
this._stopClearSelectionDelayed();
}
_onPopupFramePointerOut() {
this._isPointerOverPopup = false;
}
_clearSelection(passive) {
this._stopClearSelectionDelayed();
this._textScanner.clearSelection(passive);
}
_clearSelectionDelayed(delay, restart, passive) {
if (!this._textScanner.hasSelection()) { return; }
if (delay > 0) {
if (this._clearSelectionTimer !== null && !restart) { return; } // Already running
this._stopClearSelectionDelayed();
this._clearSelectionTimer = setTimeout(() => {
this._clearSelectionTimer = null;
if (this._isPointerOverPopup) { return; }
this._clearSelection(passive);
}, delay);
} else {
this._clearSelection(passive);
}
}
_stopClearSelectionDelayed() {
if (this._clearSelectionTimer !== null) {
clearTimeout(this._clearSelectionTimer);
this._clearSelectionTimer = null;
} }
} }
@ -354,8 +396,12 @@ class Frontend {
this.setDisabledOverride(!this._options.scanning.enableOnSearchPage); this.setDisabledOverride(!this._options.scanning.enableOnSearchPage);
} }
this._textScanner.clearSelection(true); this._clearSelection(true);
this._popupEventListeners.removeAllEventListeners();
this._popup = popup; this._popup = popup;
this._popupEventListeners.on(popup, 'framePointerOver', this._onPopupFramePointerOver.bind(this));
this._popupEventListeners.on(popup, 'framePointerOut', this._onPopupFramePointerOut.bind(this));
this._isPointerOverPopup = false;
} }
async _getDefaultPopup() { async _getDefaultPopup() {

View File

@ -95,6 +95,8 @@ class Popup extends EventDispatcher {
// Public functions // Public functions
prepare() { prepare() {
this._frame.addEventListener('mouseover', this._onFrameMouseOver.bind(this));
this._frame.addEventListener('mouseout', this._onFrameMouseOut.bind(this));
this._frame.addEventListener('mousedown', (e) => e.stopPropagation()); this._frame.addEventListener('mousedown', (e) => e.stopPropagation());
this._frame.addEventListener('scroll', (e) => e.stopPropagation()); this._frame.addEventListener('scroll', (e) => e.stopPropagation());
this._frame.addEventListener('load', this._onFrameLoad.bind(this)); this._frame.addEventListener('load', this._onFrameLoad.bind(this));
@ -207,6 +209,14 @@ class Popup extends EventDispatcher {
// Private functions // Private functions
_onFrameMouseOver() {
this.trigger('framePointerOver', {});
}
_onFrameMouseOut() {
this.trigger('framePointerOut', {});
}
_inject() { _inject() {
let injectPromise = this._injectPromise; let injectPromise = this._injectPromise;
if (injectPromise === null) { if (injectPromise === null) {

View File

@ -144,6 +144,10 @@ class TextScanner extends EventDispatcher {
return clonedTextSource.text(); return clonedTextSource.text();
} }
hasSelection() {
return (this._textSourceCurrent !== null);
}
clearSelection(passive) { clearSelection(passive) {
if (!this._canClearSelection) { return; } if (!this._canClearSelection) { return; }
if (this._textSourceCurrent !== null) { if (this._textSourceCurrent !== null) {