1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-07-03 17:48:30 +00:00

Use ref-counting with socket topic subscriptions to make it easy to tell when we should and shouldn't actually unsubscribe. Add route awareness to FineWrapper so that we can unregister the MutationObserver when we know we won't be suddenly finding an instance that we're looking for. Have the channel bar register for the channel pubsub topic. Fix minimize navigation showing the navigation bar over top of theater mode.

This commit is contained in:
SirStendec 2018-03-14 13:58:04 -04:00
parent c559790f87
commit 254d297f79
17 changed files with 160 additions and 71 deletions

View file

@ -65,7 +65,7 @@ export default class Room {
if ( this.manager.rooms[this._login] === this ) if ( this.manager.rooms[this._login] === this )
this.manager.rooms[this._login] = null; this.manager.rooms[this._login] = null;
this.manager.socket.unsubscribe(`room.${this.login}`); this.manager.socket.unsubscribe(this, `room.${this.login}`);
} }
if ( this.manager.room_ids[this.id] === this ) if ( this.manager.room_ids[this.id] === this )
@ -89,7 +89,7 @@ export default class Room {
const old_room = this.manager.rooms[this._login]; const old_room = this.manager.rooms[this._login];
if ( old_room === this ) { if ( old_room === this ) {
this.manager.rooms[this._login] = null; this.manager.rooms[this._login] = null;
this.manager.socket.unsubscribe(`room.${this.login}`); this.manager.socket.unsubscribe(this, `room.${this.login}`);
} }
} }
@ -102,7 +102,7 @@ export default class Room {
old_room.login = null; old_room.login = null;
this.manager.rooms[val] = this; this.manager.rooms[val] = this;
this.manager.socket.subscribe(`room.${val}`); this.manager.socket.subscribe(this, `room.${val}`);
this.manager.emit(':room-update-login', this, val); this.manager.emit(':room-update-login', this, val);
} }

View file

@ -50,8 +50,12 @@ export default class Twilight extends BaseSite {
this.router.on(':route', (route, match) => { this.router.on(':route', (route, match) => {
this.log.info('Navigation', route && route.name, match && match[0]); this.log.info('Navigation', route && route.name, match && match[0]);
this.fine.route(route && route.name);
}); });
const current = this.router.current;
this.fine.route(current && current.name);
document.head.appendChild(e('link', { document.head.appendChild(e('link', {
href: MAIN_URL, href: MAIN_URL,
rel: 'stylesheet', rel: 'stylesheet',
@ -95,6 +99,19 @@ Twilight.KNOWN_MODULES = {
} }
Twilight.CHAT_ROUTES = [
'collection',
'popout',
'video',
'user-videos',
'user-clips',
'user-events',
'user-followers',
'user-following',
'user'
]
Twilight.ROUTES = { Twilight.ROUTES = {
'front-page': '/', 'front-page': '/',
'collection': '/collections/:collectionID', 'collection': '/collections/:collectionID',
@ -110,10 +127,13 @@ Twilight.ROUTES = {
'dir-all': '/directory/all/:filter?', 'dir-all': '/directory/all/:filter?',
'dir-category': '/directory/:category?', 'dir-category': '/directory/:category?',
'event': '/event/:eventName', 'event': '/event/:eventName',
'following': '/following', 'popout': '/popout/:userName/chat',
'popout': '/popout',
'video': '/videos/:videoID', 'video': '/videos/:videoID',
'user-videos': '/:userName/videos/:filter?', 'user-videos': '/:userName/videos/:filter?',
'user-clips': '/:userName/manager/clips', 'user-clips': '/:userName/clips',
'user-collections': '/:userName/collections',
'user-events': '/:userName/events',
'user-followers': '/:userName/followers',
'user-following': '/:userName/following',
'user': '/:userName' 'user': '/:userName'
} }

View file

@ -19,6 +19,7 @@ export default class ChannelBar extends Module {
this.inject('site.fine'); this.inject('site.fine');
this.inject('site.apollo'); this.inject('site.apollo');
this.inject('metadata'); this.inject('metadata');
this.inject('socket');
this.apollo.registerModifier('ChannelPage_ChannelInfoBar_User', CHANNEL_QUERY); this.apollo.registerModifier('ChannelPage_ChannelInfoBar_User', CHANNEL_QUERY);
this.apollo.registerModifier('ChannelPage_ChannelInfoBar_User', data => { this.apollo.registerModifier('ChannelPage_ChannelInfoBar_User', data => {
@ -32,39 +33,60 @@ export default class ChannelBar extends Module {
this.ChannelBar = this.fine.define( this.ChannelBar = this.fine.define(
'channel-bar', 'channel-bar',
n => n.getTitle && n.getGame && n.renderGame n => n.getTitle && n.getGame && n.renderGame,
['user']
); );
this.HostBar = this.fine.define( this.HostBar = this.fine.define(
'host-container', 'host-container',
n => n.handleReportHosterClick n => n.handleReportHosterClick,
['user']
) )
} }
onEnable() { 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) => { this.ChannelBar.ready((cls, instances) => {
for(const inst of instances) for(const inst of instances)
this.updateChannelBar(inst); this.updateChannelBar(inst);
}); });
this.ChannelBar.on('unmount', this.unmountChannelBar, this);
this.ChannelBar.on('mount', this.updateChannelBar, this);
this.ChannelBar.on('update', this.updateChannelBar, this);
/*this.HostBar.on('mount', inst => { /*this.HostBar.on('unmount', this.unmountHostBar, this);
this.log.info('host-mount', inst, this.fine.getChildNode(inst)); this.HostBar.on('mount', this.updateHostBar, this);
}); this.HostBar.on('update', this.updateHostBar, this);
this.HostBar.ready((cls, instances) => { this.HostBar.ready((cls, instances) => {
for(const inst of instances) for(const inst of instances)
this.log.info('host-found', inst, this.fine.getChildNode(inst)); this.updateHostBar(inst);
})*/ });*/
} }
unmountChannelBar(inst) { // eslint-disable-line class-methods-use-this 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; const timers = inst._ffz_meta_timers;
if ( timers ) if ( timers )
for(const key in timers) for(const key in timers)
@ -75,11 +97,6 @@ export default class ChannelBar extends Module {
} }
updateChannelBar(inst) {
this.updateMetadata(inst);
}
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'); metabar = container && container.querySelector && container.querySelector('.channel-info-bar__action-container > .tw-flex');

View file

@ -10,6 +10,8 @@ import {has, split_chars} from 'utilities/object';
import Module from 'utilities/module'; import Module from 'utilities/module';
import Twilight from 'site';
import Scroller from './scroller'; import Scroller from './scroller';
import ChatLine from './line'; import ChatLine from './line';
import SettingsMenu from './settings_menu'; import SettingsMenu from './settings_menu';
@ -114,17 +116,20 @@ export default class ChatHook extends Module {
this.ChatController = this.fine.define( this.ChatController = this.fine.define(
'chat-controller', 'chat-controller',
n => n.chatService n => n.chatService,
Twilight.CHAT_ROUTES
); );
this.ChatContainer = this.fine.define( this.ChatContainer = this.fine.define(
'chat-container', 'chat-container',
n => n.showViewersList && n.onChatInputFocus n => n.showViewersList && n.onChatInputFocus,
Twilight.CHAT_ROUTES
); );
this.PinnedCheer = this.fine.define( this.PinnedCheer = this.fine.define(
'pinned-cheer', 'pinned-cheer',
n => n.collapseCheer && n.saveRenderedMessageRef n => n.collapseCheer && n.saveRenderedMessageRef,
Twilight.CHAT_ROUTES
); );

View file

@ -4,6 +4,7 @@
// Chat Line // Chat Line
// ============================================================================ // ============================================================================
import Twilight from 'site';
import Module from 'utilities/module'; import Module from 'utilities/module';
//import {Color} from 'utilities/color'; //import {Color} from 'utilities/color';
@ -25,12 +26,14 @@ export default class ChatLine extends Module {
this.ChatLine = this.fine.define( this.ChatLine = this.fine.define(
'chat-line', 'chat-line',
n => n.renderMessageBody && ! n.getMessageParts n => n.renderMessageBody && ! n.getMessageParts,
Twilight.CHAT_ROUTES
); );
this.ChatRoomLine = this.fine.define( this.ChatRoomLine = this.fine.define(
'chat-room-line', 'chat-room-line',
n => n.renderMessageBody && n.getMessageParts n => n.renderMessageBody && n.getMessageParts,
Twilight.CHAT_ROUTES
); );
} }
@ -46,7 +49,7 @@ export default class ChatLine extends Module {
const e = React.createElement; const e = React.createElement;
this.ChatLine.ready((cls, instances) => { this.ChatLine.ready(cls => {
cls.prototype.shouldComponentUpdate = function(props, state) { cls.prototype.shouldComponentUpdate = function(props, state) {
const show = state.alwaysShowMessage || ! props.message.deleted, const show = state.alwaysShowMessage || ! props.message.deleted,
old_show = this._ffz_show; old_show = this._ffz_show;

View file

@ -5,6 +5,7 @@
// ============================================================================ // ============================================================================
import {createElement as e} from 'utilities/dom'; import {createElement as e} from 'utilities/dom';
import Twilight from 'site';
import Module from 'utilities/module'; import Module from 'utilities/module';
export default class Scroller extends Module { export default class Scroller extends Module {
@ -19,7 +20,8 @@ export default class Scroller extends Module {
this.ChatScroller = this.fine.define( this.ChatScroller = this.fine.define(
'chat-scroller', 'chat-scroller',
n => n.saveScrollRef && n.handleScrollEvent n => n.saveScrollRef && n.handleScrollEvent,
Twilight.CHAT_ROUTES
); );
this.settings.add('chat.scroller.freeze', { this.settings.add('chat.scroller.freeze', {

View file

@ -4,7 +4,7 @@
// Chat Settings Menu // Chat Settings Menu
// ============================================================================ // ============================================================================
import {createElement as e} from 'utilities/dom'; import Twilight from 'site';
import Module from 'utilities/module'; import Module from 'utilities/module';
export default class SettingsMenu extends Module { export default class SettingsMenu extends Module {
@ -19,7 +19,8 @@ export default class SettingsMenu extends Module {
this.SettingsMenu = this.fine.define( this.SettingsMenu = this.fine.define(
'chat-settings', 'chat-settings',
n => n.renderUniversalOptions && n.dismissRaidsTooltip n => n.renderUniversalOptions && n.dismissRaidsTooltip,
Twilight.CHAT_ROUTES
); );
} }

View file

@ -1,7 +1,5 @@
.twilight-root { .twilight-root {
.top-nav { .top-nav {
z-index: 9999;
> div { > div {
height: 5rem !important; height: 5rem !important;
} }

View file

@ -21,7 +21,8 @@ export default class BrowsePopular extends SiteModule {
this.ChannelCard = this.fine.define( this.ChannelCard = this.fine.define(
'browse-all-channel-card', 'browse-all-channel-card',
n => n.props && n.props.channelName && n.props.linkTo && n.props.linkTo.state && n.props.linkTo.state.medium === 'twitch_browse_directory' n => n.props && n.props.channelName && n.props.linkTo && n.props.linkTo.state && n.props.linkTo.state.medium === 'twitch_browse_directory',
['dir-all']
); );
this.apollo.registerModifier('BrowsePage_Popular', res => this.modifyStreams(res), false); this.apollo.registerModifier('BrowsePage_Popular', res => this.modifyStreams(res), false);

View file

@ -70,7 +70,8 @@ export default class Following extends SiteModule {
this.ChannelCard = this.fine.define( this.ChannelCard = this.fine.define(
'following-channel-card', 'following-channel-card',
n => n.renderGameBoxArt && n.renderContentType n => n.renderGameBoxArt && n.renderContentType,
['dir-following']
); );
this.apollo.registerModifier('FollowedIndex_CurrentUser', res => { this.apollo.registerModifier('FollowedIndex_CurrentUser', res => {

View file

@ -1,18 +1,5 @@
query { query {
directory { directory {
... on Game {
streams {
edges {
node {
createdAt
type
broadcaster {
profileImageURL(width: 70)
}
}
}
}
}
... on Community { ... on Community {
streams { streams {
edges { edges {
@ -26,5 +13,18 @@ query {
} }
} }
} }
... on Game {
streams {
edges {
node {
createdAt
type
broadcaster {
profileImageURL(width: 70)
}
}
}
}
}
} }
} }

View file

@ -21,7 +21,8 @@ export default class Game extends SiteModule {
this.GameHeader = this.fine.define( this.GameHeader = this.fine.define(
'game-header', 'game-header',
n => n.renderFollowButton && n.renderGameDetailsTab n => n.renderFollowButton && n.renderGameDetailsTab,
['dir-game-index', 'dir-community']
); );
this.apollo.registerModifier('GamePage_Game', GAME_QUERY); this.apollo.registerModifier('GamePage_Game', GAME_QUERY);

View file

@ -35,7 +35,8 @@ export default class Directory extends SiteModule {
this.ChannelCard = this.fine.define( this.ChannelCard = this.fine.define(
'channel-card', 'channel-card',
n => n.props && n.props.streamNode n => n.props && n.props.streamNode,
['dir-community', 'dir-game-index']
); );

View file

@ -23,7 +23,8 @@ export default class Player extends Module {
this.Player = this.fine.define( this.Player = this.fine.define(
'twitch-player', 'twitch-player',
n => n.player && n.onPlayerReady n => n.player && n.onPlayerReady,
['front-page', 'user', 'video']
); );

View file

@ -32,7 +32,8 @@ export default class SubButton extends Module {
this.SubButton = this.fine.define( this.SubButton = this.fine.define(
'sub-button', 'sub-button',
n => n.reportSubMenuAction && n.isUserDataReady n => n.reportSubMenuAction && n.isUserDataReady,
['user', 'video']
); );
} }

View file

@ -43,7 +43,7 @@ export default class SocketClient extends Module {
this._want_connected = false; this._want_connected = false;
this._topics = new Set; this._topics = new Map;
this._pending = []; this._pending = [];
this._awaiting = new Map; this._awaiting = new Map;
@ -426,30 +426,42 @@ export default class SocketClient extends Module {
// Topics // Topics
// ======================================================================== // ========================================================================
subscribe(...topics) { subscribe(referrer, ...topics) {
const t = this._topics; const t = this._topics;
for(const topic of topics) { for(const topic of topics) {
if ( this.connected && ! t.has(topic) ) if ( ! t.has(topic) ) {
if ( this.connected )
this._send('sub', topic); this._send('sub', topic);
t.add(topic); t.set(topic, new Set);
}
const tp = t.get(topic);
tp.add(referrer);
} }
} }
unsubscribe(...topics) { unsubscribe(referrer, ...topics) {
const t = this._topics; const t = this._topics;
for(const topic of topics) { for(const topic of topics) {
if ( this.connected && t.has(topic) ) if ( ! t.has(topic) )
this._send('unsub', topic); continue;
const tp = t.get(topic);
tp.delete(referrer);
if ( ! tp.size ) {
t.delete(topic); t.delete(topic);
if ( this.connected )
this._send('unsub', topic);
}
} }
} }
get topics() { get topics() {
return Array.from(this._topics); return Array.from(this._topics.keys());
} }
} }

View file

@ -17,7 +17,8 @@ export default class Fine extends Module {
this._wrappers = new Map; this._wrappers = new Map;
this._known_classes = new Map; this._known_classes = new Map;
this._observer = null; this._observer = null;
this._waiting = null; this._waiting = [];
this._live_waiting = null;
} }
@ -250,14 +251,39 @@ export default class Fine extends Module {
// Class Wrapping // Class Wrapping
// ======================================================================== // ========================================================================
define(key, criteria) { route(route) {
this._route = route;
this._updateLiveWaiting();
}
_updateLiveWaiting() {
const lw = this._live_waiting = [],
crt = this._waiting_crit = [],
route = this._route;
if ( this._waiting )
for(const waiter of this._waiting)
if ( ! route || ! waiter.routes.length || waiter.routes.includes(route) ) {
lw.push(waiter);
crt.push(waiter.criteria);
}
if ( ! this._live_waiting.length )
this._stopWaiting();
else if ( ! this._waiting_timer )
this._startWaiting();
}
define(key, criteria, routes) {
if ( this._wrappers.has(key) ) if ( this._wrappers.has(key) )
return this._wrappers.get(key); return this._wrappers.get(key);
if ( ! criteria ) if ( ! criteria )
throw new Error('cannot find definition and no criteria provided'); throw new Error('cannot find definition and no criteria provided');
const wrapper = new FineWrapper(key, criteria, this); const wrapper = new FineWrapper(key, criteria, routes, this);
this._wrappers.set(key, wrapper); this._wrappers.set(key, wrapper);
const data = this.searchAll(this.react, [criteria], 1000)[0]; const data = this.searchAll(this.react, [criteria], 1000)[0];
@ -266,11 +292,8 @@ export default class Fine extends Module {
this._known_classes.set(data.cls, wrapper); this._known_classes.set(data.cls, wrapper);
} else { } else {
if ( ! this._waiting )
this._startWaiting();
this._waiting.push(wrapper); this._waiting.push(wrapper);
this._waiting_crit.push(criteria); this._updateLiveWaiting();
} }
return wrapper; return wrapper;
@ -278,7 +301,7 @@ export default class Fine extends Module {
_checkWaiters(nodes) { _checkWaiters(nodes) {
if ( ! this._waiting ) if ( ! this._live_waiting )
return; return;
if ( ! Array.isArray(nodes) ) if ( ! Array.isArray(nodes) )
@ -287,12 +310,12 @@ export default class Fine extends Module {
for(let node of nodes) { for(let node of nodes) {
if ( ! node ) if ( ! node )
node = this.react; node = this.react;
else if ( node._reactInternalInstance ) else if ( node._reactInternalFiber )
node = node._reactInternalInstance; node = node._reactInternalFiber;
else if ( node instanceof Node ) else if ( node instanceof Node )
node = this.getReactInstance(node); node = this.getReactInstance(node);
if ( ! node || ! this._waiting.length ) if ( ! node || ! this._live_waiting.length )
continue; continue;
const data = this.searchAll(node, this._waiting_crit, 1000); const data = this.searchAll(node, this._waiting_crit, 1000);
@ -300,26 +323,27 @@ export default class Fine extends Module {
while(i-- > 0) { while(i-- > 0) {
if ( data[i].cls ) { if ( data[i].cls ) {
const d = data[i], const d = data[i],
w = this._waiting.splice(i, 1)[0]; w = this._live_waiting.splice(i, 1)[0];
this._waiting_crit.splice(i, 1); this._waiting_crit.splice(i, 1);
this.log.info(`Found class for "${w.name}" at depth ${d.depth}`, d);
const idx = this._waiting.indexOf(w);
if ( idx !== -1 )
this._waiting.splice(idx, 1);
this.log.info(`Found class for "${w.name}" at depth ${d.depth}`, d);
w._set(d.cls, d.instances); w._set(d.cls, d.instances);
} }
} }
} }
if ( ! this._waiting.length ) if ( ! this._live_waiting.length )
this._stopWaiting(); this._stopWaiting();
} }
_startWaiting() { _startWaiting() {
this.log.info('Installing MutationObserver.'); this.log.info('Installing MutationObserver.');
this._waiting = [];
this._waiting_crit = [];
this._waiting_timer = setInterval(() => this._checkWaiters(), 500); this._waiting_timer = setInterval(() => this._checkWaiters(), 500);
if ( ! this._observer ) if ( ! this._observer )
@ -343,7 +367,7 @@ export default class Fine extends Module {
if ( this._waiting_timer ) if ( this._waiting_timer )
clearInterval(this._waiting_timer); clearInterval(this._waiting_timer);
this._waiting = null; this._live_waiting = null;
this._waiting_crit = null; this._waiting_crit = null;
this._waiting_timer = null; this._waiting_timer = null;
} }
@ -364,7 +388,7 @@ const EVENTS = {
export class FineWrapper extends EventEmitter { export class FineWrapper extends EventEmitter {
constructor(name, criteria, fine) { constructor(name, criteria, routes, fine) {
super(); super();
this.name = name; this.name = name;
@ -372,6 +396,7 @@ export class FineWrapper extends EventEmitter {
this.fine = fine; this.fine = fine;
this.instances = new Set; this.instances = new Set;
this.routes = routes || [];
this._wrapped = new Set; this._wrapped = new Set;
this._class = null; this._class = null;