1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-06-28 15:27:43 +00:00
* Fixed: Maintain the mouse hover state for chat when moving the mouse over the context menu from an in-line chat action.
* Fixed: Do not allow mouse interaction with the notice at the bottom of chat when chat if frozen, ensuring that users can interact with the bottom chat line.
* Fixed: Block and Hide Thumbnails buttons not appearing on Directory pages.
* API Fixed: `deep_copy()` converting `null` into `{}`.
* API Changed: Tooltips can now emit hover events.
This commit is contained in:
SirStendec 2019-06-12 21:13:53 -04:00
parent 21ee6fcfb7
commit 734a73eb0e
9 changed files with 119 additions and 26 deletions

View file

@ -151,7 +151,7 @@ ${typeof x[1] === 'string' ? x[1] : JSON.stringify(x[1], null, 4)}`
FrankerFaceZ.Logger = Logger; FrankerFaceZ.Logger = Logger;
const VER = FrankerFaceZ.version_info = { const VER = FrankerFaceZ.version_info = {
major: 4, minor: 3, revision: 1, major: 4, minor: 3, revision: 2,
commit: __git_commit__, commit: __git_commit__,
build: __webpack_hash__, build: __webpack_hash__,
toString: () => toString: () =>

View file

@ -331,6 +331,7 @@ export default class Actions extends Module {
manual: true, manual: true,
live: false, live: false,
html: true, html: true,
hover_events: true,
tooltipClass: 'ffz-action-balloon tw-balloon tw-block tw-border tw-elevation-1 tw-border-radius-small tw-c-background-base', tooltipClass: 'ffz-action-balloon tw-balloon tw-block tw-border tw-elevation-1 tw-border-radius-small tw-c-background-base',
arrowClass: 'tw-balloon__tail tw-overflow-hidden tw-absolute', arrowClass: 'tw-balloon__tail tw-overflow-hidden tw-absolute',
@ -350,10 +351,18 @@ export default class Actions extends Module {
}, },
content, content,
onShow: (t, tip) => onShow: async (t, tip) => {
setTimeout(() => { await tip.waitForDom();
target._ffz_outside = new ClickOutside(tip.outer, destroy) target._ffz_outside = new ClickOutside(tip.outer, destroy)
}), },
onMove: (target, tip, event) => {
this.emit('tooltips:mousemove', target, tip, event)
},
onLeave: (target, tip, event) => {
this.emit('tooltips:leave', target, tip, event);
},
onHide: destroy onHide: destroy
}); });

View file

@ -45,6 +45,7 @@ export default class TooltipProvider extends Module {
delayShow: this.checkDelayShow.bind(this), delayShow: this.checkDelayShow.bind(this),
content: this.process.bind(this), content: this.process.bind(this),
interactive: this.checkInteractive.bind(this), interactive: this.checkInteractive.bind(this),
hover_events: this.checkHoverEvents.bind(this),
popper: { popper: {
placement: 'top', placement: 'top',
modifiers: { modifiers: {
@ -55,6 +56,14 @@ export default class TooltipProvider extends Module {
boundariesElement: container boundariesElement: container
} }
} }
},
onHover: (target, tip, event) => {
this.emit(':hover', target, tip, event)
},
onLeave: (target, tip, event) => {
this.emit(':leave', target, tip, event);
} }
}); });
@ -95,6 +104,16 @@ export default class TooltipProvider extends Module {
return false; return false;
} }
checkHoverEvents(target, tip) {
const type = target.dataset.tooltipType,
handler = this.types[type];
if ( has(handler, 'hover_events') )
return maybe_call(handler.hover_events, null, target, tip);
return false;
}
process(target, tip) { process(target, tip) {
const type = target.dataset.tooltipType || 'text', const type = target.dataset.tooltipType || 'text',
handler = this.types[type]; handler = this.types[type];
@ -103,7 +122,7 @@ export default class TooltipProvider extends Module {
return [ return [
createElement('strong', null, 'Unhandled Tooltip Type'), createElement('strong', null, 'Unhandled Tooltip Type'),
createElement('code', { createElement('code', {
className: 'block pd-t-05 border-t mg-t-05', className: 'tw-block pd-t-05 border-t mg-t-05',
style: { style: {
fontFamily: 'monospace', fontFamily: 'monospace',
textAlign: 'left' textAlign: 'left'

View file

@ -15,6 +15,8 @@ const SCROLL_EVENTS = [
'DOMMouseScroll' 'DOMMouseScroll'
]; ];
let last_id = 0;
export default class Scroller extends Module { export default class Scroller extends Module {
constructor(...args) { constructor(...args) {
super(...args); super(...args);
@ -221,6 +223,11 @@ export default class Scroller extends Module {
this._ffz_installed = true; this._ffz_installed = true;
const inst = this; const inst = this;
this._ffz_accessor = `_ffz_contains_${last_id++}`;
t.on('tooltips:mousemove', this.ffzTooltipHover, this);
t.on('tooltips:leave', this.ffzTooltipLeave, this);
inst.ffz_oldScrollEvent = inst.handleScrollEvent; inst.ffz_oldScrollEvent = inst.handleScrollEvent;
inst.ffz_oldScroll = inst.scrollToBottom; inst.ffz_oldScroll = inst.scrollToBottom;
@ -443,6 +450,28 @@ export default class Scroller extends Module {
this.ffzUpdateKeyTags(); this.ffzUpdateKeyTags();
} }
cls.prototype.ffzTooltipHover = function(target, tip, event) {
if ( target[this._ffz_accessor] == null ) {
const scroller = this.scroll && this.scroll.scrollContent;
target[this._ffz_accessor] = scroller ? scroller.contains(target) : false;
}
if ( target[this._ffz_accessor] )
this.ffzMouseMove(event);
}
cls.prototype.ffzTooltipLeave = function(target) {
if ( this.ffz_outside )
return;
if ( target[this._ffz_accessor] == null ) {
const scroller = this.scroll && this.scroll.scrollContent;
target[this._ffz_accessor] = scroller ? scroller.contains(target) : false;
}
if ( target[this._ffz_accessor] )
this.ffzMouseLeave();
}
// Keyboard Stuff // Keyboard Stuff
@ -509,7 +538,7 @@ export default class Scroller extends Module {
} }
cls.prototype.listFooter = function() { cls.prototype.listFooter = function() {
let msg; let msg, cls = '';
if ( this.state.isPaused ) { if ( this.state.isPaused ) {
const f = t.pause, const f = t.pause,
reason = f === 2 ? t.i18n.t('key.ctrl', 'Ctrl Key') : reason = f === 2 ? t.i18n.t('key.ctrl', 'Ctrl Key') :
@ -523,6 +552,7 @@ export default class Scroller extends Module {
t.i18n.t('key.mouse', 'Mouse Movement'); t.i18n.t('key.mouse', 'Mouse Movement');
msg = t.i18n.t('chat.paused', '(Chat Paused Due to {reason})', {reason}); msg = t.i18n.t('chat.paused', '(Chat Paused Due to {reason})', {reason});
cls = 'ffz--freeze-indicator';
} else if ( this.state.isAutoScrolling ) } else if ( this.state.isAutoScrolling )
return null; return null;
@ -530,7 +560,7 @@ export default class Scroller extends Module {
msg = t.i18n.t('chat.messages-below', 'More messages below.'); msg = t.i18n.t('chat.messages-below', 'More messages below.');
return createElement('div', { return createElement('div', {
className: 'chat-list__list-footer tw-absolute tw-align-items-center tw-border-radius-medium tw-bottom-0 tw-flex tw-full-width tw-justify-content-center tw-pd-05', className: `chat-list__list-footer tw-absolute tw-align-items-center tw-border-radius-medium tw-bottom-0 tw-flex tw-full-width tw-justify-content-center tw-pd-05 ${cls}`,
onClick: this.ffzFastResume onClick: this.ffzFastResume
}, createElement('div', null, msg)); }, createElement('div', null, msg));
} }
@ -615,6 +645,9 @@ export default class Scroller extends Module {
} }
onUnmount(inst) { // eslint-disable-line class-methods-use-this onUnmount(inst) { // eslint-disable-line class-methods-use-this
this.off('tooltips:mousemove', inst.ffzTooltipHover, inst);
this.off('tooltips:leave', inst.ffzTooltipLeave, inst);
window.removeEventListener('keydown', inst.ffzHandleKey); window.removeEventListener('keydown', inst.ffzHandleKey);
window.removeEventListener('keyup', inst.ffzHandleKey); window.removeEventListener('keyup', inst.ffzHandleKey);
} }

View file

@ -23,7 +23,7 @@ export default class Game extends SiteModule {
this.GameHeader = this.fine.define( this.GameHeader = this.fine.define(
'game-header', 'game-header',
n => n.props && n.props.data && n.renderDropsAvailable, n => n.props && n.props.data && n.getBannerImage && n.getCategoryDisplayNameAndFollowButton,
['dir-game-index', 'dir-community', 'dir-game-videos', 'dir-game-clips', 'dir-game-details'] ['dir-game-index', 'dir-community', 'dir-game-videos', 'dir-game-clips', 'dir-game-details']
); );
@ -75,7 +75,7 @@ export default class Game extends SiteModule {
if ( get('data.game', inst.props) == null || ! container || ! container.querySelector ) if ( get('data.game', inst.props) == null || ! container || ! container.querySelector )
return; return;
const buttons = container.querySelector('.tw-flex > .tw-inline-flex'); const buttons = container.querySelector('.tw-flex > .tw-flex-column');
if ( ! buttons ) if ( ! buttons )
return; return;
@ -107,7 +107,7 @@ export default class Game extends SiteModule {
}; };
block_btn = (<button block_btn = (<button
class="tw-mg-l-1 tw-button ffz-directory-toggle-block" class="tw-mg-r-1 tw-button ffz-directory-toggle-block"
onClick={this.generateClickHandler('directory.game.blocked-games', game, update_block)} onClick={this.generateClickHandler('directory.game.blocked-games', game, update_block)}
> >
{block_label = <span class="tw-button__text" />} {block_label = <span class="tw-button__text" />}
@ -116,7 +116,7 @@ export default class Game extends SiteModule {
update_block(); update_block();
hidden_btn = (<button hidden_btn = (<button
class="tw-mg-l-1 tw-button ffz-directory-toggle-thumbnail" class="tw-button ffz-directory-toggle-thumbnail"
onClick={this.generateClickHandler('directory.game.hidden-thumbnails', game, update_hidden)} onClick={this.generateClickHandler('directory.game.hidden-thumbnails', game, update_hidden)}
> >
{hidden_label = <span class="tw-button__text" />} {hidden_label = <span class="tw-button__text" />}

View file

@ -9,11 +9,6 @@
pointer-events: all; pointer-events: all;
} }
.ffz-directory-buttons {
order: 1;
}
// TODO: Color variables // TODO: Color variables
.ffz-directory-toggle-thumbnail { .ffz-directory-toggle-thumbnail {

View file

@ -1,13 +1,13 @@
<template> <template>
<div class="ffz--autocomplete tw-relative"> <div class="ffz--autocomplete tw-relative">
<div class="tw-search-input" data-a-target="dropdown-search-input"> <div class="tw-search-input" data-a-target="dropdown-search-input">
<label v-if="placeholder" :for="'ffz-autocomplete$' + id" class="tw-hide-accessible">{{ placeholder }}</label> <label v-if="placeholder" :for="_id" class="tw-hide-accessible">{{ placeholder }}</label>
<div class="tw-relative"> <div class="tw-relative">
<div v-if="hasIcon" class="tw-absolute tw-align-items-center tw-c-text-alt-2 tw-flex tw-full-height tw-input__icon tw-justify-content-center tw-left-0 tw-top-0 tw-z-default"> <div v-if="hasIcon" class="tw-absolute tw-align-items-center tw-c-text-alt-2 tw-flex tw-full-height tw-input__icon tw-justify-content-center tw-left-0 tw-top-0 tw-z-default">
<figure :class="icon" /> <figure :class="icon" />
</div> </div>
<input <input
:id="'ffz-autocomplete$' + id" :id="_id"
:placeholder="placeholder" :placeholder="placeholder"
:class="[hasIcon ? 'tw-pd-l-3' : 'tw-pd-l-1']" :class="[hasIcon ? 'tw-pd-l-3' : 'tw-pd-l-1']"
v-model="search" v-model="search"
@ -72,6 +72,10 @@ let last_id = 0;
export default { export default {
props: { props: {
inputId: {
type: String,
required: false
},
items: { items: {
type: [Array, Function], type: [Array, Function],
required: false, required: false,
@ -145,6 +149,13 @@ export default {
}, },
computed: { computed: {
_id() {
if ( this.inputId && this.inputId.length )
return this.inputId;
return `ffz-autocomplete$${this.id}`;
},
hasIcon() { hasIcon() {
return this.icon && this.icon.length > 0 return this.icon && this.icon.length > 0
}, },
@ -190,8 +201,8 @@ export default {
}, },
created() { created() {
this.maybeClose = debounce(this.maybeClose, 25); this.maybeClose = debounce(this.maybeClose, 250);
this.updateCache = debounce(this.updateCache, 250, 2); this.updateCache = debounce(this.updateCache, 500, 2);
}, },
methods: { methods: {

View file

@ -307,6 +307,11 @@ export function get(path, object) {
export function deep_copy(object, seen) { export function deep_copy(object, seen) {
if ( object === null )
return null;
else if ( object === undefined )
return undefined;
if ( typeof object !== 'object' ) if ( typeof object !== 'object' )
return object; return object;

View file

@ -241,21 +241,39 @@ export class Tooltip {
tip.add_class = undefined; tip.add_class = undefined;
} }
const interactive = maybe_call(opts.interactive, null, target, tip); const interactive = maybe_call(opts.interactive, null, target, tip),
hover_events = maybe_call(opts.hover_events, null, target, tip);
el.classList.toggle('interactive', interactive || false); el.classList.toggle('interactive', interactive || false);
if ( ! opts.manual ) { if ( ! opts.manual || (hover_events && (opts.onHover || opts.onLeave || opts.onMove)) ) {
el.addEventListener('mouseover', el._ffz_over_handler = () => { if ( hover_events && opts.onMove )
el.addEventListener('mousemove', el._ffz_move_handler = event => {
opts.onMove(target, tip, event);
});
el.addEventListener('mouseover', el._ffz_over_handler = event => {
if ( ! document.contains(target) ) if ( ! document.contains(target) )
this.hide(tip); this.hide(tip);
else if ( maybe_call(opts.interactive, null, target, tip) ) if ( hover_events && opts.onHover )
opts.onHover(target, tip, event);
if ( opts.manual ) {
/* no-op */
} else if ( maybe_call(opts.interactive, null, target, tip) )
this._enter(target); this._enter(target);
else else
this._exit(target); this._exit(target);
}); });
el.addEventListener('mouseout', el._ffz_out_handler = () => this._exit(target)); el.addEventListener('mouseout', el._ffz_out_handler = event => {
if ( hover_events && opts.onLeave )
opts.onLeave(target, tip, event);
if ( ! opts.manual )
this._exit(target);
});
} }
// Assign our content. If there's a Promise, we'll need // Assign our content. If there's a Promise, we'll need
@ -342,6 +360,9 @@ export class Tooltip {
if ( el._ffz_out_handler ) if ( el._ffz_out_handler )
el.removeEventListener('mouseout', el._ffz_out_handler); el.removeEventListener('mouseout', el._ffz_out_handler);
if ( el._ffz_move_handler )
el.removeEventListener('mousemove', el._ffz_move_handler);
el.remove(); el.remove();
tip.outer = el._ffz_out_handler = el._ffz_over_handler = null; tip.outer = el._ffz_out_handler = el._ffz_over_handler = null;
} }