1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-06-28 15:27:43 +00:00
FrankerFaceZ/src/sites/twitch-twilight/modules/layout.js

443 lines
14 KiB
JavaScript
Raw Normal View History

'use strict';
import { IS_FIREFOX } from 'src/utilities/constants';
// ============================================================================
// Layout Overrides for Twitch Twilight
// ============================================================================
import Module from 'utilities/module';
import {debounce, has} from 'utilities/object';
const PORTRAIT_ROUTES = ['user', 'video', 'user-video', 'user-clip', 'user-videos', 'user-clips', 'user-collections', 'user-events', 'user-followers', 'user-following'];
const MINIMAL_ROUTES = ['popout', 'embed-chat', 'dash-chat'];
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');
this.inject('site.elemental');
/*this.TopNav = this.fine.define(
'top-nav',
n => n.computeStyles && n.navigationLinkSize
);*/
/*this.RightColumn = this.fine.define(
'tw-rightcolumn',
n => n.hideOnBreakpoint && n.handleToggleVisibility
);*/
this.ResizeDetector = this.fine.define(
'resize-detector',
n => n.maybeDebounceOnScroll && n.setGrowDivRef && n.props.onResize
);
this.SideBar = this.elemental.define(
'sidebar',
'.side-bar-contents',
null,
{childNodes: true, subtree: true}, 1
);
/*this.SideBarChannels = this.fine.define(
'nav-cards',
t => t.getCardSlideInContent && t.props && has(t.props, 'tooltipContent')
);*/
this.settings.add('clips.layout.big', {
default: false,
ui: {
path: 'Appearance > Layout >> Clips',
title: 'Allow the player to get larger on `clips.twitch.tv` pages.',
description: 'This only affects windows at least 1,200 pixels wide and attempts to make the player and chat replay as large as possible.',
component: 'setting-check-box'
}
});
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'
}
});
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)
});
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;
}
}
});
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)
})
this.settings.add('layout.use-portrait', {
requires: ['layout.portrait', 'layout.portrait-threshold', 'context.route.name', 'context.size', 'context.isWatchParty'],
process(ctx) {
if ( ctx.get('context.isWatchParty') )
return false;
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');
},
changed: () => this.updatePortraitMode()
});
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)
});
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)
});
this.settings.add('layout.is-theater-mode', {
requires: ['context.ui.theatreModeEnabled', 'context.fullscreen'],
process(ctx) {
if ( ctx.get('context.fullscreen') )
return false;
return ctx.get('context.ui.theatreModeEnabled');
}
});
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', {
requires: ['context.new_channel', 'context.squad_bar', /*'context.hosting',*/ 'layout.is-theater-mode', 'player.theatre.no-whispers', 'whispers.show', 'layout.minimal-navigation'],
process(ctx) {
let height = 0;
if ( ctx.get('layout.is-theater-mode') ) {
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;
if ( ctx.get('context.squad_bar') )
height += 6;
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', 'layout.is-theater-mode', 'context.ui.sideNavExpanded'],
process(ctx) {
if ( ! ctx.get('layout.side-nav.show') || ctx.get('layout.is-theater-mode') )
return 0;
return ctx.get('context.ui.sideNavExpanded') ? 24 : 5
},
//changed: val => this.css_tweaks.setVariable('portrait-extra-width', `${val}rem`)
});
this.settings.add('layout.is-minimal', {
require: ['context.route.name'],
process(ctx) {
return MINIMAL_ROUTES.includes(ctx.get('context.route.name'));
}
});
}
onEnable() {
document.body.classList.toggle('ffz--portrait-invert', this.settings.get('layout.portrait-invert'));
this.on(':update-nav', this.updateNavLinks, this);
this.on(':resize', this.handleResize, this);
this.settings.getChanges('layout.portrait-min-chat', val => this.css_tweaks.toggle('portrait-chat', val));
this.settings.getChanges('layout.inject-portrait', val => this.css_tweaks.toggle('portrait', val));
this.settings.getChanges('layout.use-portrait-swapped', val => this.css_tweaks.toggle('portrait-swapped', val));
this.settings.getChanges('layout.use-portrait-meta', val => this.css_tweaks.toggle('portrait-metadata', val));
this.settings.getChanges('layout.use-portrait-meta-top', val => this.css_tweaks.toggle('portrait-metadata-top', val));
this.settings.getChanges('layout.portrait-extra-width', val => this.css_tweaks.setVariable('portrait-extra-width', `${val}rem`));
this.settings.getChanges('layout.portrait-extra-height', val => this.css_tweaks.setVariable('portrait-extra-height', `${val}rem`));
/*this.css_tweaks.toggle('portrait-chat', this.settings.get('layout.portrait-min-chat'));
this.css_tweaks.toggle('portrait', this.settings.get('layout.inject-portrait'));
this.css_tweaks.toggle('portrait-swapped', this.settings.get('layout.use-portrait-swapped'));
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'));
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`);*/
this.on('site.directory:update-cards', () => {
this.SideBar.each(el => this._updateSidebar(el));
//for(const inst of this.SideBarChannels.instances)
// this.updateCardClass(inst);
});
this.ResizeDetector.ready(() => {
if ( this._needs_resize ) {
this._needs_resize = false;
this.handleResize();
}
});
this.SideBar.on('mount', this.updateSidebar, this);
this.SideBar.on('mutate', this.updateSidebar, this);
this.SideBar.each(el => this.updateSidebar(el));
/*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);*/
/*const t = this;
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();
}
});*/
}
handleResize() {
if ( this._resize_timer )
return;
this._resize_timer = setTimeout(() => this._handleResize(), IS_FIREFOX ? 500 : 100);
}
_handleResize() {
clearTimeout(this._resize_timer);
this._resize_timer = null;
if ( ! this.ResizeDetector.instances.size )
this._needs_resize = true;
else {
for(const inst of this.ResizeDetector.instances) {
inst?.onScroll?.();
//inst?.props?.onResize?.();
}
this.emit('site.player:fix-player');
}
}
get is_minimal() {
return this.settings.get('layout.is-minimal')
}
updateSidebar(el) {
if ( ! el )
return;
if ( ! el._ffz_update )
el._ffz_update = debounce(() => requestAnimationFrame(() => this._updateSidebar(el)), 1000, 2);
el._ffz_update();
}
_updateSidebar(el) {
if ( ! el )
return;
const cards = el.querySelectorAll('.side-nav-card'),
blocked_games = this.settings.provider.get('directory.game.blocked-games', []);
for(const card of cards) {
const react = this.fine.getReactInstance(card),
props = react?.return?.return?.memoizedProps,
stream = props?.metadataRight?.props?.stream,
rerun = stream?.type === 'rerun' ?? false,
game = stream?.game?.displayName,
offline = props?.offline ?? false;
let should_hide = false;
if ( game && blocked_games.includes(game) )
should_hide = true;
if ( props?.isPromoted && this.settings.get('directory.hide-promoted') )
should_hide = true;
else {
if ( ! should_hide ) {
const regexes = this.settings.get('__filter:directory.block-users');
const login = props?.userLogin;
if ( regexes && login ) {
if ( regexes[0] )
regexes[0].lastIndex = -1;
if ( regexes[1] )
regexes[1].lastIndex = -1;
if ( (regexes[0] && regexes[0].test(login)) || (regexes[1] && regexes[1].test(login)) )
should_hide = true;
}
}
if ( ! should_hide) {
const regexes = this.settings.get('__filter:directory.block-titles');
const title = stream?.broadcaster?.broadcastSettings?.title;
if ( regexes && title ) {
if ( regexes[0] )
regexes[0].lastIndex = -1;
if ( regexes[1] )
regexes[1].lastIndex = -1;
if ( (regexes[0] && regexes[0].test(title)) || (regexes[1] && regexes[1].test(title)) )
should_hide = true;
}
}
}
card.classList.toggle('ffz--side-nav-card-rerun', rerun);
card.classList.toggle('ffz--side-nav-card-offline', offline);
card.classList.toggle('tw-hide', should_hide);
}
}
/*updateCardClass(inst) {
const node = this.fine.getChildNode(inst);
if ( node ) {
node.classList.toggle('ffz--side-nav-card-rerun',
inst.props?.tooltipContent?.props?.streamType === 'rerun'
);
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));
}
}*/
updateNavLinks() {
}
updatePortraitMode() {
}
}