2019-03-19 15:03:43 -04:00
'use strict' ;
2020-11-14 21:58:22 -05:00
import { IS _FIREFOX } from 'src/utilities/constants' ;
2019-03-19 15:03:43 -04:00
// ============================================================================
// Layout Overrides for Twitch Twilight
// ============================================================================
import Module from 'utilities/module' ;
2019-06-06 16:33:14 -04:00
import { has } from 'utilities/object' ;
2019-03-19 15:03:43 -04:00
const PORTRAIT _ROUTES = [ 'user' , 'video' , 'user-video' , 'user-clip' , 'user-videos' , 'user-clips' , 'user-collections' , 'user-events' , 'user-followers' , 'user-following' ] ;
2019-08-09 14:24:26 -04:00
const MINIMAL _ROUTES = [ 'popout' , 'embed-chat' , 'dash-chat' ] ;
2019-03-19 15:03:43 -04:00
export default class Layout extends Module {
constructor ( ... args ) {
super ( ... args ) ;
this . should _enable = true ;
this . inject ( 'settings' ) ;
this . inject ( 'site.fine' ) ;
this . inject ( 'site.css_tweaks' ) ;
2019-11-14 19:52:35 -05:00
/ * t h i s . T o p N a v = t h i s . f i n e . d e f i n e (
2019-09-28 23:52:50 -04:00
'top-nav' ,
n => n . computeStyles && n . navigationLinkSize
2019-11-14 19:52:35 -05:00
) ; * /
2019-09-28 23:52:50 -04:00
2019-10-28 01:06:02 -04:00
/ * t h i s . R i g h t C o l u m n = t h i s . f i n e . d e f i n e (
2019-03-19 15:03:43 -04:00
'tw-rightcolumn' ,
2019-04-18 03:16:19 -04:00
n => n . hideOnBreakpoint && n . handleToggleVisibility
2019-10-28 01:06:02 -04:00
) ; * /
2019-06-06 16:33:14 -04:00
2020-10-16 15:19:11 -04:00
this . ResizeDetector = this . fine . define (
'resize-detector' ,
n => n . maybeDebounceOnScroll && n . setGrowDivRef && n . props . onResize
) ;
2019-10-23 17:49:01 -05:00
this . SideBarChannels = this . fine . define (
'nav-cards' ,
t => t . getCardSlideInContent && t . props && has ( t . props , 'tooltipContent' )
) ;
2019-03-19 15:03:43 -04:00
this . settings . add ( 'layout.portrait' , {
default : false ,
ui : {
path : 'Appearance > Layout >> Channel' ,
title : 'Enable Portrait Mode' ,
description : 'In Portrait Mode, chat will be displayed beneath the player when the window is taller than it is wide.' ,
component : 'setting-check-box'
}
} ) ;
2019-06-17 15:32:38 -04:00
this . settings . add ( 'layout.portrait-invert' , {
default : false ,
ui : {
path : 'Appearance > Layout >> Channel' ,
title : 'When in portrait mode, place chat at the top.' ,
component : 'setting-check-box'
} ,
changed : val => document . body . classList . toggle ( 'ffz--portrait-invert' , val )
} ) ;
2019-03-19 15:03:43 -04:00
this . settings . add ( 'layout.portrait-threshold' , {
default : 1.25 ,
ui : {
path : 'Appearance > Layout >> Channel' ,
title : 'Portrait Mode Threshold' ,
description : 'This is the Width to Height ratio at which point Portrait Mode will begin to activate.' ,
component : 'setting-text-box' ,
process ( val ) {
val = parseFloat ( val , 10 )
if ( isNaN ( val ) || ! isFinite ( val ) || val <= 0 )
return 1.25 ;
return val ;
}
}
2020-11-24 16:43:51 -05:00
} ) ;
this . settings . add ( 'layout.portrait-min-chat' , {
default : false ,
requires : [ 'layout.use-portrait' ] ,
process ( ctx , val ) {
if ( ! ctx . get ( 'layout.use-portrait' ) )
return false ;
return val ;
} ,
ui : {
path : 'Appearance > Layout >> Channel' ,
title : 'Use compact chat in Portrait Mode.' ,
description : 'When enabled, this minimizes the chat header and places the chat input box in line with the chat buttons in order to present a more compact chat able to display more lines with limited vertical space.' ,
component : 'setting-check-box'
} ,
changed : val => this . css _tweaks . toggle ( 'portrait-chat' , val )
2019-03-19 15:03:43 -04:00
} )
this . settings . add ( 'layout.use-portrait' , {
2020-09-29 14:15:43 -04:00
requires : [ 'layout.portrait' , 'layout.portrait-threshold' , 'context.route.name' , 'context.size' , 'context.isWatchParty' ] ,
2019-03-19 15:03:43 -04:00
process ( ctx ) {
2020-09-29 14:15:43 -04:00
if ( ctx . get ( 'context.isWatchParty' ) )
return false ;
2019-03-19 15:03:43 -04:00
const size = ctx . get ( 'context.size' ) ;
if ( ! size || ! ctx . get ( 'layout.portrait' ) || ! PORTRAIT _ROUTES . includes ( ctx . get ( 'context.route.name' ) ) )
return false ;
const ratio = size . width / size . height ;
return ratio <= ctx . get ( 'layout.portrait-threshold' ) ;
} ,
2019-05-16 14:46:26 -04:00
changed : ( ) => this . updatePortraitMode ( )
2019-03-19 15:03:43 -04:00
} ) ;
2019-04-05 18:04:14 -04:00
this . settings . add ( 'layout.inject-portrait' , {
requires : [ 'layout.use-portrait' , 'context.ui.rightColumnExpanded' ] ,
process ( ctx ) {
return ctx . get ( 'layout.use-portrait' ) && ctx . get ( 'context.ui.rightColumnExpanded' ) ;
} ,
changed : val => this . css _tweaks . toggle ( 'portrait' , val )
} ) ;
this . settings . add ( 'layout.use-portrait-swapped' , {
requires : [ 'layout.inject-portrait' , 'layout.swap-sidebars' ] ,
process ( ctx ) {
return ctx . get ( 'layout.inject-portrait' ) && ctx . get ( 'layout.swap-sidebars' )
} ,
changed : val => this . css _tweaks . toggle ( 'portrait-swapped' , val )
} ) ;
2019-11-25 17:50:20 -05:00
this . settings . add ( 'layout.use-portrait-meta' , {
requires : [ 'layout.inject-portrait' , 'player.theatre.metadata' ] ,
process ( ctx ) {
return ctx . get ( 'layout.inject-portrait' ) && ctx . get ( 'player.theatre.metadata' )
} ,
changed : val => this . css _tweaks . toggle ( 'portrait-metadata' , val )
} ) ;
this . settings . add ( 'layout.use-portrait-meta-top' , {
requires : [ 'layout.use-portrait-meta' , 'layout.portrait-invert' ] ,
process ( ctx ) {
return ctx . get ( 'layout.use-portrait-meta' ) && ! ctx . get ( 'layout.portrait-invert' )
} ,
changed : val => this . css _tweaks . toggle ( 'portrait-metadata-top' , val )
} ) ;
2019-03-19 15:03:43 -04:00
this . settings . add ( 'layout.show-portrait-chat' , {
requires : [ 'layout.use-portrait' , 'layout.portrait-extra-height' , 'layout.portrait-extra-width' ] ,
process ( ) {
// TODO: Calculate this based on the expected player height.
return true ;
} ,
changed : ( ) => this . updatePortraitMode ( )
} ) ;
this . settings . add ( 'layout.portrait-extra-height' , {
2019-05-07 15:04:12 -04:00
requires : [ 'context.new_channel' , 'context.squad_bar' , 'context.hosting' , 'context.ui.theatreModeEnabled' , 'player.theatre.no-whispers' , 'whispers.show' , 'layout.minimal-navigation' ] ,
2019-03-19 15:03:43 -04:00
process ( ctx ) {
let height = 0 ;
if ( ctx . get ( 'context.ui.theatreModeEnabled' ) ) {
if ( ctx . get ( 'layout.minimal-navigation' ) )
height += 1 ;
if ( ctx . get ( 'whispers.show' ) && ! ctx . get ( 'player.theatre.no-whispers' ) )
height += 4 ;
} else {
height = ctx . get ( 'layout.minimal-navigation' ) ? 1 : 5 ;
if ( ctx . get ( 'whispers.show' ) )
height += 4 ;
2019-05-07 15:04:12 -04:00
if ( ctx . get ( 'context.squad_bar' ) )
height += 6 ;
2019-03-19 15:03:43 -04:00
height += ctx . get ( 'context.new_channel' ) ? 1 : 5 ;
if ( ctx . get ( 'context.hosting' ) )
height += 4 ;
}
return height ;
} ,
changed : val => this . css _tweaks . setVariable ( 'portrait-extra-height' , ` ${ val } rem ` )
} )
this . settings . add ( 'layout.portrait-extra-width' , {
require : [ 'layout.side-nav.show' , 'context.ui.theatreModeEnabled' , 'context.ui.sideNavExpanded' ] ,
process ( ctx ) {
if ( ! ctx . get ( 'layout.side-nav.show' ) || ctx . get ( 'context.ui.theatreModeEnabled' ) )
return 0 ;
return ctx . get ( 'context.ui.sideNavExpanded' ) ? 24 : 5
} ,
changed : val => this . css _tweaks . setVariable ( 'portrait-extra-width' , ` ${ val } rem ` )
} ) ;
2019-04-18 03:16:19 -04:00
this . settings . add ( 'layout.is-minimal' , {
require : [ 'context.route.name' ] ,
process ( ctx ) {
return MINIMAL _ROUTES . includes ( ctx . get ( 'context.route.name' ) ) ;
}
} ) ;
2019-03-19 15:03:43 -04:00
}
onEnable ( ) {
2019-06-17 15:32:38 -04:00
document . body . classList . toggle ( 'ffz--portrait-invert' , this . settings . get ( 'layout.portrait-invert' ) ) ;
2019-09-28 23:52:50 -04:00
this . on ( ':update-nav' , this . updateNavLinks , this ) ;
2020-10-16 15:19:11 -04:00
this . on ( ':resize' , this . handleResize , this ) ;
2019-09-28 23:52:50 -04:00
2020-11-24 16:43:51 -05:00
this . css _tweaks . toggle ( 'portrait-chat' , this . settings . get ( 'layout.portrait-min-chat' ) ) ;
2019-04-05 18:04:14 -04:00
this . css _tweaks . toggle ( 'portrait' , this . settings . get ( 'layout.inject-portrait' ) ) ;
this . css _tweaks . toggle ( 'portrait-swapped' , this . settings . get ( 'layout.use-portrait-swapped' ) ) ;
2019-11-25 17:50:20 -05:00
this . css _tweaks . toggle ( 'portrait-metadata' , this . settings . get ( 'layout.use-portrait-meta' ) ) ;
this . css _tweaks . toggle ( 'portrait-metadata-top' , this . settings . get ( 'layout.use-portrait-meta-top' ) ) ;
2019-03-19 15:03:43 -04:00
this . css _tweaks . setVariable ( 'portrait-extra-width' , ` ${ this . settings . get ( 'layout.portrait-extra-width' ) } rem ` ) ;
this . css _tweaks . setVariable ( 'portrait-extra-height' , ` ${ this . settings . get ( 'layout.portrait-extra-height' ) } rem ` ) ;
2020-07-16 16:34:03 -04:00
this . on ( 'site.directory:update-cards' , ( ) => {
for ( const inst of this . SideBarChannels . instances )
this . updateCardClass ( inst ) ;
} ) ;
2020-11-14 14:08:27 -05:00
this . ResizeDetector . ready ( ( ) => {
if ( this . _needs _resize ) {
this . _needs _resize = false ;
this . handleResize ( ) ;
}
} ) ;
2019-10-23 17:49:01 -05:00
this . SideBarChannels . ready ( ( cls , instances ) => {
for ( const inst of instances )
this . updateCardClass ( inst ) ;
} ) ;
this . SideBarChannels . on ( 'mount' , this . updateCardClass , this ) ;
this . SideBarChannels . on ( 'update' , this . updateCardClass , this ) ;
2019-10-28 01:06:02 -04:00
/ * c o n s t t = t h i s ;
2019-03-19 15:03:43 -04:00
this . RightColumn . ready ( ( cls , instances ) => {
cls . prototype . ffzHideOnBreakpoint = function ( ) {
try {
if ( ! this . containerRef )
return ;
if ( ! t . settings . get ( 'layout.use-portrait' ) )
return this . oldHideOnBreakpoint ? this . oldHideOnBreakpoint ( ) : null ;
const should _show = t . settings . get ( 'layout.show-portrait-chat' ) ,
is _showing = this . props . rightColumnExpandedByBreakpoint ;
if ( should _show && ! is _showing ) {
this . containerRef . style . display = '' ;
this . props . expandRightColumnFromBreakpoint ( ) ;
} else if ( ! should _show && is _showing ) {
this . containerRef . style . display = 'none' ;
this . props . collapseRightColumnFromBreakpoint ( ) ;
}
} catch ( err ) {
if ( this . oldHideOnBreakpoint )
this . oldHideOnBreakpoint ( ) ;
t . log . capture ( err ) ;
}
}
cls . prototype . ffzInstall = function ( ) {
if ( this . oldHideOnBreakpoint )
return ;
this . oldHideOnBreakpoint = this . hideOnBreakpoint ;
this . hideOnBreakpoint = ( ) => this . ffzHideOnBreakpoint ( ) ;
}
for ( const inst of instances ) {
window . removeEventListener ( 'resize' , inst . hideOnBreakpoint ) ;
inst . ffzInstall ( ) ;
window . addEventListener ( 'resize' , inst . hideOnBreakpoint ) ;
inst . hideOnBreakpoint ( ) ;
}
2019-10-28 01:06:02 -04:00
} ) ; * /
2019-03-19 15:03:43 -04:00
}
2020-10-16 15:19:11 -04:00
handleResize ( ) {
if ( this . _resize _timer )
return ;
2020-11-14 21:58:22 -05:00
this . _resize _timer = setTimeout ( ( ) => this . _handleResize ( ) , IS _FIREFOX ? 500 : 100 ) ;
2020-10-16 15:19:11 -04:00
}
_handleResize ( ) {
2020-11-14 14:08:27 -05:00
clearTimeout ( this . _resize _timer ) ;
2020-10-16 15:19:11 -04:00
this . _resize _timer = null ;
2020-11-14 14:08:27 -05:00
if ( ! this . ResizeDetector . instances . size )
this . _needs _resize = true ;
2020-11-14 21:58:22 -05:00
else {
2020-11-14 14:08:27 -05:00
for ( const inst of this . ResizeDetector . instances ) {
inst ? . props ? . onResize ? . ( ) ;
}
2020-11-14 21:58:22 -05:00
this . emit ( 'site.player:fix-player' ) ;
}
2020-10-16 15:19:11 -04:00
}
2019-04-18 03:16:19 -04:00
get is _minimal ( ) {
return this . settings . get ( 'layout.is-minimal' )
}
2019-10-23 17:49:01 -05:00
updateCardClass ( inst ) {
const node = this . fine . getChildNode ( inst ) ;
2020-07-16 16:34:03 -04:00
if ( node ) {
2019-10-23 17:49:01 -05:00
node . classList . toggle ( 'ffz--side-nav-card-rerun' ,
inst . props ? . tooltipContent ? . props ? . streamType === 'rerun'
) ;
2020-07-16 16:34:03 -04:00
node . classList . toggle ( 'ffz--side-nav-card-offline' ,
inst . props ? . offline === true
) ;
const game = inst . props ? . tooltipContent ? . props ? . gameName || inst . props ? . metadataLeft ? . props ? . activity ? . stream ? . game ? . name || inst . props ? . metadataLeft ;
node . classList . toggle ( 'tw-hide' , this . settings . provider . get ( 'directory.game.blocked-games' , [ ] ) . includes ( game ) ) ;
}
2019-10-23 17:49:01 -05:00
}
2019-09-28 23:52:50 -04:00
updateNavLinks ( ) {
2019-11-14 19:52:35 -05:00
2019-09-28 23:52:50 -04:00
}
2019-03-19 15:03:43 -04:00
updatePortraitMode ( ) {
2019-12-13 20:14:55 -05:00
2019-03-19 15:03:43 -04:00
}
}