1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-07-05 02:28:31 +00:00

4.0.0-rc10

* Added: Support for rendering metadata for the new Twitch layout.
* Fixed: The option to disable Hosts not working with the new Twitch layout.
This commit is contained in:
SirStendec 2018-08-01 15:11:03 -04:00
parent 44dd04de20
commit 6654fda11d
7 changed files with 495 additions and 88 deletions

View file

@ -100,7 +100,7 @@ class FrankerFaceZ extends Module {
FrankerFaceZ.Logger = Logger; FrankerFaceZ.Logger = Logger;
const VER = FrankerFaceZ.version_info = { const VER = FrankerFaceZ.version_info = {
major: 4, minor: 0, revision: 0, extra: '-rc9.3', major: 4, minor: 0, revision: 0, extra: '-rc10',
commit: __git_commit__, commit: __git_commit__,
build: __webpack_hash__, build: __webpack_hash__,
toString: () => toString: () =>

View file

@ -4,7 +4,7 @@
// Channel Metadata // Channel Metadata
// ============================================================================ // ============================================================================
import {createElement, ClickOutside} from 'utilities/dom'; import {createElement, ClickOutside, setChildren} from 'utilities/dom';
import {maybe_call} from 'utilities/object'; import {maybe_call} from 'utilities/object';
import {duration_to_string} from 'utilities/time'; import {duration_to_string} from 'utilities/time';
@ -58,10 +58,10 @@ export default class Metadata extends Module {
this.definitions.uptime = { this.definitions.uptime = {
refresh() { return this.settings.get('metadata.uptime') > 0 }, refresh() { return this.settings.get('metadata.uptime') > 0 },
setup() { setup(data) {
const socket = this.resolve('socket'), const socket = this.resolve('socket'),
apollo = this.resolve('site.apollo'), apollo = this.resolve('site.apollo'),
created_at = apollo.getFromQuery('ChannelPage_ChannelHeader', 'data.user.stream.createdAt'); created_at = apollo.getFromQuery(data.legacy ? 'ChannelPage_ChannelHeader' : 'ChannelPage_User', 'data.user.stream.createdAt');
if ( ! created_at ) if ( ! created_at )
return {}; return {};
@ -86,6 +86,8 @@ export default class Metadata extends Module {
return duration_to_string(data.uptime, false, false, false, setting !== 2); return duration_to_string(data.uptime, false, false, false, setting !== 2);
}, },
subtitle: () => this.i18n.t('metadata.uptime.subtitle', 'Uptime'),
tooltip(data) { tooltip(data) {
if ( ! data.created ) if ( ! data.created )
return null; return null;
@ -135,7 +137,7 @@ export default class Metadata extends Module {
} }
} }
if ( ! stats ) if ( ! stats || stats.hlsLatencyBroadcaster < -100 )
return {stats}; return {stats};
let drift = 0; let drift = 0;
@ -154,6 +156,8 @@ export default class Metadata extends Module {
order: 3, order: 3,
icon: 'ffz-i-gauge', icon: 'ffz-i-gauge',
subtitle: () => this.i18n.t('metadata.player-stats.subtitle', 'Latency'),
label(data) { label(data) {
if ( ! this.settings.get('metadata.player-stats') || ! data.delay ) if ( ! this.settings.get('metadata.player-stats') || ! data.delay )
return null; return null;
@ -226,9 +230,6 @@ export default class Metadata extends Module {
if ( bar ) { if ( bar ) {
for(const inst of bar.ChannelBar.instances) for(const inst of bar.ChannelBar.instances)
bar.updateMetadata(inst, keys); bar.updateMetadata(inst, keys);
for(const inst of bar.HostBar.instances)
bar.updateMetadata(inst, keys);
} }
} }
@ -239,6 +240,264 @@ export default class Metadata extends Module {
let el = container.querySelector(`.ffz-stat[data-key="${key}"]`); let el = container.querySelector(`.ffz-stat[data-key="${key}"]`);
const def = this.definitions[key],
destroy = () => {
if ( el ) {
if ( el.tooltip )
el.tooltip.destroy();
if ( el.popper )
el.popper.destroy();
if ( el._ffz_destroy )
el._ffz_destroy();
el._ffz_destroy = el.tooltip = el.popper = null;
el.remove();
}
};
if ( ! def )
return destroy();
try {
// Process the data if a setup method is defined.
if ( def.setup )
data = await def.setup.call(this, data);
// Let's get refresh logic out of the way now.
const refresh = maybe_call(def.refresh, this, data);
if ( refresh )
timers[key] = setTimeout(
() => refresh_fn(key),
typeof refresh === 'number' ? refresh : 1000
);
// Grab the element again in case it changed, somehow.
el = container.querySelector(`.ffz-sidebar-stat[data-key="${key}"]`);
let stat, old_color, sub_el;
const label = maybe_call(def.label, this, data);
if ( ! label )
return destroy();
const tooltip = maybe_call(def.tooltip, this, data),
subtitle = maybe_call(def.subtitle, this, data),
order = maybe_call(def.order, this, data),
color = maybe_call(def.color, this, data) || '';
if ( ! el ) {
let icon = maybe_call(def.icon, this, data);
let button = false;
el = (<div
class="ffz-sidebar-stat tw-flex tw-flex-grow-1 tw-flex-column tw-justify-content-center"
data-key={key}
tip_content={tooltip}
/>);
if ( def.popup || def.click ) {
button = true;
let btn, popup;
let cls = maybe_call(def.button, this, data);
if ( typeof cls !== 'string' )
cls = `tw-button--${cls ? 'hollow' : 'text'}`;
if ( typeof icon === 'string' )
icon = (<span class="tw-button__icon tw-button__icon--left"><figure class={icon} /></span>);
if ( def.popup && def.click ) {
el.appendChild(<div class="tw-flex tw--full-width tw-flex-no-wrap">
{btn = (<button class={`tw-interactive tw-button tw-button--full-width ${cls} ffz-has-stat-arrow`}>
{icon}
{stat = <div class="tw-button__text ffz-sidebar-stat--label" />}
</button>)}
{popup = (<button class={`tw-button ${cls} ffz-stat-arrow`}>
<span class="tw-button__icon tw-pd-x-0">
<figure class="ffz-i-down-dir" />
</span>
</button>)}
</div>);
} else {
el.appendChild(btn = popup = (<button class={`tw-interactive tw-button tw-button--full-width ${cls}`}>
{icon}
{stat = <div class="tw-button__text ffz-sidebar-stat--label" />}
{def.popup && <span class="tw-button__icon tw-button__icon--right">
<figure class="ffz-i-down-dir" />
</span>}
</button>));
}
if ( def.click )
btn.addEventListener('click', e => {
if ( btn.disabled || btn.classList.contains('disabled') || el.disabled || el.classList.contains('disabled') )
return false;
def.click.call(this, el._ffz_data, e, () => refresh_fn(key));
});
if ( def.popup )
popup.addEventListener('click', () => {
if ( popup.disabled || popup.classList.contains('disabled') || el.disabled || el.classList.contains('disabled') )
return false;
if ( el._ffz_popup )
return el._ffz_destroy();
const destroy = el._ffz_destroy = () => {
if ( el._ffz_outside )
el._ffz_outside.destroy();
if ( el._ffz_popup ) {
const fp = el._ffz_popup;
el._ffz_popup = null;
fp.destroy();
}
el._ffz_destroy = el._ffz_outside = null;
};
const parent = document.body.querySelector('body #root,body'),
tt = el._ffz_popup = new Tooltip(parent, el, {
logger: this.log,
manual: true,
html: true,
tooltipClass: 'ffz-metadata-balloon tw-balloon tw-block tw-border tw-elevation-1 tw-border-radius-small tw-c-background',
// Hide the arrow for now, until we re-do our CSS to make it render correctly.
arrowClass: 'tw-balloon__tail tw-overflow-hidden tw-absolute',
arrowInner: 'tw-balloon__tail-symbol tw-border-t tw-border-r tw-border-b tw-border-l tw-border-radius-small tw-c-background tw-absolute',
innerClass: 'tw-pd-1',
popper: {
placement: 'right-start',
modifiers: {
preventOverflow: {
boundariesElement: parent
},
flip: {
behavior: ['top', 'bottom', 'left', 'right']
}
}
},
content: (t, tip) => def.popup.call(this, el._ffz_data, tip, () => refresh_fn(key)),
onShow: (t, tip) =>
setTimeout(() => {
el._ffz_outside = new ClickOutside(tip.outer, destroy);
}),
onHide: destroy
});
tt._enter(el);
});
} else {
el.appendChild(<div class="tw-align-items-center tw-flex tw-justify-content-center tw-font-size-4">
{stat = <div class="tw-strong ffz-sidebar-stat--label" />}
</div>);
}
el.appendChild(sub_el = <div class="tw-flex tw-justify-content-center tw-c-text-alt-2 tw-font-size-6 ffz-sidebar-stat--subtitle" />);
let subcontainer;
if ( button ) {
subcontainer = container.querySelector('.ffz-sidebar-stats--buttons');
if ( ! subcontainer )
container.appendChild(subcontainer = (<div class="tw-flex tw-flex-wrap tw-justify-content-between ffz-sidebar-stats ffz-sidebar-stats--buttons" />));
} else {
subcontainer = container.querySelector('.ffz-sidebar-stats--stats');
if ( ! subcontainer ) {
subcontainer = (<div class="tw-flex tw-flex-wrap tw-justify-content-between ffz-sidebar-stats ffz-sidebar-stats--stats" />);
const btns = container.querySelector('.ffz-sidebar-stats--buttons');
if ( btns )
container.insertBefore(subcontainer, btns);
else
container.appendChild(subcontainer);
}
}
el._ffz_order = order;
if ( order != null )
el.style.order = order;
subcontainer.appendChild(el);
if ( def.tooltip ) {
const parent = document.body.querySelector('body #root,body');
el.tooltip = new Tooltip(parent, el, {
logger: this.log,
live: false,
html: true,
content: () => el.tip_content,
onShow: (t, tip) => el.tip = tip,
onHide: () => el.tip = null,
popper: {
placement: 'top',
modifiers: {
flip: {
behavior: ['bottom', 'top']
},
preventOverflow: {
boundariesElement: parent
}
}
}
});
}
} else {
stat = el.querySelector('.ffz-sidebar-stat--label');
sub_el = el.querySelector('.ffz-sidebar-stat--subtitle')
old_color = el.dataset.color || '';
if ( el._ffz_order !== order )
el.style.order = el._ffz_order = order;
if ( el.tip_content !== tooltip ) {
el.tip_content = tooltip;
if ( el.tip )
el.tip.element.innerHTML = tooltip;
}
}
if ( old_color !== color )
el.dataset.color = el.style.color = color;
el._ffz_data = data;
setChildren(stat, label, true);
setChildren(sub_el, subtitle, true);
if ( def.disabled !== undefined )
el.disabled = maybe_call(def.disabled, this, data);
} catch(err) {
this.log.capture(err, {
tags: {
metadata: key
}
});
this.log.error(`Error rendering metadata for ${key}`, err);
return destroy();
}
}
async renderLegacy(key, data, container, timers, refresh_fn) {
if ( timers[key] )
clearTimeout(timers[key]);
let el = container.querySelector(`.ffz-stat[data-key="${key}"]`);
const def = this.definitions[key], const def = this.definitions[key],
destroy = () => { destroy = () => {
if ( el ) { if ( el ) {

View file

@ -5,7 +5,7 @@
// ============================================================================ // ============================================================================
import Module from 'utilities/module'; import Module from 'utilities/module';
import { has } from 'utilities/object'; import { get, has } from 'utilities/object';
import Twilight from 'site'; import Twilight from 'site';
@ -44,9 +44,8 @@ export default class Channel extends Module {
this.ChannelPage = this.fine.define( this.ChannelPage = this.fine.define(
'channel-page', 'channel-page',
n => n.getHostedChannelLogin && n.handleHostingChange, n => (n.getHostedChannelLogin && n.handleHostingChange) || n.hostModeFromGraphQL,
//n => n.hostModeFromGraphQL, ['user', 'user-videos', 'user-clips', 'user-collections', 'user-events', 'user-followers', 'user-following']
['user']
); );
this.RaidController = this.fine.define( this.RaidController = this.fine.define(
@ -68,17 +67,12 @@ export default class Channel extends Module {
}); });
this.ChannelPage.on('update', inst => { this.ChannelPage.on('update', inst => {
if ( this.settings.get('channel.hosting.enable') ) if ( this.settings.get('channel.hosting.enable') || has(inst.state, 'hostMode') )
return; return;
// We can't do this immediately because the player state // We can't do this immediately because the player state
// occasionally screws up if we do. // occasionally screws up if we do.
setTimeout(() => { setTimeout(() => {
/*if ( inst.state.hostMode ) {
inst.ffzExpectedHost = inst.state.hostMode;
inst.ffzOldSetState({hostMode: null});
}*/
const current_channel = inst.props.data && inst.props.data.variables && inst.props.data.variables.currentChannelLogin; const current_channel = inst.props.data && inst.props.data.variables && inst.props.data.variables.currentChannelLogin;
if ( current_channel && current_channel !== inst.state.videoPlayerSource ) { if ( current_channel && current_channel !== inst.state.videoPlayerSource ) {
inst.ffzExpectedHost = inst.state.videoPlayerSource; inst.ffzExpectedHost = inst.state.videoPlayerSource;
@ -134,26 +128,40 @@ export default class Channel extends Module {
if ( inst._ffz_hosting_wrapped ) if ( inst._ffz_hosting_wrapped )
return; return;
const t = this; const t = this,
new_style = has(inst.state, 'hostMode');
inst.ffzGetChannel = () => {
const params = inst.props.match.params
if ( ! params )
return get('props.data.variables.currentChannelLogin', inst)
return params.channelName || params.channelLogin
}
inst.ffzOldSetState = inst.setState; inst.ffzOldSetState = inst.setState;
inst.setState = function(state, ...args) { inst.setState = function(state, ...args) {
try { try {
if ( ! t.settings.get('channel.hosting.enable') ) { if ( new_style ) {
if ( has(state, 'isHosting') ) if ( has(state, 'hostMode') ) {
state.isHosting = false; t.log.info('update-host', state.hostMode)
if ( has(state, 'videoPlayerSource') ) inst.ffzExpectedHost = state.hostMode;
state.videoPlayerSource = inst.props.match.params.channelName; if ( state.hostMode && ! t.settings.get('channel.hosting.enable') ) {
} state.hostMode = null;
state.videoPlayerSource = inst.ffzGetChannel();
/*if ( has(state, 'hostMode') ) { }
inst.ffzExpectedHost = state.hostMode;
if ( state.hostMode && ! t.settings.get('channel.hosting.enable') ) {
state.hostMode = null;
state.videoPlayerSource = inst.props.match.params.channelName;
} }
}*/
} else {
if ( ! t.settings.get('channel.hosting.enable') ) {
if ( has(state, 'isHosting') )
state.isHosting = false;
if ( has(state, 'videoPlayerSource') )
state.videoPlayerSource = inst.ffzGetChannel();
}
}
} catch(err) { } catch(err) {
t.log.capture(err, {extra: {props: inst.props, state}}); t.log.capture(err, {extra: {props: inst.props, state}});
@ -164,34 +172,41 @@ export default class Channel extends Module {
inst._ffz_hosting_wrapped = true; inst._ffz_hosting_wrapped = true;
inst.ffzOldGetHostedLogin = inst.getHostedChannelLogin; if ( new_style ) {
inst.getHostedChannelLogin = function() { const hosted = inst.ffzExpectedHost = inst.state.hostMode;
return t.settings.get('channel.hosting.enable') ? t.log.info('Expected Host', hosted);
inst.ffzOldGetHostedLogin() : null;
}
inst.ffzOldHostHandler = inst.handleHostingChange; if ( hosted && ! this.settings.get('channel.hosting.enable') )
inst.handleHostingChange = function(channel) { inst.ffzOldSetState({
inst.ffzExpectedHost = channel; hostMode: null,
if ( t.settings.get('channel.hosting.enable') ) videoPlayerSource: inst.props.match.params.channelName
return inst.ffzOldHostHandler(channel); });
}
// Store the current state and disable the current host if needed. } else {
inst.ffzExpectedHost = inst.state.isHosting ? inst.state.videoPlayerSource : null; inst.ffzOldGetHostedLogin = inst.getHostedChannelLogin;
if ( ! this.settings.get('channel.hosting.enable') ) inst.getHostedChannelLogin = function() {
inst.ffzOldHostHandler(null); return t.settings.get('channel.hosting.enable') ?
inst.ffzOldGetHostedLogin() : null;
}
inst.ffzOldHostHandler = inst.handleHostingChange;
inst.handleHostingChange = function(channel) {
inst.ffzExpectedHost = channel;
if ( t.settings.get('channel.hosting.enable') )
return inst.ffzOldHostHandler(channel);
}
// Store the current state and disable the current host if needed.
inst.ffzExpectedHost = inst.state.isHosting ? inst.state.videoPlayerSource : null;
if ( ! this.settings.get('channel.hosting.enable') )
inst.ffzOldHostHandler(null);
}
// Finally, we force an update so that any child components // Finally, we force an update so that any child components
// receive our updated handler. // receive our updated handler.
inst.forceUpdate(); inst.forceUpdate();
/*const hosted = inst.ffzExpectedHost = inst.state.hostMode;
if ( hosted && ! this.settings.get('channel.hosting.enable') )
inst.ffzOldSetState({
hostMode: null,
videoPlayerSource: inst.props.match.params.channelName
});*/
} }
@ -200,15 +215,17 @@ export default class Channel extends Module {
val = this.settings.get('channel.hosting.enable'); val = this.settings.get('channel.hosting.enable');
for(const inst of this.ChannelPage.instances) { for(const inst of this.ChannelPage.instances) {
inst.ffzOldHostHandler(val ? inst.ffzExpectedHost : null); if ( has(inst.state, 'hostMode') ) {
const host = val ? inst.ffzExpectedHost : null,
target = host && host.hostedChannel && host.hostedChannel.login || inst.ffzGetChannel();
/*const host = val ? inst.ffzExpectedHost : null, inst.ffzOldSetState({
target = host && host.hostedChannel && host.hostedChannel.login || inst.props.match.params.channelName; hostMode: host,
videoPlayerSource: target
});
inst.ffzOldSetState({ } else
hostMode: host, inst.ffzOldHostHandler(val ? inst.ffzExpectedHost : null);
videoPlayerSource: target
});*/
} }
} }
} }

View file

@ -9,7 +9,6 @@ import {deep_copy} from 'utilities/object';
import CHANNEL_QUERY from './channel_bar_query.gql'; import CHANNEL_QUERY from './channel_bar_query.gql';
export default class ChannelBar extends Module { export default class ChannelBar extends Module {
constructor(...args) { constructor(...args) {
super(...args); super(...args);
@ -21,27 +20,19 @@ export default class ChannelBar extends Module {
this.inject('metadata'); this.inject('metadata');
this.inject('socket'); this.inject('socket');
this.apollo.registerModifier('ChannelPage_ChannelHeader', CHANNEL_QUERY); this.apollo.registerModifier('ChannelPage_User', CHANNEL_QUERY);
this.apollo.registerModifier('ChannelPage_ChannelHeader', data => { /*this.apollo.registerModifier('ChannelPage_User', data => {
const u = data && data.data && data.data.user; const u = data && data.data && data.data.user;
if ( u ) { if ( u ) {
const o = u.profileViewCount = new Number(u.profileViewCount || 0); const o = u.profileViewCount = new Number(u.profileViewCount || 0);
o.data = deep_copy(u); o.data = deep_copy(u);
} }
}, false); }, false);*/
this.ChannelBar = this.fine.define( this.ChannelBar = this.fine.define(
'channel-bar', 'channel-bar',
n => n.getTitle && n.getGame && n.renderGame, n => n.renderChannelMetadata && n.renderTitleInfo,
['user'] ['user', 'video', 'user-videos', 'user-clips', 'user-collections', 'user-events', 'user-followers', 'user-following']
);
this.HostBar = this.fine.define(
'host-container',
n => n.handleReportHosterClick,
['user']
) )
} }
@ -54,16 +45,6 @@ export default class ChannelBar extends Module {
for(const inst of instances) for(const inst of instances)
this.updateChannelBar(inst); this.updateChannelBar(inst);
}); });
/*this.HostBar.on('unmount', this.unmountHostBar, this);
this.HostBar.on('mount', this.updateHostBar, this);
this.HostBar.on('update', this.updateHostBar, this);
this.HostBar.ready((cls, instances) => {
for(const inst of instances)
this.updateHostBar(inst);
});*/
} }
@ -99,11 +80,13 @@ export default class ChannelBar extends Module {
updateMetadata(inst, keys) { updateMetadata(inst, keys) {
const container = this.fine.getChildNode(inst), const container = this.fine.getChildNode(inst),
metabar = container && container.querySelector && container.querySelector('.channel-info-bar__action-container > .tw-flex'); wrapper = container && container.querySelector && container.querySelector('.side-nav-channel-info__info-wrapper > .tw-pd-t-05');
if ( ! inst._ffz_mounted || ! metabar ) if ( ! inst._ffz_mounted || ! wrapper )
return; return;
const metabar = wrapper;
if ( ! keys ) if ( ! keys )
keys = this.metadata.keys; keys = this.metadata.keys;
else if ( ! Array.isArray(keys) ) else if ( ! Array.isArray(keys) )
@ -112,7 +95,7 @@ export default class ChannelBar extends Module {
const timers = inst._ffz_meta_timers = inst._ffz_meta_timers || {}, const timers = inst._ffz_meta_timers = inst._ffz_meta_timers || {},
refresh_func = key => this.updateMetadata(inst, key), refresh_func = key => this.updateMetadata(inst, key),
data = { data = {
channel: inst.props.userData && inst.props.userData.user, channel: inst.props.data && inst.props.data.user,
hosting: false, hosting: false,
_inst: inst _inst: inst
} }

View file

@ -8,7 +8,9 @@ body .channel-page__video-player--theatre-mode {
} }
body .video-watch-page__right-column, body .video-watch-page__right-column,
body .channel-page__right-column { body .channel-page__right-column,
body .right-column,
body .channel-root__right-column {
width: var(--ffz-chat-width); width: var(--ffz-chat-width);
} }

View file

@ -0,0 +1,124 @@
'use strict';
// ============================================================================
// Channel Bar
// ============================================================================
import Module from 'utilities/module';
import {deep_copy} from 'utilities/object';
import CHANNEL_QUERY from './channel_bar_query.gql';
export default class LegacyChannelBar extends Module {
constructor(...args) {
super(...args);
this.should_enable = true;
this.inject('site.fine');
this.inject('site.apollo');
this.inject('metadata');
this.inject('socket');
this.apollo.registerModifier('ChannelPage_ChannelHeader', CHANNEL_QUERY);
this.apollo.registerModifier('ChannelPage_ChannelHeader', data => {
const u = data && data.data && data.data.user;
if ( u ) {
const o = u.profileViewCount = new Number(u.profileViewCount || 0);
o.data = deep_copy(u);
}
}, false);
this.ChannelBar = this.fine.define(
'legacy-channel-bar',
n => n.getTitle && n.getGame && n.renderGame,
['user']
);
this.HostBar = this.fine.define(
'legacy-host-container',
n => n.handleReportHosterClick,
['user']
)
}
onEnable() {
this.ChannelBar.on('unmount', this.unmountChannelBar, this);
this.ChannelBar.on('mount', this.updateChannelBar, this);
this.ChannelBar.on('update', this.updateChannelBar, this);
this.ChannelBar.ready((cls, instances) => {
for(const inst of instances)
this.updateChannelBar(inst);
});
/*this.HostBar.on('unmount', this.unmountHostBar, this);
this.HostBar.on('mount', this.updateHostBar, this);
this.HostBar.on('update', this.updateHostBar, this);
this.HostBar.ready((cls, instances) => {
for(const inst of instances)
this.updateHostBar(inst);
});*/
}
updateChannelBar(inst) {
const login = inst.props.channelLogin;
if ( login !== inst._ffz_old_login ) {
if ( inst._ffz_old_login )
this.socket.unsubscribe(inst, `channel.${inst._ffz_old_login}`);
if ( login )
this.socket.subscribe(inst, `channel.${login}`);
inst._ffz_old_login = login;
}
this.updateMetadata(inst);
}
unmountChannelBar(inst) {
if ( inst._ffz_old_login ) {
this.socket.unsubscribe(inst, `channel.${inst._ffz_old_login}`);
inst._ffz_old_login = null;
}
const timers = inst._ffz_meta_timers;
if ( timers )
for(const key in timers)
if ( timers[key] )
clearTimeout(timers[key]);
inst._ffz_meta_timers = null;
}
updateMetadata(inst, keys) {
const container = this.fine.getChildNode(inst),
metabar = container && container.querySelector && container.querySelector('.channel-info-bar__action-container > .tw-flex');
if ( ! inst._ffz_mounted || ! metabar )
return;
if ( ! keys )
keys = this.metadata.keys;
else if ( ! Array.isArray(keys) )
keys = [keys];
const timers = inst._ffz_meta_timers = inst._ffz_meta_timers || {},
refresh_func = key => this.updateMetadata(inst, key),
data = {
channel: inst.props.userData && inst.props.userData.user,
hosting: false,
legacy: true,
_inst: inst
}
for(const key of keys)
this.metadata.renderLegacy(key, data, metabar, timers, refresh_func);
}
}

View file

@ -36,8 +36,30 @@
} }
} }
.ffz-sidebar-stats {
margin-top: .5rem;
margin-right: -1rem;
& + .ffz-sidebar-stats {
margin-top: 0 !important;
}
.ffz-sidebar-stat {
min-width: calc(50% - 1rem);
margin: 0 1rem 1rem 0;
}
}
.ffz-has-stat-arrow {
border-top-right-radius: 0 !important;
border-bottom-right-radius: 0 !important;
}
.ffz-stat-arrow { .ffz-stat-arrow {
border-left: none !important; border-left: none !important;
border-top-left-radius: 0 !important;
border-bottom-left-radius: 0 !important;
} }