2017-11-14 04:12:10 -05:00
'use strict' ;
// ============================================================================
// Chat Scroller
// ============================================================================
2018-03-14 13:58:04 -04:00
import Twilight from 'site' ;
2017-11-14 04:12:10 -05:00
import Module from 'utilities/module' ;
2019-06-03 19:47:41 -04:00
const SCROLL _EVENTS = [
'touchmove' ,
'scroll' ,
'wheel' ,
'mousewheel' ,
'DOMMouseScroll'
] ;
2017-11-14 04:12:10 -05:00
2019-06-12 21:13:53 -04:00
let last _id = 0 ;
2017-11-14 04:12:10 -05:00
export default class Scroller extends Module {
constructor ( ... args ) {
super ( ... args ) ;
this . inject ( 'settings' ) ;
this . inject ( 'i18n' ) ;
this . inject ( 'chat' ) ;
this . inject ( 'site.fine' ) ;
2018-03-03 16:38:50 -05:00
this . inject ( 'site.web_munch' ) ;
2017-11-14 04:12:10 -05:00
this . ChatScroller = this . fine . define (
'chat-scroller' ,
2019-09-07 14:34:40 -04:00
n => n . saveScrollRef && n . handleScrollEvent && ! n . renderLines && n . resume ,
2018-03-14 13:58:04 -04:00
Twilight . CHAT _ROUTES
2017-11-14 04:12:10 -05:00
) ;
this . settings . add ( 'chat.scroller.freeze' , {
default : 0 ,
ui : {
2019-09-12 13:11:08 -04:00
path : 'Chat > Behavior >> Scrolling' ,
2019-06-03 19:47:41 -04:00
title : 'Pause Chat Scrolling' ,
2020-04-22 14:30:34 -04:00
description : 'Automatically stop chat from scrolling when moving the mouse over it or holding a key.\n\n**Note:** Scrolling up in chat will always prevent automatic scrolling, regardless of this setting.' ,
2017-11-14 04:12:10 -05:00
component : 'setting-select-box' ,
data : [
2020-09-10 14:02:39 -04:00
{ value : 0 , title : 'Use Twitch Setting' } ,
2017-11-14 04:12:10 -05:00
{ value : 1 , title : 'On Hover' } ,
{ value : 2 , title : 'When Ctrl is Held' } ,
{ value : 3 , title : 'When Meta is Held' } ,
{ value : 4 , title : 'When Alt is Held' } ,
{ value : 5 , title : 'When Shift is Held' } ,
{ value : 6 , title : 'Ctrl or Hover' } ,
{ value : 7 , title : 'Meta or Hover' } ,
{ value : 8 , title : 'Alt or Hover' } ,
{ value : 9 , title : 'Shift or Hover' }
]
}
} ) ;
2018-07-03 18:18:39 -04:00
2019-06-03 19:47:41 -04:00
this . settings . add ( 'chat.scroller.freeze-requires-hover' , {
default : true ,
ui : {
path : 'Chat > Behavior >> Scrolling' ,
title : 'Require the mouse to be over chat to freeze with a hotkey.' ,
component : 'setting-check-box'
}
} ) ;
this . settings . add ( 'chat.scroller.hover-delay' , {
default : 750 ,
ui : {
path : 'Chat > Behavior >> Scrolling' ,
title : 'Hover Timeout' ,
description : 'Chat will only remain frozen due to mouse hovering for this long after the mouse stops moving.' ,
component : 'setting-combo-box' ,
data : [
{ value : 250 , title : '0.25 Seconds' } ,
{ value : 500 , title : '0.50 Seconds' } ,
{ value : 750 , title : '0.75 Seconds' } ,
{ value : 1000 , title : '1 Second' } ,
{ value : 2500 , title : '2.5 Seconds' } ,
{ value : 5000 , title : '5 Seconds' }
]
}
} ) ;
2018-07-03 18:18:39 -04:00
this . settings . add ( 'chat.scroller.smooth-scroll' , {
default : 0 ,
ui : {
2018-07-09 21:35:31 -04:00
path : 'Chat > Behavior >> Scrolling' ,
2018-07-03 18:18:39 -04:00
title : 'Smooth Scrolling' ,
2021-02-16 17:40:27 -05:00
description : '**Note:** This may not behave well depending on your browser. Disable this if you encounter issues.\n\nSmoothly slide new chat messages into view. Speed will increase as necessary to keep up with chat.' ,
2018-07-03 18:18:39 -04:00
component : 'setting-select-box' ,
data : [
{ value : 0 , title : 'Disabled' } ,
{ value : 1 , title : 'Slow' } ,
{ value : 2 , title : 'Medium' } ,
{ value : 3 , title : 'Fast' } ,
{ value : 4 , title : 'Very Fast' }
]
}
} ) ;
2017-11-14 04:12:10 -05:00
}
2019-05-16 14:46:26 -04:00
updateUseKeys ( ) {
const old _use = this . use _keys ;
this . use _keys = false ;
for ( const act of this . chat . context . get ( 'chat.actions.inline' ) )
2019-06-03 19:47:41 -04:00
if ( act && act . display && act . display . keys ) {
2019-05-16 14:46:26 -04:00
this . use _keys = true ;
2019-06-03 19:47:41 -04:00
break ;
}
2022-12-07 16:52:07 -05:00
for ( const act of this . chat . context . get ( 'chat.actions.hover' ) )
if ( act && act . display && act . display . keys ) {
this . use _keys = true ;
break ;
}
2019-05-16 14:46:26 -04:00
if ( this . use _keys !== old _use ) {
for ( const inst of this . ChatScroller . instances )
inst && inst . ffzUpdateKeys && inst . ffzUpdateKeys ( ) ;
}
}
2019-06-03 19:47:41 -04:00
async onEnable ( ) {
this . on ( 'i18n:update' , ( ) => this . ChatScroller . forceUpdate ( ) ) ;
this . chat . context . on ( 'changed:chat.actions.inline' , this . updateUseKeys , this ) ;
this . updateUseKeys ( ) ;
this . pause _hover = this . chat . context . get ( 'chat.scroller.freeze-requires-hover' ) ;
this . chat . context . on ( 'changed:chat.scroller.freeze-requires-hover' , val => {
this . pause _hover = val ;
2017-11-14 04:12:10 -05:00
for ( const inst of this . ChatScroller . instances )
2019-06-03 19:47:41 -04:00
inst . ffzMaybeUnpause ( ) ;
} )
2017-11-14 04:12:10 -05:00
2019-06-03 19:47:41 -04:00
this . pause _delay = this . chat . context . get ( 'chat.scroller.hover-delay' ) ;
this . chat . context . on ( 'changed:chat.scroller.hover-delay' , val => {
this . pause _delay = val ;
for ( const inst of this . ChatScroller . instances )
inst . ffzMaybeUnpause ( ) ;
} )
this . pause = this . chat . context . get ( 'chat.scroller.freeze' ) ;
2017-11-14 04:12:10 -05:00
this . chat . context . on ( 'changed:chat.scroller.freeze' , val => {
2019-06-03 19:47:41 -04:00
this . pause = val ;
2017-11-14 04:12:10 -05:00
2019-06-03 19:47:41 -04:00
for ( const inst of this . ChatScroller . instances )
inst . ffzMaybeUnpause ( ) ;
2017-11-14 04:12:10 -05:00
} ) ;
2021-02-16 17:40:27 -05:00
this . smooth _scroll = this . chat . context . get ( 'chat.scroller.smooth-scroll' ) ;
2018-07-03 18:18:39 -04:00
this . chat . context . on ( 'changed:chat.scroller.smooth-scroll' , val => {
2019-06-03 19:47:41 -04:00
this . smooth _scroll = val ;
2018-07-03 18:18:39 -04:00
2018-07-03 18:39:00 -04:00
for ( const inst of this . ChatScroller . instances )
2018-07-03 18:18:39 -04:00
inst . ffzSetSmoothScroll ( val ) ;
} ) ;
2019-06-03 19:47:41 -04:00
const t = this ,
React = await this . web _munch . findModule ( 'react' ) ,
createElement = React && React . createElement ;
if ( ! createElement )
return t . log . warn ( ` Unable to get React. ` ) ;
2017-11-14 04:12:10 -05:00
this . ChatScroller . ready ( ( cls , instances ) => {
2019-06-03 19:47:41 -04:00
const old _catch = cls . prototype . componentDidCatch ,
2018-03-03 16:38:50 -05:00
old _render = cls . prototype . render ;
// Try catching errors. With any luck, maybe we can
// recover from the error when we re-build?
cls . prototype . componentDidCatch = function ( err , info ) {
// Don't log infinitely if stuff gets super screwed up.
const errs = this . state . ffz _errors || 0 ;
if ( errs < 100 ) {
this . setState ( {
ffz _errors : errs + 1 ,
ffz _total _errors : ( this . state . ffz _total _errors || 0 ) + 1
} ) ;
2018-04-11 17:05:31 -04:00
t . log . capture ( err , { extra : info } ) ;
2018-03-03 16:38:50 -05:00
t . log . info ( 'Error within Chat' , err , info , errs ) ;
}
if ( old _catch )
return old _catch . call ( this , err , info ) ;
}
cls . prototype . ffzZeroErrors = function ( ) {
this . setState ( { ffz _errors : 0 } ) ;
}
cls . prototype . render = function ( ) {
if ( this . state . ffz _errors > 0 ) {
let timer ;
const auto = this . state . ffz _total _errors < 10 ,
handler = ( ) => {
clearTimeout ( timer ) ;
this . ffzZeroErrors ( ) ;
}
if ( auto )
timer = setTimeout ( handler , 250 ) ;
2018-04-01 18:24:08 -04:00
if ( ! createElement )
2018-03-03 16:38:50 -05:00
return null ;
2018-04-01 18:24:08 -04:00
return createElement ( 'div' , {
2018-09-25 18:37:14 -04:00
className : 'tw-border-l tw-c-background-alt-2 tw-c-text-base tw-full-width tw-full-height tw-align-items-center tw-flex tw-flex-column tw-justify-content-center tw-relative'
2018-03-03 16:38:50 -05:00
} , [
2018-04-01 18:24:08 -04:00
createElement ( 'div' , { className : 'tw-mg-b-1' } , 'There was an error displaying chat.' ) ,
! auto && createElement ( 'button' , {
2018-03-03 16:38:50 -05:00
className : 'tw-button' ,
onClick : handler
2018-04-01 18:24:08 -04:00
} , createElement ( 'span' , { className : 'tw-button__text' } , 'Try Again' ) )
2018-03-03 16:38:50 -05:00
] ) ;
2020-07-14 15:20:09 -04:00
} else {
const out = old _render . call ( this ) ;
try {
const children = out ? . props ? . children ? . props ? . children ,
child = children ? . [ 2 ] ;
2020-09-10 14:02:39 -04:00
if ( child ? . type ? . displayName === 'ChatPausedFooter' )
2020-07-14 15:20:09 -04:00
children [ 2 ] = this . ffzRenderFooter ( ) ;
} catch ( err ) { /* no-op */ }
return out ;
}
2018-03-03 16:38:50 -05:00
}
2017-11-14 04:12:10 -05:00
2019-06-03 19:47:41 -04:00
cls . prototype . ffzInstallHandler = function ( ) {
if ( this . _ffz _installed )
2017-11-14 04:12:10 -05:00
return ;
2019-06-03 19:47:41 -04:00
this . _ffz _installed = true ;
const inst = this ;
2020-09-10 14:02:39 -04:00
inst . ffz _oldScrollEvent = inst . handleScrollEvent ;
inst . ffz _oldScroll = inst . scrollToBottom ;
2021-02-10 16:53:10 -05:00
inst . ffz _acting = false ;
2019-06-13 22:56:50 -04:00
inst . ffz _outside = true ;
inst . _ffz _accessor = ` _ffz_contains_ ${ last _id ++ } ` ;
2019-06-12 21:13:53 -04:00
t . on ( 'tooltips:mousemove' , this . ffzTooltipHover , this ) ;
t . on ( 'tooltips:leave' , this . ffzTooltipLeave , this ) ;
2021-02-16 17:40:27 -05:00
inst . scrollToBottom = function ( ) {
if ( inst . _ffz _scroll _request )
return ;
inst . _ffz _scroll _request = requestAnimationFrame ( inst . _scrollToBottom ) ;
}
inst . _scrollToBottom = function ( ) {
inst . _ffz _scroll _request = null ;
//inst._ffz_snapshot = null;
if ( ! inst . state . isPaused ) {
if ( inst . ffz _smooth _scroll && ! inst . _ffz _one _fast _scroll )
inst . ffzSmoothScroll ( ) ;
else {
inst . _ffz _one _fast _scroll = false ;
inst . ffz _oldScroll ( ) ;
}
}
}
2019-06-03 19:47:41 -04:00
inst . handleScrollEvent = function ( event ) {
2020-09-10 14:02:39 -04:00
if ( ! inst . scrollRef ? . scrollContent )
2019-06-03 19:47:41 -04:00
return ;
2017-11-14 04:12:10 -05:00
2019-06-03 19:47:41 -04:00
if ( ! ( event . which > 0 || event . type === 'mousewheel' || event . type === 'wheel' || event . type === 'touchmove' ) )
return ;
2018-07-03 18:39:00 -04:00
2019-06-03 19:47:41 -04:00
// How far are we scrolled up?
2020-09-10 14:02:39 -04:00
const scroller = inst . scrollRef . scrollContent ,
offset = scroller . scrollHeight - scroller . scrollTop - scroller . offsetHeight ,
scrolled _up = offset > 10 ;
2018-07-03 18:39:00 -04:00
2020-09-10 14:02:39 -04:00
if ( scrolled _up !== inst . state . ffz _scrolled _up )
inst . setState ( { ffz _scrolled _up : scrolled _up } ) ;
2018-07-03 18:39:00 -04:00
2020-09-10 14:02:39 -04:00
if ( inst . state . isPaused && scrolled _up )
inst . setLoadMoreEnabled ( true ) ;
2018-07-13 14:32:12 -04:00
2020-09-10 14:02:39 -04:00
if ( inst . state . isPaused && ! scrolled _up )
inst . ffzMaybeUnpause ( ) ;
else if ( ! inst . state . isPaused && scrolled _up )
inst . pause ( ) ;
2019-06-03 19:47:41 -04:00
}
2018-07-03 18:18:39 -04:00
2019-06-03 19:47:41 -04:00
const old _resume = inst . resume ;
2018-07-03 18:39:00 -04:00
2019-06-03 19:47:41 -04:00
inst . ffzFastResume = function ( ) {
inst . _ffz _one _fast _scroll = true ;
inst . resume ( ) ;
2018-07-03 18:18:39 -04:00
}
2018-07-03 18:39:00 -04:00
2019-06-03 19:47:41 -04:00
inst . resume = function ( ) {
2020-09-10 14:02:39 -04:00
inst . setState ( { ffz _scrolled _up : false } ) ;
old _resume . call ( this ) ;
2019-06-03 19:47:41 -04:00
}
2018-07-03 18:39:00 -04:00
2019-06-03 19:47:41 -04:00
// Event Registration
2018-07-03 18:39:00 -04:00
2019-06-03 19:47:41 -04:00
const Mousetrap = t . web _munch . getModule ( 'mousetrap' ) || window . Mousetrap ;
if ( Mousetrap != null ) {
Mousetrap . unbind ( 'alt' , 'keydown' ) ;
Mousetrap . unbind ( 'alt' , 'keyup' ) ;
}
2018-07-03 18:39:00 -04:00
2019-06-03 19:47:41 -04:00
inst . ffzHandleKey = inst . ffzHandleKey . bind ( inst ) ;
2019-06-08 17:35:48 -04:00
window . addEventListener ( 'keydown' , inst . ffzHandleKey ) ;
window . addEventListener ( 'keyup' , inst . ffzHandleKey ) ;
2017-11-16 15:54:58 -05:00
2019-06-03 19:47:41 -04:00
inst . hoverPause = inst . ffzMouseMove . bind ( inst ) ;
inst . hoverResume = inst . ffzMouseLeave . bind ( inst ) ;
2017-11-16 15:54:58 -05:00
2019-06-03 19:47:41 -04:00
const node = t . fine . getChildNode ( inst ) ;
if ( node )
node . addEventListener ( 'mousemove' , inst . hoverPause ) ;
2019-04-30 15:18:29 -04:00
2020-09-10 14:02:39 -04:00
const scroller = this . scrollRef ? . scrollContent ;
2019-06-03 19:47:41 -04:00
if ( scroller ) {
for ( const event of SCROLL _EVENTS ) {
scroller . removeEventListener ( event , inst . ffz _oldScrollEvent ) ;
scroller . addEventListener ( event , inst . handleScrollEvent ) ;
2018-07-03 18:18:39 -04:00
}
2018-01-15 20:40:54 -05:00
}
2019-06-03 19:47:41 -04:00
// We need to refresh the element to make sure it's using the correct
// event handlers for mouse enter / leave.
inst . forceUpdate ( ) ;
2019-10-13 00:41:09 -04:00
t . emit ( 'site:dom-update' , 'chat-scroller' , inst ) ;
2019-06-03 19:47:41 -04:00
}
2019-04-30 15:18:29 -04:00
2021-02-16 17:40:27 -05:00
cls . prototype . ffzSmoothScroll = function ( ) {
if ( this . _ffz _smooth _animation )
cancelAnimationFrame ( this . _ffz _smooth _animation ) ;
this . ffz _is _smooth _scrolling = true ;
// Step setting value is # pixels to scroll per 10ms.
// 1 is pretty slow, 2 medium, 3 fast, 4 very fast.
let step = this . ffz _smooth _scroll ,
old _time = performance . now ( ) ;
const scroll _content = this . scrollRef ? . scrollContent ;
if ( ! scroll _content )
return ;
const target _top = scroll _content . scrollHeight - scroll _content . clientHeight ,
difference = target _top - scroll _content . scrollTop ;
// If we are falling behind speed us up
if ( difference > scroll _content . clientHeight ) {
// we are a full scroll away, just jump there
step = difference ;
} else if ( difference > 200 ) {
// we are starting to fall behind, speed it up a bit
step += step * Math . floor ( difference / 200 ) ;
}
const smoothAnimation = ( ) => {
4.25.0
* Fixed: Smooth Scrolling no longer causing chat to scroll. (Closes #1068)
* Fixed: Issue with users using certain external stylesheets causing chat messages to become impossible to read on mouse hover. (Closes #1066)
* Fixed: Issues with badge sorting causing some badges to be overridden when they shouldn't be.
* Changed: Improve caching of badge data, such that re-rendering chat lines requires less computation.
* Changed: Refactor how chat lines listen for settings changes to reduce code duplication.
* Changed: Refactor how chat lines are invalidated to minimize work when changing settings.
* API Added: `chat:rerender-lines` event that, when emitted, causes all chat lines to be re-rendered.
* API Added: `chat:update-line-tokens` event that, when emitted, causes all chat lines to have their tokens invalidated and recalculated.
* API Added: `chat:update-line-badges` event that, when emitted, causes all chat lines to have their cached badges invalidated and recalculated.
* API Changed: `chat:update-lines-by-user` now has extra properties for separately invalidating tokens or badges. The full signature is `chat:update-lines-by-user(id, login, invalidate_tokens = true, invalidate_badges = true)`
2021-06-23 16:08:57 -04:00
if ( this . state . isPaused )
2021-02-16 17:40:27 -05:00
return this . ffz _is _smooth _scrolling = false ;
// See how much time has passed to get a step based off the delta
const current _time = performance . now ( ) ,
delta = current _time - old _time ,
current _step = step * ( delta / 10 ) ;
// we need to move at least one full pixel for scrollTop to do anything in this delta.
if ( current _step >= 1 ) {
const scroll _top = scroll _content . scrollTop ,
next _top = scroll _top + current _step ,
target _top = scroll _content . scrollHeight - scroll _content . clientHeight ;
old _time = current _time ;
if ( next _top < target _top ) {
scroll _content . scrollTop = scroll _top + current _step ;
this . _ffz _smooth _animation = requestAnimationFrame ( smoothAnimation ) ;
} else {
// We've reached the bottom.
scroll _content . scrollTop = target _top ;
this . ffz _is _smooth _scrolling = false ;
}
} else {
// The frame happened so quick since last update that we haven't moved a full pixel.
// Just wait.
this . _ffz _smooth _animation = requestAnimationFrame ( smoothAnimation ) ;
}
}
smoothAnimation ( ) ;
}
2019-06-03 19:47:41 -04:00
cls . prototype . ffzSetSmoothScroll = function ( value ) {
this . ffz _smooth _scroll = value ;
this . ffzMaybeUnpause ( ) ;
}
2017-11-16 15:54:58 -05:00
2019-06-03 19:47:41 -04:00
// Event Handling
2017-11-22 15:39:38 -05:00
2019-06-03 19:47:41 -04:00
cls . prototype . ffzReadKeysFromEvent = function ( event ) {
if ( event . altKey === this . ffz _alt &&
event . shiftKey === this . ffz _shift &&
event . ctrlKey === this . ffz _ctrl &&
event . metaKey === this . ffz _meta )
return false ;
2017-11-16 15:54:58 -05:00
2019-06-03 19:47:41 -04:00
this . ffz _alt = event . altKey ;
this . ffz _shift = event . shiftKey ;
this . ffz _ctrl = event . ctrlKey ;
this . ffz _meta = event . metaKey ;
return true ;
2017-11-14 04:12:10 -05:00
}
2019-06-03 19:47:41 -04:00
cls . prototype . ffzHandleKey = function ( event ) {
if ( ! this . ffzReadKeysFromEvent ( event ) )
2017-11-14 04:12:10 -05:00
return ;
2019-06-03 19:47:41 -04:00
this . ffzUpdateKeyTags ( ) ;
2017-11-14 04:12:10 -05:00
2020-09-10 14:02:39 -04:00
if ( ( t . pause _hover && this . ffz _outside ) || this . ffzGetMode ( ) < 2 )
2019-06-03 19:47:41 -04:00
return ;
2017-11-14 04:12:10 -05:00
2019-06-03 19:47:41 -04:00
const should _pause = this . ffzShouldBePaused ( ) ,
changed = should _pause !== this . state . isPaused ;
2017-11-14 04:12:10 -05:00
2019-06-03 19:47:41 -04:00
if ( changed )
if ( should _pause ) {
this . pause ( ) ;
2020-09-10 14:02:39 -04:00
if ( ! this . state . ffz _scrolled _up )
this . setLoadMoreEnabled ( false ) ;
2019-06-03 19:47:41 -04:00
} else
this . resume ( ) ;
2017-11-14 04:12:10 -05:00
}
2019-06-03 19:47:41 -04:00
cls . prototype . ffzInstallHoverTimer = function ( ) {
if ( this . _ffz _hover _timer )
return ;
2017-11-14 04:12:10 -05:00
2019-06-03 19:47:41 -04:00
this . _ffz _hover _timer = setInterval ( ( ) => {
if ( this . state . isPaused && this . ffzShouldBePaused ( ) )
return ;
2017-11-14 04:12:10 -05:00
2019-06-03 19:47:41 -04:00
this . ffzMaybeUnpause ( ) ;
} , 50 ) ;
}
2017-11-14 04:12:10 -05:00
2019-06-03 19:47:41 -04:00
cls . prototype . ffzMouseMove = function ( event ) {
this . ffz _last _move = Date . now ( ) ;
const was _outside = this . ffz _outside ;
this . ffz _outside = false ;
2017-11-16 15:54:58 -05:00
2019-06-03 19:47:41 -04:00
if ( this . _ffz _outside _timer ) {
clearTimeout ( this . _ffz _outside _timer ) ;
this . _ffz _outside _timer = null ;
2017-11-14 04:12:10 -05:00
}
2019-06-03 19:47:41 -04:00
const keys _updated = this . ffzReadKeysFromEvent ( event ) ;
2017-11-14 04:12:10 -05:00
2019-06-03 19:47:41 -04:00
// If nothing changed, stop processing.
if ( ! keys _updated && event . screenX === this . ffz _sx && event . screenY === this . ffz _sy ) {
if ( was _outside )
this . ffzUpdateKeyTags ( ) ;
2017-11-14 04:12:10 -05:00
2019-06-03 19:47:41 -04:00
return ;
2017-11-14 04:12:10 -05:00
}
2019-06-03 19:47:41 -04:00
this . ffz _sx = event . screenX ;
this . ffz _sy = event . screenY ;
2017-11-14 04:12:10 -05:00
2019-06-03 19:47:41 -04:00
if ( keys _updated || was _outside )
this . ffzUpdateKeyTags ( ) ;
2017-11-14 04:12:10 -05:00
2019-06-03 19:47:41 -04:00
const should _pause = this . ffzShouldBePaused ( ) ,
changed = should _pause !== this . state . isPaused ;
2017-11-14 04:12:10 -05:00
2019-06-03 19:47:41 -04:00
if ( changed )
if ( should _pause ) {
this . pause ( ) ;
this . ffzInstallHoverTimer ( ) ;
2020-09-10 14:02:39 -04:00
if ( ! this . state . ffz _scrolled _up )
this . setLoadMoreEnabled ( false ) ;
2019-05-16 14:46:26 -04:00
2019-06-03 19:47:41 -04:00
} else
this . resume ( ) ;
}
2017-11-14 04:12:10 -05:00
2019-06-03 19:47:41 -04:00
cls . prototype . ffzMouseLeave = function ( ) {
this . ffz _outside = true ;
if ( this . _ffz _outside _timer )
clearTimeout ( this . _ffz _outside _timer ) ;
2017-11-14 04:12:10 -05:00
2019-06-03 19:47:41 -04:00
this . _ffz _outside _timer = setTimeout ( ( ) => this . ffzMaybeUnpause ( ) , 64 ) ;
this . ffzUpdateKeyTags ( ) ;
2017-11-14 04:12:10 -05:00
}
2019-06-12 21:13:53 -04:00
cls . prototype . ffzTooltipHover = function ( target , tip , event ) {
if ( target [ this . _ffz _accessor ] == null ) {
2020-09-10 14:02:39 -04:00
const scroller = this . scrollRef ? . scrollContent ;
2019-06-12 21:13:53 -04:00
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 ) {
2020-09-10 14:02:39 -04:00
const scroller = this . scrollRef ? . scrollContent ;
2019-06-12 21:13:53 -04:00
target [ this . _ffz _accessor ] = scroller ? scroller . contains ( target ) : false ;
}
if ( target [ this . _ffz _accessor ] )
this . ffzMouseLeave ( ) ;
}
2017-11-14 04:12:10 -05:00
2019-06-03 19:47:41 -04:00
// Keyboard Stuff
2021-02-10 16:53:10 -05:00
cls . prototype . ffzUpdateActing = function ( ) {
if ( ! this . _ffz _key _frame _acting )
this . _ffz _key _frame _acting = requestAnimationFrame ( ( ) => this . ffz _updateActing ( ) ) ;
}
cls . prototype . ffz _updateActing = function ( ) {
this . _ffz _key _frame _acting = null ;
if ( ! this . scrollRef ? . root )
return ;
this . scrollRef . root . dataset . acting = this . ffz _acting ;
}
2019-06-03 19:47:41 -04:00
cls . prototype . ffzUpdateKeyTags = function ( ) {
if ( ! this . _ffz _key _frame )
this . _ffz _key _frame = requestAnimationFrame ( ( ) => this . ffz _updateKeyTags ( ) ) ;
2019-05-16 14:46:26 -04:00
}
2019-06-03 19:47:41 -04:00
cls . prototype . ffz _updateKeyTags = function ( ) {
this . _ffz _key _frame = null ;
2019-05-16 14:46:26 -04:00
if ( ! t . use _keys && this . ffz _use _keys === t . use _keys )
return ;
2020-09-10 14:02:39 -04:00
if ( ! this . scrollRef ? . root )
2019-05-16 14:46:26 -04:00
return ;
this . ffz _use _keys = t . use _keys ;
2020-09-10 14:02:39 -04:00
this . scrollRef . root . classList . toggle ( 'ffz--keys' , t . use _keys ) ;
2019-05-16 14:46:26 -04:00
2020-09-10 14:02:39 -04:00
const ds = this . scrollRef . root . dataset ;
2019-05-16 14:46:26 -04:00
if ( ! t . use _keys ) {
delete ds . alt ;
delete ds . ctrl ;
delete ds . shift ;
delete ds . meta ;
} else {
ds . alt = ! this . ffz _outside && this . ffz _alt ;
ds . ctrl = ! this . ffz _outside && this . ffz _ctrl ;
ds . shift = ! this . ffz _outside && this . ffz _shift ;
ds . meta = ! this . ffz _outside && this . ffz _meta ;
}
}
2017-11-14 04:12:10 -05:00
2019-06-03 19:47:41 -04:00
// Pause Stuff
2019-05-16 14:46:26 -04:00
2019-06-03 19:47:41 -04:00
cls . prototype . ffzShouldBePaused = function ( since ) {
if ( since == null )
since = Date . now ( ) - this . ffz _last _move ;
2019-05-16 14:46:26 -04:00
2020-09-10 14:02:39 -04:00
if ( this . state . ffz _scrolled _up )
return true ;
const mode = this . ffzGetMode ( ) ,
2019-06-03 19:47:41 -04:00
require _hover = t . pause _hover ;
2020-09-10 14:02:39 -04:00
return ( ! require _hover || ! this . ffz _outside ) && (
2021-02-10 16:53:10 -05:00
( this . ffz _acting ) ||
2019-06-03 19:47:41 -04:00
( this . ffz _ctrl && ( mode === 2 || mode === 6 ) ) ||
( this . ffz _meta && ( mode === 3 || mode === 7 ) ) ||
( this . ffz _alt && ( mode === 4 || mode === 8 ) ) ||
( this . ffz _shift && ( mode === 5 || mode === 9 ) ) ||
( ! this . ffz _outside && since < t . pause _delay && ( mode === 1 || mode > 5 ) )
) ;
2020-09-10 14:02:39 -04:00
}
2017-11-14 04:12:10 -05:00
2020-09-10 14:02:39 -04:00
cls . prototype . ffzGetMode = function ( ) {
let mode = t . pause ;
if ( mode === 0 && ! this . props . mouseoverPausingDisabled ) {
const setting = this . props . pauseSetting ;
if ( setting === 'MOUSEOVER_ALTKEY' )
mode = 8 ;
else if ( setting === 'MOUSEOVER' )
mode = 1 ;
else if ( setting === 'ALTKEY' )
mode = 4 ;
}
return mode ;
2019-06-03 19:47:41 -04:00
}
2017-11-14 04:12:10 -05:00
2019-06-03 19:47:41 -04:00
cls . prototype . ffzMaybeUnpause = function ( ) {
if ( this . state . isPaused && ! this . _ffz _unpause _frame )
this . _ffz _unpause _frame = requestAnimationFrame ( ( ) => {
this . _ffz _unpause _frame = null ;
if ( this . state . isPaused && ! this . ffzShouldBePaused ( ) )
this . resume ( ) ;
} ) ;
}
2019-05-16 14:46:26 -04:00
2020-07-14 15:20:09 -04:00
cls . prototype . ffzRenderFooter = function ( ) {
2019-06-12 21:13:53 -04:00
let msg , cls = '' ;
2020-09-10 14:02:39 -04:00
if ( this . state . ffz _scrolled _up )
msg = t . i18n . t ( 'chat.messages-below' , 'Chat Paused Due to Scroll' ) ;
else if ( this . state . isPaused ) {
const f = this . ffzGetMode ( ) ,
2021-02-10 16:53:10 -05:00
reason = this . ffz _acting ? t . i18n . t ( 'chat.acting' , 'Taking Action' ) :
f === 2 ? t . i18n . t ( 'key.ctrl' , 'Ctrl Key' ) :
f === 3 ? t . i18n . t ( 'key.meta' , 'Meta Key' ) :
f === 4 ? t . i18n . t ( 'key.alt' , 'Alt Key' ) :
f === 5 ? t . i18n . t ( 'key.shift' , 'Shift Key' ) :
f === 6 ? t . i18n . t ( 'key.ctrl_mouse' , 'Ctrl or Mouse' ) :
f === 7 ? t . i18n . t ( 'key.meta_mouse' , 'Meta or Mouse' ) :
f === 8 ? t . i18n . t ( 'key.alt_mouse' , 'Alt or Mouse' ) :
f === 9 ? t . i18n . t ( 'key.shift_mouse' , 'Shift or Mouse' ) :
t . i18n . t ( 'key.mouse' , 'Mouse Movement' ) ;
2019-06-03 19:47:41 -04:00
2020-04-03 19:30:28 -04:00
msg = t . i18n . t ( 'chat.paused' , 'Chat Paused Due to {reason}' , { reason } ) ;
2019-06-12 21:13:53 -04:00
cls = 'ffz--freeze-indicator' ;
2019-06-03 19:47:41 -04:00
2020-09-10 14:02:39 -04:00
} else
2020-07-14 15:20:09 -04:00
return createElement ( 'div' , {
className : ` chat-scroll-footer__placeholder tw-flex tw-justify-content-center tw-relative ${ cls } `
} , null ) ;
2017-11-14 04:12:10 -05:00
2019-06-03 19:47:41 -04:00
return createElement ( 'div' , {
2020-07-14 15:20:09 -04:00
className : ` chat-scroll-footer__placeholder tw-flex tw-justify-content-center tw-relative ${ cls } `
} , createElement ( 'div' , {
className : 'tw-absolute tw-border-radius-medium tw-bottom-0 tw-c-background-overlay tw-c-text-overlay tw-mg-b-1'
} , createElement ( 'button' , {
2021-02-16 17:40:27 -05:00
className : 'tw-align-items-center tw-align-middle tw-border-bottom-left-radius-medium tw-border-bottom-right-radius-medium tw-border-top-left-radius-medium tw-border-top-right-radius-medium ffz-core-button ffz-core-button--overlay ffz-core-button--text tw-inline-flex tw-interactive tw-justify-content-center tw-overflow-hidden tw-relative' ,
2020-07-14 15:20:09 -04:00
'data-a-target' : 'chat-list-footer' ,
onClick : this . ffzFastResume
} , createElement ( 'div' , {
2021-02-16 17:40:27 -05:00
className : 'tw-align-items-center ffz-core-button-label tw-flex tw-flex-grow-0'
2020-07-14 15:20:09 -04:00
} , createElement ( 'div' , {
className : 'tw-flex-grow-0'
} , msg ) ) ) ) ) ;
2018-07-03 18:18:39 -04:00
}
2019-06-03 19:47:41 -04:00
// Do the thing~
2018-07-03 18:18:39 -04:00
2017-11-14 04:12:10 -05:00
for ( const inst of instances )
this . onMount ( inst ) ;
} ) ;
this . ChatScroller . on ( 'mount' , this . onMount , this ) ;
this . ChatScroller . on ( 'unmount' , this . onUnmount , this ) ;
}
onMount ( inst ) {
2019-06-03 19:47:41 -04:00
inst . ffzSetSmoothScroll ( this . smooth _scroll ) ;
2017-11-16 15:54:58 -05:00
inst . ffzInstallHandler ( ) ;
2017-11-14 04:12:10 -05:00
}
2019-06-08 17:35:48 -04:00
onUnmount ( inst ) { // eslint-disable-line class-methods-use-this
2019-06-12 21:13:53 -04:00
this . off ( 'tooltips:mousemove' , inst . ffzTooltipHover , inst ) ;
this . off ( 'tooltips:leave' , inst . ffzTooltipLeave , inst ) ;
2022-12-08 16:19:14 -05:00
if ( inst . _ffz _hover _timer ) {
clearInterval ( inst . _ffz _hover _timer ) ;
inst . _ffz _hover _timer = null ;
}
if ( inst . _ffz _outside _timer ) {
clearTimeout ( inst . _ffz _outside _timer ) ;
inst . _ffz _outside _timer = null ;
}
2019-06-08 17:35:48 -04:00
window . removeEventListener ( 'keydown' , inst . ffzHandleKey ) ;
window . removeEventListener ( 'keyup' , inst . ffzHandleKey ) ;
2017-11-14 04:12:10 -05:00
}
}