diff --git a/src/modules/chat/room.js b/src/modules/chat/room.js index 7e9565bc..912ed20a 100644 --- a/src/modules/chat/room.js +++ b/src/modules/chat/room.js @@ -65,7 +65,7 @@ export default class Room { if ( this.manager.rooms[this._login] === this ) 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 ) @@ -89,7 +89,7 @@ export default class Room { const old_room = this.manager.rooms[this._login]; if ( old_room === this ) { 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; 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); } diff --git a/src/sites/twitch-twilight/index.js b/src/sites/twitch-twilight/index.js index ca0f5ef2..51fbbc82 100644 --- a/src/sites/twitch-twilight/index.js +++ b/src/sites/twitch-twilight/index.js @@ -50,8 +50,12 @@ export default class Twilight extends BaseSite { this.router.on(':route', (route, match) => { 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', { href: MAIN_URL, 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 = { 'front-page': '/', 'collection': '/collections/:collectionID', @@ -110,10 +127,13 @@ Twilight.ROUTES = { 'dir-all': '/directory/all/:filter?', 'dir-category': '/directory/:category?', 'event': '/event/:eventName', - 'following': '/following', - 'popout': '/popout', + 'popout': '/popout/:userName/chat', 'video': '/videos/:videoID', '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' } \ No newline at end of file diff --git a/src/sites/twitch-twilight/modules/channel_bar.js b/src/sites/twitch-twilight/modules/channel_bar.js index 5369a7c6..6efc6eb0 100644 --- a/src/sites/twitch-twilight/modules/channel_bar.js +++ b/src/sites/twitch-twilight/modules/channel_bar.js @@ -19,6 +19,7 @@ export default class ChannelBar extends Module { this.inject('site.fine'); this.inject('site.apollo'); this.inject('metadata'); + this.inject('socket'); this.apollo.registerModifier('ChannelPage_ChannelInfoBar_User', CHANNEL_QUERY); this.apollo.registerModifier('ChannelPage_ChannelInfoBar_User', data => { @@ -32,39 +33,60 @@ export default class ChannelBar extends Module { this.ChannelBar = this.fine.define( 'channel-bar', - n => n.getTitle && n.getGame && n.renderGame + n => n.getTitle && n.getGame && n.renderGame, + ['user'] ); this.HostBar = this.fine.define( 'host-container', - n => n.handleReportHosterClick + 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.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.log.info('host-mount', inst, this.fine.getChildNode(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.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; if ( timers ) for(const key in timers) @@ -75,11 +97,6 @@ export default class ChannelBar extends Module { } - updateChannelBar(inst) { - this.updateMetadata(inst); - } - - updateMetadata(inst, keys) { const container = this.fine.getChildNode(inst), metabar = container && container.querySelector && container.querySelector('.channel-info-bar__action-container > .tw-flex'); diff --git a/src/sites/twitch-twilight/modules/chat/index.js b/src/sites/twitch-twilight/modules/chat/index.js index 40c83c0d..425a0f7e 100644 --- a/src/sites/twitch-twilight/modules/chat/index.js +++ b/src/sites/twitch-twilight/modules/chat/index.js @@ -10,6 +10,8 @@ import {has, split_chars} from 'utilities/object'; import Module from 'utilities/module'; +import Twilight from 'site'; + import Scroller from './scroller'; import ChatLine from './line'; import SettingsMenu from './settings_menu'; @@ -114,17 +116,20 @@ export default class ChatHook extends Module { this.ChatController = this.fine.define( 'chat-controller', - n => n.chatService + n => n.chatService, + Twilight.CHAT_ROUTES ); this.ChatContainer = this.fine.define( 'chat-container', - n => n.showViewersList && n.onChatInputFocus + n => n.showViewersList && n.onChatInputFocus, + Twilight.CHAT_ROUTES ); this.PinnedCheer = this.fine.define( 'pinned-cheer', - n => n.collapseCheer && n.saveRenderedMessageRef + n => n.collapseCheer && n.saveRenderedMessageRef, + Twilight.CHAT_ROUTES ); diff --git a/src/sites/twitch-twilight/modules/chat/line.js b/src/sites/twitch-twilight/modules/chat/line.js index 094625ab..fa6f293e 100644 --- a/src/sites/twitch-twilight/modules/chat/line.js +++ b/src/sites/twitch-twilight/modules/chat/line.js @@ -4,6 +4,7 @@ // Chat Line // ============================================================================ +import Twilight from 'site'; import Module from 'utilities/module'; //import {Color} from 'utilities/color'; @@ -25,12 +26,14 @@ export default class ChatLine extends Module { this.ChatLine = this.fine.define( 'chat-line', - n => n.renderMessageBody && ! n.getMessageParts + n => n.renderMessageBody && ! n.getMessageParts, + Twilight.CHAT_ROUTES ); this.ChatRoomLine = this.fine.define( '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; - this.ChatLine.ready((cls, instances) => { + this.ChatLine.ready(cls => { cls.prototype.shouldComponentUpdate = function(props, state) { const show = state.alwaysShowMessage || ! props.message.deleted, old_show = this._ffz_show; diff --git a/src/sites/twitch-twilight/modules/chat/scroller.js b/src/sites/twitch-twilight/modules/chat/scroller.js index 676f1fbb..ec23d3b4 100644 --- a/src/sites/twitch-twilight/modules/chat/scroller.js +++ b/src/sites/twitch-twilight/modules/chat/scroller.js @@ -5,6 +5,7 @@ // ============================================================================ import {createElement as e} from 'utilities/dom'; +import Twilight from 'site'; import Module from 'utilities/module'; export default class Scroller extends Module { @@ -19,7 +20,8 @@ export default class Scroller extends Module { this.ChatScroller = this.fine.define( 'chat-scroller', - n => n.saveScrollRef && n.handleScrollEvent + n => n.saveScrollRef && n.handleScrollEvent, + Twilight.CHAT_ROUTES ); this.settings.add('chat.scroller.freeze', { diff --git a/src/sites/twitch-twilight/modules/chat/settings_menu.js b/src/sites/twitch-twilight/modules/chat/settings_menu.js index 03fafb1f..aca0db2c 100644 --- a/src/sites/twitch-twilight/modules/chat/settings_menu.js +++ b/src/sites/twitch-twilight/modules/chat/settings_menu.js @@ -4,7 +4,7 @@ // Chat Settings Menu // ============================================================================ -import {createElement as e} from 'utilities/dom'; +import Twilight from 'site'; import Module from 'utilities/module'; export default class SettingsMenu extends Module { @@ -19,7 +19,8 @@ export default class SettingsMenu extends Module { this.SettingsMenu = this.fine.define( 'chat-settings', - n => n.renderUniversalOptions && n.dismissRaidsTooltip + n => n.renderUniversalOptions && n.dismissRaidsTooltip, + Twilight.CHAT_ROUTES ); } diff --git a/src/sites/twitch-twilight/modules/css_tweaks/styles/minimal-navigation.scss b/src/sites/twitch-twilight/modules/css_tweaks/styles/minimal-navigation.scss index fb0668a4..bb14d747 100644 --- a/src/sites/twitch-twilight/modules/css_tweaks/styles/minimal-navigation.scss +++ b/src/sites/twitch-twilight/modules/css_tweaks/styles/minimal-navigation.scss @@ -1,7 +1,5 @@ .twilight-root { .top-nav { - z-index: 9999; - > div { height: 5rem !important; } diff --git a/src/sites/twitch-twilight/modules/directory/browse_popular.js b/src/sites/twitch-twilight/modules/directory/browse_popular.js index e70c936b..56f223a4 100644 --- a/src/sites/twitch-twilight/modules/directory/browse_popular.js +++ b/src/sites/twitch-twilight/modules/directory/browse_popular.js @@ -21,7 +21,8 @@ export default class BrowsePopular extends SiteModule { this.ChannelCard = this.fine.define( '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); diff --git a/src/sites/twitch-twilight/modules/directory/following.js b/src/sites/twitch-twilight/modules/directory/following.js index 9ece895f..8838d286 100644 --- a/src/sites/twitch-twilight/modules/directory/following.js +++ b/src/sites/twitch-twilight/modules/directory/following.js @@ -70,7 +70,8 @@ export default class Following extends SiteModule { this.ChannelCard = this.fine.define( 'following-channel-card', - n => n.renderGameBoxArt && n.renderContentType + n => n.renderGameBoxArt && n.renderContentType, + ['dir-following'] ); this.apollo.registerModifier('FollowedIndex_CurrentUser', res => { diff --git a/src/sites/twitch-twilight/modules/directory/game.gql b/src/sites/twitch-twilight/modules/directory/game.gql index e9914785..7f31632d 100644 --- a/src/sites/twitch-twilight/modules/directory/game.gql +++ b/src/sites/twitch-twilight/modules/directory/game.gql @@ -1,6 +1,6 @@ query { directory { - ... on Game { + ... on Community { streams { edges { node { @@ -13,7 +13,7 @@ query { } } } - ... on Community { + ... on Game { streams { edges { node { diff --git a/src/sites/twitch-twilight/modules/directory/game.js b/src/sites/twitch-twilight/modules/directory/game.js index 2f0fabef..9f29f64f 100644 --- a/src/sites/twitch-twilight/modules/directory/game.js +++ b/src/sites/twitch-twilight/modules/directory/game.js @@ -21,7 +21,8 @@ export default class Game extends SiteModule { this.GameHeader = this.fine.define( 'game-header', - n => n.renderFollowButton && n.renderGameDetailsTab + n => n.renderFollowButton && n.renderGameDetailsTab, + ['dir-game-index', 'dir-community'] ); this.apollo.registerModifier('GamePage_Game', GAME_QUERY); diff --git a/src/sites/twitch-twilight/modules/directory/index.js b/src/sites/twitch-twilight/modules/directory/index.js index 1e95ec3e..bf9e8d1d 100644 --- a/src/sites/twitch-twilight/modules/directory/index.js +++ b/src/sites/twitch-twilight/modules/directory/index.js @@ -35,7 +35,8 @@ export default class Directory extends SiteModule { this.ChannelCard = this.fine.define( 'channel-card', - n => n.props && n.props.streamNode + n => n.props && n.props.streamNode, + ['dir-community', 'dir-game-index'] ); diff --git a/src/sites/twitch-twilight/modules/player.js b/src/sites/twitch-twilight/modules/player.js index c56886db..d30fe099 100644 --- a/src/sites/twitch-twilight/modules/player.js +++ b/src/sites/twitch-twilight/modules/player.js @@ -23,7 +23,8 @@ export default class Player extends Module { this.Player = this.fine.define( 'twitch-player', - n => n.player && n.onPlayerReady + n => n.player && n.onPlayerReady, + ['front-page', 'user', 'video'] ); diff --git a/src/sites/twitch-twilight/modules/sub_button.js b/src/sites/twitch-twilight/modules/sub_button.js index 80cce842..fbedb3c9 100644 --- a/src/sites/twitch-twilight/modules/sub_button.js +++ b/src/sites/twitch-twilight/modules/sub_button.js @@ -32,7 +32,8 @@ export default class SubButton extends Module { this.SubButton = this.fine.define( 'sub-button', - n => n.reportSubMenuAction && n.isUserDataReady + n => n.reportSubMenuAction && n.isUserDataReady, + ['user', 'video'] ); } diff --git a/src/socket.js b/src/socket.js index 1e16f626..640e7e75 100644 --- a/src/socket.js +++ b/src/socket.js @@ -43,7 +43,7 @@ export default class SocketClient extends Module { this._want_connected = false; - this._topics = new Set; + this._topics = new Map; this._pending = []; this._awaiting = new Map; @@ -426,30 +426,42 @@ export default class SocketClient extends Module { // Topics // ======================================================================== - subscribe(...topics) { + subscribe(referrer, ...topics) { const t = this._topics; for(const topic of topics) { - if ( this.connected && ! t.has(topic) ) - this._send('sub', topic); + if ( ! t.has(topic) ) { + if ( this.connected ) + 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; for(const topic of topics) { - if ( this.connected && t.has(topic) ) - this._send('unsub', topic); + if ( ! t.has(topic) ) + continue; - t.delete(topic); + const tp = t.get(topic); + tp.delete(referrer); + + if ( ! tp.size ) { + t.delete(topic); + if ( this.connected ) + this._send('unsub', topic); + } } } get topics() { - return Array.from(this._topics); + return Array.from(this._topics.keys()); } } diff --git a/src/utilities/compat/fine.js b/src/utilities/compat/fine.js index ab6700d4..69ab5ac6 100644 --- a/src/utilities/compat/fine.js +++ b/src/utilities/compat/fine.js @@ -17,7 +17,8 @@ export default class Fine extends Module { this._wrappers = new Map; this._known_classes = new Map; this._observer = null; - this._waiting = null; + this._waiting = []; + this._live_waiting = null; } @@ -250,14 +251,39 @@ export default class Fine extends Module { // 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) ) return this._wrappers.get(key); if ( ! criteria ) 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); 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); } else { - if ( ! this._waiting ) - this._startWaiting(); - this._waiting.push(wrapper); - this._waiting_crit.push(criteria); + this._updateLiveWaiting(); } return wrapper; @@ -278,7 +301,7 @@ export default class Fine extends Module { _checkWaiters(nodes) { - if ( ! this._waiting ) + if ( ! this._live_waiting ) return; if ( ! Array.isArray(nodes) ) @@ -287,12 +310,12 @@ export default class Fine extends Module { for(let node of nodes) { if ( ! node ) node = this.react; - else if ( node._reactInternalInstance ) - node = node._reactInternalInstance; + else if ( node._reactInternalFiber ) + node = node._reactInternalFiber; else if ( node instanceof Node ) node = this.getReactInstance(node); - if ( ! node || ! this._waiting.length ) + if ( ! node || ! this._live_waiting.length ) continue; const data = this.searchAll(node, this._waiting_crit, 1000); @@ -300,26 +323,27 @@ export default class Fine extends Module { while(i-- > 0) { if ( data[i].cls ) { 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.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); } } } - if ( ! this._waiting.length ) + if ( ! this._live_waiting.length ) this._stopWaiting(); } _startWaiting() { this.log.info('Installing MutationObserver.'); - - this._waiting = []; - this._waiting_crit = []; this._waiting_timer = setInterval(() => this._checkWaiters(), 500); if ( ! this._observer ) @@ -343,7 +367,7 @@ export default class Fine extends Module { if ( this._waiting_timer ) clearInterval(this._waiting_timer); - this._waiting = null; + this._live_waiting = null; this._waiting_crit = null; this._waiting_timer = null; } @@ -364,7 +388,7 @@ const EVENTS = { export class FineWrapper extends EventEmitter { - constructor(name, criteria, fine) { + constructor(name, criteria, routes, fine) { super(); this.name = name; @@ -372,6 +396,7 @@ export class FineWrapper extends EventEmitter { this.fine = fine; this.instances = new Set; + this.routes = routes || []; this._wrapped = new Set; this._class = null;