2018-04-28 17:56:03 -04:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
// Channel
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
import Module from 'utilities/module';
|
2020-06-30 19:48:46 -04:00
|
|
|
import { Color } from 'utilities/color';
|
|
|
|
import {debounce} from 'utilities/object';
|
2018-04-28 17:56:03 -04:00
|
|
|
|
2018-07-18 17:06:14 -04:00
|
|
|
|
2020-06-30 19:48:46 -04:00
|
|
|
const USER_PAGES = ['user', 'video', 'user-video', 'user-clip', 'user-videos', 'user-clips', 'user-collections', 'user-events', 'user-followers', 'user-following'];
|
2018-04-28 17:56:03 -04:00
|
|
|
|
|
|
|
export default class Channel extends Module {
|
2020-06-30 19:48:46 -04:00
|
|
|
|
2018-04-28 17:56:03 -04:00
|
|
|
constructor(...args) {
|
|
|
|
super(...args);
|
|
|
|
|
|
|
|
this.should_enable = true;
|
|
|
|
|
2020-06-30 19:48:46 -04:00
|
|
|
this.inject('i18n');
|
2018-04-28 17:56:03 -04:00
|
|
|
this.inject('settings');
|
2019-11-11 14:38:49 -05:00
|
|
|
this.inject('site.css_tweaks');
|
2020-06-30 19:48:46 -04:00
|
|
|
this.inject('site.fine');
|
|
|
|
this.inject('site.elemental');
|
|
|
|
this.inject('site.twitch_data');
|
|
|
|
this.inject('metadata');
|
|
|
|
this.inject('socket');
|
|
|
|
|
2020-07-01 19:07:17 -04:00
|
|
|
/*this.SideNav = this.elemental.define(
|
|
|
|
'side-nav', '.side-bar-contents .side-nav-section:first-child',
|
|
|
|
null,
|
|
|
|
{childNodes: true, subtree: true}, 1
|
|
|
|
);*/
|
|
|
|
|
2020-06-30 19:48:46 -04:00
|
|
|
this.ChannelRoot = this.elemental.define(
|
|
|
|
'channel-root', '.channel-root',
|
|
|
|
USER_PAGES,
|
|
|
|
{attributes: true}, 1
|
2018-05-10 19:56:39 -04:00
|
|
|
);
|
2019-06-18 19:48:51 -04:00
|
|
|
|
2020-06-30 19:48:46 -04:00
|
|
|
this.InfoBar = this.elemental.define(
|
|
|
|
'channel-info-bar', '.channel-info-content',
|
|
|
|
USER_PAGES,
|
|
|
|
{childNodes: true, subtree: true}, 1
|
2019-11-14 19:52:35 -05:00
|
|
|
);
|
2019-11-11 14:38:49 -05:00
|
|
|
}
|
|
|
|
|
2018-04-28 17:56:03 -04:00
|
|
|
onEnable() {
|
2020-06-23 17:17:00 -04:00
|
|
|
this.updateChannelColor();
|
|
|
|
|
2020-07-01 19:07:17 -04:00
|
|
|
//this.SideNav.on('mount', this.updateHidden, this);
|
|
|
|
//this.SideNav.on('mutate', this.updateHidden, this);
|
|
|
|
|
2020-06-30 19:48:46 -04:00
|
|
|
this.ChannelRoot.on('mount', this.updateRoot, this);
|
|
|
|
this.ChannelRoot.on('mutate', this.updateRoot, this);
|
|
|
|
this.ChannelRoot.on('unmount', this.removeRoot, this);
|
|
|
|
this.ChannelRoot.each(el => this.updateRoot(el));
|
2019-11-11 14:38:49 -05:00
|
|
|
|
2020-06-30 19:48:46 -04:00
|
|
|
this.InfoBar.on('mount', this.updateBar, this);
|
|
|
|
this.InfoBar.on('mutate', this.updateBar, this);
|
|
|
|
this.InfoBar.on('unmount', this.removeBar, this);
|
|
|
|
this.InfoBar.each(el => this.updateBar(el));
|
2019-11-11 14:38:49 -05:00
|
|
|
}
|
|
|
|
|
2020-07-01 19:07:17 -04:00
|
|
|
/*updateHidden(el) { // eslint-disable-line class-methods-use-this
|
|
|
|
if ( ! el._ffz_raf )
|
|
|
|
el._ffz_raf = requestAnimationFrame(() => {
|
|
|
|
el._ffz_raf = null;
|
|
|
|
const nodes = el.querySelectorAll('.side-nav-card__avatar--offline');
|
|
|
|
for(const node of nodes) {
|
|
|
|
const par = node.closest('.tw-transition');
|
|
|
|
if ( par && el.contains(par) )
|
|
|
|
par.classList.add('tw-hide');
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}*/
|
|
|
|
|
2020-06-30 19:48:46 -04:00
|
|
|
updateSubscription(login) {
|
|
|
|
if ( this._subbed_login === login )
|
2019-11-14 19:52:35 -05:00
|
|
|
return;
|
|
|
|
|
2020-06-30 19:48:46 -04:00
|
|
|
if ( this._subbed_login ) {
|
|
|
|
this.socket.unsubscribe(this, `channel.${this._subbed_login}`);
|
|
|
|
this._subbed_login = null;
|
|
|
|
}
|
2019-11-14 19:52:35 -05:00
|
|
|
|
2020-06-30 19:48:46 -04:00
|
|
|
if ( login ) {
|
|
|
|
this.socket.subscribe(this, `channel.${login}`);
|
|
|
|
this._subbed_login = login;
|
|
|
|
}
|
2019-11-14 19:52:35 -05:00
|
|
|
}
|
|
|
|
|
2020-06-30 19:48:46 -04:00
|
|
|
updateBar(el) {
|
|
|
|
// TODO: Run a data check to abort early if nothing has changed before updating metadata
|
|
|
|
// thus avoiding a potential loop from mutations.
|
|
|
|
if ( ! el._ffz_update )
|
|
|
|
el._ffz_update = debounce(() => requestAnimationFrame(() => this._updateBar(el)), 1000, 2);
|
2019-11-14 19:52:35 -05:00
|
|
|
|
2020-06-30 19:48:46 -04:00
|
|
|
el._ffz_update();
|
2018-04-28 17:56:03 -04:00
|
|
|
}
|
|
|
|
|
2020-06-30 19:48:46 -04:00
|
|
|
_updateBar(el) {
|
|
|
|
if ( el._ffz_cont && ! el.contains(el._ffz_cont) ) {
|
|
|
|
el._ffz_cont.classList.remove('ffz--meta-tray');
|
|
|
|
el._ffz_cont = null;
|
|
|
|
}
|
2018-04-28 17:56:03 -04:00
|
|
|
|
2020-06-30 19:48:46 -04:00
|
|
|
if ( ! el._ffz_cont ) {
|
|
|
|
const report = el.querySelector('.report-button'),
|
|
|
|
cont = report && report.closest('.tw-flex-wrap.tw-justify-content-end');
|
2018-07-09 21:35:31 -04:00
|
|
|
|
2020-06-30 19:48:46 -04:00
|
|
|
if ( cont && el.contains(cont) ) {
|
|
|
|
el._ffz_cont = cont;
|
|
|
|
cont.classList.add('ffz--meta-tray');
|
2018-07-09 21:35:31 -04:00
|
|
|
|
2020-06-30 19:48:46 -04:00
|
|
|
} else
|
|
|
|
el._ffz_cont = null;
|
|
|
|
}
|
2018-07-09 21:35:31 -04:00
|
|
|
|
2020-06-30 19:48:46 -04:00
|
|
|
const react = this.fine.getReactInstance(el),
|
|
|
|
props = react?.memoizedProps?.children?.props;
|
2018-07-09 21:35:31 -04:00
|
|
|
|
2020-06-30 19:48:46 -04:00
|
|
|
if ( ! el._ffz_cont || ! props?.channelID ) {
|
|
|
|
this.updateSubscription(null);
|
|
|
|
return;
|
2018-07-09 21:35:31 -04:00
|
|
|
}
|
|
|
|
|
2020-06-30 19:48:46 -04:00
|
|
|
this.updateSubscription(props.channelLogin);
|
|
|
|
this.updateMetadata(el);
|
2018-07-09 21:35:31 -04:00
|
|
|
}
|
|
|
|
|
2020-06-30 19:48:46 -04:00
|
|
|
removeBar(el) {
|
|
|
|
this.updateSubscription(null);
|
2018-07-09 21:35:31 -04:00
|
|
|
|
2020-06-30 19:48:46 -04:00
|
|
|
if ( el._ffz_cont )
|
|
|
|
el._ffz_cont.classList.remove('ffz--meta-tray');
|
2019-06-18 19:48:51 -04:00
|
|
|
|
2020-06-30 19:48:46 -04:00
|
|
|
el._ffz_cont = null;
|
|
|
|
if ( el._ffz_meta_timers ) {
|
|
|
|
for(const val of Object.values(el._ffz_meta_timers))
|
|
|
|
clearTimeout(val);
|
2019-06-18 19:48:51 -04:00
|
|
|
|
2020-06-30 19:48:46 -04:00
|
|
|
el._ffz_meta_timers = null;
|
|
|
|
}
|
2018-05-10 19:56:39 -04:00
|
|
|
|
2020-06-30 19:48:46 -04:00
|
|
|
el._ffz_update = null;
|
2018-05-10 19:56:39 -04:00
|
|
|
}
|
|
|
|
|
2020-06-30 19:48:46 -04:00
|
|
|
updateMetadata(el, keys) {
|
|
|
|
const cont = el._ffz_cont,
|
|
|
|
react = this.fine.getReactInstance(el),
|
|
|
|
props = react?.memoizedProps?.children?.props;
|
2018-05-10 19:56:39 -04:00
|
|
|
|
2020-06-30 19:48:46 -04:00
|
|
|
if ( ! cont || ! el.contains(cont) || ! props || ! props.channelID )
|
2018-04-28 17:56:03 -04:00
|
|
|
return;
|
|
|
|
|
2020-06-30 19:48:46 -04:00
|
|
|
if ( ! keys )
|
|
|
|
keys = this.metadata.keys;
|
|
|
|
else if ( ! Array.isArray(keys) )
|
|
|
|
keys = [keys];
|
|
|
|
|
|
|
|
const timers = el._ffz_meta_timers = el._ffz_meta_timers || {},
|
|
|
|
refresh_fn = key => this.updateMetadata(el, key),
|
|
|
|
data = {
|
|
|
|
channel: {
|
|
|
|
id: props.channelID,
|
|
|
|
login: props.channelLogin,
|
|
|
|
display_name: props.displayName,
|
|
|
|
live: props.isLive,
|
|
|
|
live_since: props.liveSince
|
|
|
|
},
|
|
|
|
props,
|
|
|
|
hosted: {
|
|
|
|
login: props.hostLogin,
|
|
|
|
display_name: props.hostDisplayName
|
|
|
|
},
|
|
|
|
el,
|
|
|
|
getBroadcastID: () => this.getBroadcastID(el, props.channelID)
|
|
|
|
};
|
|
|
|
|
|
|
|
for(const key of keys)
|
|
|
|
this.metadata.renderLegacy(key, data, cont, timers, refresh_fn);
|
|
|
|
}
|
2018-04-28 17:56:03 -04:00
|
|
|
|
|
|
|
|
2020-06-30 19:48:46 -04:00
|
|
|
updateRoot(el) {
|
|
|
|
const root = this.fine.getReactInstance(el),
|
|
|
|
channel = root?.return?.memoizedState?.next?.memoizedState?.current?.previousData?.result?.data?.user;
|
2018-04-28 17:56:03 -04:00
|
|
|
|
2020-06-30 19:48:46 -04:00
|
|
|
if ( channel && channel.id ) {
|
|
|
|
this.updateChannelColor(channel.primaryColorHex);
|
2018-04-28 17:56:03 -04:00
|
|
|
|
2020-06-30 19:48:46 -04:00
|
|
|
this.settings.updateContext({
|
|
|
|
channel: channel.login,
|
|
|
|
channelID: channel.id,
|
|
|
|
channelColor: channel.primaryColorHex
|
|
|
|
});
|
2018-11-13 14:50:17 -05:00
|
|
|
|
2020-06-30 19:48:46 -04:00
|
|
|
} else
|
|
|
|
this.removeRoot();
|
|
|
|
}
|
2018-11-13 14:50:17 -05:00
|
|
|
|
2020-06-30 19:48:46 -04:00
|
|
|
removeRoot() {
|
|
|
|
this.updateChannelColor();
|
|
|
|
this.settings.updateContext({
|
|
|
|
channel: null,
|
|
|
|
channelID: null,
|
|
|
|
channelColor: null
|
|
|
|
});
|
|
|
|
}
|
2018-08-04 15:01:00 -04:00
|
|
|
|
2020-06-30 19:48:46 -04:00
|
|
|
updateChannelColor(color) {
|
|
|
|
let parsed = color && Color.RGBA.fromHex(color);
|
|
|
|
if ( ! parsed )
|
|
|
|
parsed = Color.RGBA.fromHex('9147FF');
|
2018-08-01 15:11:03 -04:00
|
|
|
|
2020-06-30 19:48:46 -04:00
|
|
|
if ( parsed ) {
|
|
|
|
this.css_tweaks.setVariable('channel-color', parsed.toCSS());
|
|
|
|
this.css_tweaks.setVariable('channel-color-20', parsed._a(0.2).toCSS());
|
|
|
|
this.css_tweaks.setVariable('channel-color-30', parsed._a(0.3).toCSS());
|
2018-08-01 15:11:03 -04:00
|
|
|
} else {
|
2020-06-30 19:48:46 -04:00
|
|
|
this.css_tweaks.deleteVariable('channel-color');
|
|
|
|
this.css_tweaks.deleteVariable('channel-color-20');
|
|
|
|
this.css_tweaks.deleteVariable('channel-color-30');
|
2018-08-01 15:11:03 -04:00
|
|
|
}
|
2018-04-28 17:56:03 -04:00
|
|
|
}
|
|
|
|
|
2020-06-30 19:48:46 -04:00
|
|
|
getBroadcastID(el, channel_id) {
|
|
|
|
const cache = el._ffz_bcast_cache = el._ffz_bcast_cache || {};
|
|
|
|
if ( channel_id === cache.channel_id ) {
|
|
|
|
if ( Date.now() - cache.saved < 60000 )
|
|
|
|
return Promise.resolve(cache.broadcast_id);
|
|
|
|
}
|
2018-04-28 17:56:03 -04:00
|
|
|
|
2020-06-30 19:48:46 -04:00
|
|
|
return new Promise(async (s, f) => {
|
|
|
|
if ( cache.updating ) {
|
|
|
|
cache.updating.push([s, f]);
|
|
|
|
return ;
|
|
|
|
}
|
2018-08-04 15:01:00 -04:00
|
|
|
|
2020-06-30 19:48:46 -04:00
|
|
|
cache.channel_id = channel_id;
|
|
|
|
cache.updating = [[s,f]];
|
|
|
|
let id, err;
|
2018-08-04 15:01:00 -04:00
|
|
|
|
2020-06-30 19:48:46 -04:00
|
|
|
try {
|
|
|
|
id = await this.twitch_data.getBroadcastID(channel_id);
|
|
|
|
} catch(error) {
|
|
|
|
id = null;
|
|
|
|
err = error;
|
|
|
|
}
|
2018-11-13 14:50:17 -05:00
|
|
|
|
2020-06-30 19:48:46 -04:00
|
|
|
const waiters = cache.updating;
|
|
|
|
cache.updating = null;
|
2018-11-13 14:50:17 -05:00
|
|
|
|
2020-06-30 19:48:46 -04:00
|
|
|
if ( cache.channel_id !== channel_id ) {
|
|
|
|
err = new Error('Outdated');
|
|
|
|
cache.channel_id = null;
|
|
|
|
cache.broadcast_id = null;
|
|
|
|
cache.saved = 0;
|
|
|
|
for(const pair of waiters)
|
|
|
|
pair[1](err);
|
2018-07-18 18:58:05 -04:00
|
|
|
|
2020-06-30 19:48:46 -04:00
|
|
|
return;
|
|
|
|
}
|
2018-06-27 14:13:59 -04:00
|
|
|
|
2020-06-30 19:48:46 -04:00
|
|
|
cache.broadcast_id = id;
|
|
|
|
cache.saved = Date.now();
|
2018-08-04 15:01:00 -04:00
|
|
|
|
2020-06-30 19:48:46 -04:00
|
|
|
for(const pair of waiters)
|
|
|
|
err ? pair[1](err) : pair[0](id);
|
|
|
|
});
|
2018-04-28 17:56:03 -04:00
|
|
|
}
|
|
|
|
}
|