mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-06-28 15:27:43 +00:00
4.20.11
* Added: Setting to disable Channel Hosting. * Added: Setting to hide streams tagged as Promoted from the directory. * Fixed: Tool-tips not appearing when an element is open in fullscreen. * Fixed: Bug when destroying a tool-tip instance. * Fixed: Directory cards not updating with FFZ features on the front page. * API Added: `Subpump` module for manipulating Twitch PubSub events. * API Changed: Add-Ons can now define custom settings profile filters.
This commit is contained in:
parent
ced61d2bfc
commit
22c60050e0
17 changed files with 356 additions and 96 deletions
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "frankerfacez",
|
"name": "frankerfacez",
|
||||||
"author": "Dan Salvato LLC",
|
"author": "Dan Salvato LLC",
|
||||||
"version": "4.20.10",
|
"version": "4.20.11",
|
||||||
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
@ -334,7 +334,7 @@ export default class Actions extends Module {
|
||||||
target._ffz_destroy = target._ffz_outside = target._ffz_on_destroy = null;
|
target._ffz_destroy = target._ffz_outside = target._ffz_on_destroy = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const parent = document.body.querySelector('#root>div') || document.body,
|
const parent = document.fullscreenElement || document.body.querySelector('#root>div') || document.body,
|
||||||
tt = target._ffz_popup = new Tooltip(parent, target, {
|
tt = target._ffz_popup = new Tooltip(parent, target, {
|
||||||
logger: this.log,
|
logger: this.log,
|
||||||
manual: true,
|
manual: true,
|
||||||
|
@ -807,8 +807,8 @@ export default class Actions extends Module {
|
||||||
return this.log.warn(`No click handler for action provider "${data.action}"`);
|
return this.log.warn(`No click handler for action provider "${data.action}"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( target._ffz_tooltip$0 )
|
if ( target._ffz_tooltip )
|
||||||
target._ffz_tooltip$0.hide();
|
target._ffz_tooltip.hide();
|
||||||
|
|
||||||
return data.definition.click.call(this, event, data);
|
return data.definition.click.call(this, event, data);
|
||||||
}
|
}
|
||||||
|
@ -827,8 +827,8 @@ export default class Actions extends Module {
|
||||||
if ( target.classList.contains('disabled') )
|
if ( target.classList.contains('disabled') )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if ( target._ffz_tooltip$0 )
|
if ( target._ffz_tooltip )
|
||||||
target._ffz_tooltip$0.hide();
|
target._ffz_tooltip.hide();
|
||||||
|
|
||||||
if ( ! data.definition.context && ! data.definition.uses_reason )
|
if ( ! data.definition.context && ! data.definition.uses_reason )
|
||||||
return;
|
return;
|
||||||
|
@ -847,8 +847,8 @@ export default class Actions extends Module {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
const target = event.target;
|
const target = event.target;
|
||||||
if ( target._ffz_tooltip$0 )
|
if ( target._ffz_tooltip )
|
||||||
target._ffz_tooltip$0.hide();
|
target._ffz_tooltip.hide();
|
||||||
|
|
||||||
this.renderUserContext(target, actions);
|
this.renderUserContext(target, actions);
|
||||||
}
|
}
|
||||||
|
|
|
@ -386,7 +386,7 @@ export default class Emotes extends Module {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this.toggleFavorite(source, id);
|
this.toggleFavorite(source, id);
|
||||||
const tt = target._ffz_tooltip$0;
|
const tt = target._ffz_tooltip;
|
||||||
if ( tt && tt.visible ) {
|
if ( tt && tt.visible ) {
|
||||||
tt.hide();
|
tt.hide();
|
||||||
setTimeout(() => document.contains(target) && tt.show(), 0);
|
setTimeout(() => document.contains(target) && tt.show(), 0);
|
||||||
|
|
|
@ -56,7 +56,7 @@ export default class Overrides extends Module {
|
||||||
v.$destroy();
|
v.$destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
const parent = document.body.querySelector('#root>div') || document.body;
|
const parent = document.fullscreenElement || document.body.querySelector('#root>div') || document.body;
|
||||||
|
|
||||||
popup = new Tooltip(parent, [], {
|
popup = new Tooltip(parent, [], {
|
||||||
logger: this.log,
|
logger: this.log,
|
||||||
|
|
|
@ -144,8 +144,10 @@ export default {
|
||||||
props: ['item', 'context'],
|
props: ['item', 'context'],
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
|
const settings = this.context.getFFZ().resolve('settings');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
filters: deep_copy(require('src/settings/filters.js')),
|
filters: deep_copy(settings.filters),
|
||||||
|
|
||||||
old_name: null,
|
old_name: null,
|
||||||
old_desc: null,
|
old_desc: null,
|
||||||
|
|
|
@ -59,16 +59,30 @@ export default class TooltipProvider extends Module {
|
||||||
|
|
||||||
this.types.text = target => sanitize(target.dataset.title);
|
this.types.text = target => sanitize(target.dataset.title);
|
||||||
this.types.html = target => target.dataset.title;
|
this.types.html = target => target.dataset.title;
|
||||||
|
|
||||||
|
this.onFSChange = this.onFSChange.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
onEnable() {
|
onEnable() {
|
||||||
const container = document.querySelector('.sunlight-root') || document.querySelector('#root>div') || document.querySelector('#root') || document.querySelector('.clips-root') || document.body;
|
const container = document.querySelector('.sunlight-root') || document.querySelector('#root>div') || document.querySelector('#root') || document.querySelector('.clips-root') || document.body;
|
||||||
|
|
||||||
|
window.addEventListener('fullscreenchange', this.onFSChange);
|
||||||
|
|
||||||
// is_minimal = false; //container && container.classList.contains('twilight-minimal-root');
|
// is_minimal = false; //container && container.classList.contains('twilight-minimal-root');
|
||||||
|
|
||||||
this.tips = new Tooltip(container, 'ffz-tooltip', {
|
this.container = container;
|
||||||
|
this.tip_element = container;
|
||||||
|
this.tips = this._createInstance(container);
|
||||||
|
|
||||||
|
this.on(':cleanup', this.cleanup);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_createInstance(container) {
|
||||||
|
return new Tooltip(container, 'ffz-tooltip', {
|
||||||
html: true,
|
html: true,
|
||||||
i18n: this.i18n,
|
i18n: this.i18n,
|
||||||
|
live: true,
|
||||||
|
|
||||||
delayHide: this.checkDelayHide.bind(this),
|
delayHide: this.checkDelayHide.bind(this),
|
||||||
delayShow: this.checkDelayShow.bind(this),
|
delayShow: this.checkDelayShow.bind(this),
|
||||||
|
@ -99,10 +113,20 @@ export default class TooltipProvider extends Module {
|
||||||
this.emit(':leave', target, tip, event);
|
this.emit(':leave', target, tip, event);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.on(':cleanup', this.cleanup);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
onFSChange() {
|
||||||
|
const tip_element = document.fullscreenElement || this.container;
|
||||||
|
if ( tip_element !== this.tip_element ) {
|
||||||
|
this.tips.destroy();
|
||||||
|
this.tip_element = tip_element;
|
||||||
|
this.tips = this._createInstance(tip_element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
cleanup() {
|
cleanup() {
|
||||||
this.tips.cleanup();
|
this.tips.cleanup();
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,8 @@ import SettingsProfile from './profile';
|
||||||
import SettingsContext from './context';
|
import SettingsContext from './context';
|
||||||
import MigrationManager from './migration';
|
import MigrationManager from './migration';
|
||||||
|
|
||||||
|
import * as FILTERS from './filters';
|
||||||
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// SettingsManager
|
// SettingsManager
|
||||||
|
@ -38,12 +40,19 @@ export default class SettingsManager extends Module {
|
||||||
this.ui_structures = new Map;
|
this.ui_structures = new Map;
|
||||||
this.definitions = new Map;
|
this.definitions = new Map;
|
||||||
|
|
||||||
|
// Filters
|
||||||
|
this.filters = {};
|
||||||
|
|
||||||
|
for(const key in FILTERS)
|
||||||
|
if ( has(FILTERS, key) )
|
||||||
|
this.filters[key] = FILTERS[key];
|
||||||
|
|
||||||
|
|
||||||
// Create our provider as early as possible.
|
// Create our provider as early as possible.
|
||||||
const provider = this.provider = this._createProvider();
|
const provider = this.provider = this._createProvider();
|
||||||
this.log.info(`Using Provider: ${provider.constructor.name}`);
|
this.log.info(`Using Provider: ${provider.constructor.name}`);
|
||||||
provider.on('changed', this._onProviderChange, this);
|
provider.on('changed', this._onProviderChange, this);
|
||||||
|
|
||||||
|
|
||||||
this.migrations = new MigrationManager(this);
|
this.migrations = new MigrationManager(this);
|
||||||
|
|
||||||
// Also create the main context as early as possible.
|
// Also create the main context as early as possible.
|
||||||
|
@ -63,6 +72,20 @@ export default class SettingsManager extends Module {
|
||||||
this.enable();
|
this.enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
addFilter(key, data) {
|
||||||
|
if ( this.filters[key] )
|
||||||
|
return this.log.warn('Tried to add already existing filter', key);
|
||||||
|
|
||||||
|
this.filters[key] = data;
|
||||||
|
this.updateRoutes();
|
||||||
|
}
|
||||||
|
|
||||||
|
getFilterBasicEditor() { // eslint-disable-line class-methods-use-this
|
||||||
|
return () => import(/* webpackChunkName: 'main-menu' */ './components/basic-toggle.vue')
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
generateLog() {
|
generateLog() {
|
||||||
const out = [];
|
const out = [];
|
||||||
for(const [key, value] of this.main_context.__cache.entries())
|
for(const [key, value] of this.main_context.__cache.entries())
|
||||||
|
|
|
@ -58,7 +58,7 @@ export default class SettingsProfile extends EventEmitter {
|
||||||
|
|
||||||
matches(context) {
|
matches(context) {
|
||||||
if ( ! this.matcher )
|
if ( ! this.matcher )
|
||||||
this.matcher = createTester(this.context, require('./filters'));
|
this.matcher = createTester(this.context, this.manager.filters);
|
||||||
|
|
||||||
return this.matcher(context);
|
return this.matcher(context);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import Fine from 'utilities/compat/fine';
|
||||||
import FineRouter from 'utilities/compat/fine-router';
|
import FineRouter from 'utilities/compat/fine-router';
|
||||||
import Apollo from 'utilities/compat/apollo';
|
import Apollo from 'utilities/compat/apollo';
|
||||||
import TwitchData from 'utilities/twitch-data';
|
import TwitchData from 'utilities/twitch-data';
|
||||||
|
import Subpump from 'utilities/compat/subpump';
|
||||||
|
|
||||||
import Switchboard from './switchboard';
|
import Switchboard from './switchboard';
|
||||||
|
|
||||||
|
@ -36,6 +37,7 @@ export default class Twilight extends BaseSite {
|
||||||
this.inject(Apollo, false);
|
this.inject(Apollo, false);
|
||||||
this.inject(TwitchData);
|
this.inject(TwitchData);
|
||||||
this.inject(Switchboard);
|
this.inject(Switchboard);
|
||||||
|
this.inject(Subpump);
|
||||||
|
|
||||||
this._dom_updates = [];
|
this._dom_updates = [];
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
import Module from 'utilities/module';
|
import Module from 'utilities/module';
|
||||||
import { Color } from 'utilities/color';
|
import { Color } from 'utilities/color';
|
||||||
import {debounce} from 'utilities/object';
|
import {debounce} from 'utilities/object';
|
||||||
|
import { valueToNode } from 'C:/Users/Stendec/AppData/Local/Microsoft/TypeScript/3.8/node_modules/@babel/types/lib/index';
|
||||||
|
|
||||||
|
|
||||||
const USER_PAGES = ['user', 'video', 'user-video', 'user-clip', 'user-videos', 'user-clips', 'user-collections', 'user-events', 'user-followers', 'user-following'];
|
const USER_PAGES = ['user', 'video', 'user-video', 'user-clip', 'user-videos', 'user-clips', 'user-collections', 'user-events', 'user-followers', 'user-following'];
|
||||||
|
@ -22,6 +23,7 @@ export default class Channel extends Module {
|
||||||
this.inject('settings');
|
this.inject('settings');
|
||||||
this.inject('site.css_tweaks');
|
this.inject('site.css_tweaks');
|
||||||
this.inject('site.elemental');
|
this.inject('site.elemental');
|
||||||
|
this.inject('site.subpump');
|
||||||
this.inject('site.fine');
|
this.inject('site.fine');
|
||||||
this.inject('site.router');
|
this.inject('site.router');
|
||||||
this.inject('site.twitch_data');
|
this.inject('site.twitch_data');
|
||||||
|
@ -37,11 +39,16 @@ export default class Channel extends Module {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/*this.SideNav = this.elemental.define(
|
this.settings.add('channel.hosting.enable', {
|
||||||
'side-nav', '.side-bar-contents .side-nav-section:first-child',
|
default: true,
|
||||||
null,
|
ui: {
|
||||||
{childNodes: true, subtree: true}, 1
|
path: 'Channel > Behavior >> Hosting',
|
||||||
);*/
|
title: 'Enable Channel Hosting',
|
||||||
|
component: 'setting-check-box'
|
||||||
|
},
|
||||||
|
changed: val => ! val && this.InfoBar.each(el => this.updateBar(el))
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
this.ChannelRoot = this.elemental.define(
|
this.ChannelRoot = this.elemental.define(
|
||||||
'channel-root', '.channel-root',
|
'channel-root', '.channel-root',
|
||||||
|
@ -59,10 +66,6 @@ export default class Channel extends Module {
|
||||||
onEnable() {
|
onEnable() {
|
||||||
this.updateChannelColor();
|
this.updateChannelColor();
|
||||||
|
|
||||||
//this.SideNav.on('mount', this.updateHidden, this);
|
|
||||||
//this.SideNav.on('mutate', this.updateHidden, this);
|
|
||||||
//this.SideNav.each(el => this.updateHidden(el));
|
|
||||||
|
|
||||||
this.ChannelRoot.on('mount', this.updateRoot, this);
|
this.ChannelRoot.on('mount', this.updateRoot, this);
|
||||||
this.ChannelRoot.on('mutate', this.updateRoot, this);
|
this.ChannelRoot.on('mutate', this.updateRoot, this);
|
||||||
this.ChannelRoot.on('unmount', this.removeRoot, this);
|
this.ChannelRoot.on('unmount', this.removeRoot, this);
|
||||||
|
@ -73,10 +76,13 @@ export default class Channel extends Module {
|
||||||
this.InfoBar.on('unmount', this.removeBar, this);
|
this.InfoBar.on('unmount', this.removeBar, this);
|
||||||
this.InfoBar.each(el => this.updateBar(el));
|
this.InfoBar.each(el => this.updateBar(el));
|
||||||
|
|
||||||
|
this.subpump.on(':pubsub-message', this.onPubSub, this);
|
||||||
|
|
||||||
this.router.on(':route', route => {
|
this.router.on(':route', route => {
|
||||||
if ( route?.name === 'user' )
|
if ( route?.name === 'user' )
|
||||||
setTimeout(this.maybeClickChat.bind(this), 1000);
|
setTimeout(this.maybeClickChat.bind(this), 1000);
|
||||||
}, this);
|
}, this);
|
||||||
|
|
||||||
this.maybeClickChat();
|
this.maybeClickChat();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,21 +94,50 @@ export default class Channel extends Module {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*updateHidden(el) { // eslint-disable-line class-methods-use-this
|
setHost(channel_id, channel_login, target_id, target_login) {
|
||||||
if ( ! el._ffz_raf )
|
const topic = `stream-chat-room-v1.${channel_id}`;
|
||||||
el._ffz_raf = requestAnimationFrame(() => {
|
|
||||||
el._ffz_raf = null;
|
|
||||||
const nodes = el.querySelectorAll('.side-nav-card');
|
|
||||||
for(const node of nodes) {
|
|
||||||
const react = this.fine.getReactInstance(node),
|
|
||||||
props = react?.return?.return?.return?.memoizedProps;
|
|
||||||
|
|
||||||
const offline = props?.offline ?? node.querySelector('.side-nav-card__avatar--offline') != null;
|
|
||||||
node.classList.toggle('ffz--offline-side-nav', offline);
|
|
||||||
|
|
||||||
|
this.subpump.inject(topic, {
|
||||||
|
type: 'host_target_change',
|
||||||
|
data: {
|
||||||
|
channel_id,
|
||||||
|
channel_login,
|
||||||
|
target_channel_id: target_id || null,
|
||||||
|
target_channel_login: target_login || null,
|
||||||
|
previous_target_channel_id: null,
|
||||||
|
num_viewers: 0
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}*/
|
|
||||||
|
this.subpump.inject(topic, {
|
||||||
|
type: 'host_target_change_v2',
|
||||||
|
data: {
|
||||||
|
channel_id,
|
||||||
|
channel_login,
|
||||||
|
target_channel_id: target_id || null,
|
||||||
|
target_channel_login: target_login || null,
|
||||||
|
previous_target_channel_id: null,
|
||||||
|
num_viewers: 0
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
onPubSub(event) {
|
||||||
|
if ( event.prefix !== 'stream-chat-room-v1' || this.settings.get('channel.hosting.enable') )
|
||||||
|
return;
|
||||||
|
|
||||||
|
const type = event.message.type;
|
||||||
|
if ( type === 'host_target_change' || type === 'host_target_change_v2' ) {
|
||||||
|
this.log.info('Nulling Host Target Change', type);
|
||||||
|
event.message.data.target_channel_id = null;
|
||||||
|
event.message.data.target_channel_login = null;
|
||||||
|
event.message.data.previous_target_channel_id = null;
|
||||||
|
event.message.data.num_viewers = 0;
|
||||||
|
event.markChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
updateSubscription(login) {
|
updateSubscription(login) {
|
||||||
if ( this._subbed_login === login )
|
if ( this._subbed_login === login )
|
||||||
|
@ -154,6 +189,9 @@ export default class Channel extends Module {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( ! this.settings.get('channel.hosting.enable') && props.hostLogin )
|
||||||
|
this.setHost(props.channelID, props.channelLogin, null, null);
|
||||||
|
|
||||||
this.updateSubscription(props.channelLogin);
|
this.updateSubscription(props.channelLogin);
|
||||||
this.updateMetadata(el);
|
this.updateMetadata(el);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1155,7 +1155,7 @@ export default class EmoteMenu extends Module {
|
||||||
|
|
||||||
/*clickRefresh(event) {
|
/*clickRefresh(event) {
|
||||||
const target = event.currentTarget,
|
const target = event.currentTarget,
|
||||||
tt = target && target._ffz_tooltip$0;
|
tt = target && target._ffz_tooltip;
|
||||||
|
|
||||||
if ( tt && tt.hide )
|
if ( tt && tt.hide )
|
||||||
tt.hide();
|
tt.hide();
|
||||||
|
|
|
@ -19,9 +19,9 @@ export const CARD_CONTEXTS = ((e ={}) => {
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
||||||
const CREATIVE_ID = 488191;
|
//const CREATIVE_ID = 488191;
|
||||||
|
|
||||||
const DIR_ROUTES = ['dir', 'dir-community', 'dir-community-index', 'dir-creative', 'dir-following', 'dir-game-index', 'dir-game-clips', 'dir-game-videos', 'dir-all', 'dir-category', 'user-videos', 'user-clips'];
|
const DIR_ROUTES = ['front-page', 'dir', 'dir-community', 'dir-community-index', 'dir-creative', 'dir-following', 'dir-game-index', 'dir-game-clips', 'dir-game-videos', 'dir-all', 'dir-category', 'user-videos', 'user-clips'];
|
||||||
|
|
||||||
|
|
||||||
export default class Directory extends SiteModule {
|
export default class Directory extends SiteModule {
|
||||||
|
@ -53,7 +53,6 @@ export default class Directory extends SiteModule {
|
||||||
DIR_ROUTES
|
DIR_ROUTES
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
this.settings.add('directory.hidden.style', {
|
this.settings.add('directory.hidden.style', {
|
||||||
default: 2,
|
default: 2,
|
||||||
|
|
||||||
|
@ -133,6 +132,18 @@ export default class Directory extends SiteModule {
|
||||||
changed: value => this.css_tweaks.toggleHide('dir-live-ind', value)
|
changed: value => this.css_tweaks.toggleHide('dir-live-ind', value)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.settings.add('directory.hide-promoted', {
|
||||||
|
default: false,
|
||||||
|
|
||||||
|
ui: {
|
||||||
|
path: 'Directory > Channels >> Appearance',
|
||||||
|
title: 'Do not show Promoted streams in the directory.',
|
||||||
|
component: 'setting-check-box'
|
||||||
|
},
|
||||||
|
|
||||||
|
changed: () => this.updateCards()
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
this.settings.add('directory.hide-vodcasts', {
|
this.settings.add('directory.hide-vodcasts', {
|
||||||
default: false,
|
default: false,
|
||||||
|
@ -324,7 +335,8 @@ export default class Directory extends SiteModule {
|
||||||
el.dataset.ffzType = props.streamType;
|
el.dataset.ffzType = props.streamType;
|
||||||
|
|
||||||
const should_hide = (props.streamType === 'rerun' && this.settings.get('directory.hide-vodcasts')) ||
|
const should_hide = (props.streamType === 'rerun' && this.settings.get('directory.hide-vodcasts')) ||
|
||||||
(props.context != null && props.context !== CARD_CONTEXTS.SingleGameList && this.settings.provider.get('directory.game.blocked-games', []).includes(game));
|
(props.context != null && props.context !== CARD_CONTEXTS.SingleGameList && this.settings.provider.get('directory.game.blocked-games', []).includes(game)) ||
|
||||||
|
(props.sourceType === 'PROMOTION' && this.settings.get('directory.hide-promoted'));
|
||||||
|
|
||||||
let hide_container = el.closest('.tw-tower > div');
|
let hide_container = el.closest('.tw-tower > div');
|
||||||
if ( ! hide_container )
|
if ( ! hide_container )
|
||||||
|
|
|
@ -92,7 +92,7 @@ export default {
|
||||||
methods: {
|
methods: {
|
||||||
clickWithTip(event, fn, ...args) {
|
clickWithTip(event, fn, ...args) {
|
||||||
const el = event.target,
|
const el = event.target,
|
||||||
tip = el && el._ffz_tooltip$0,
|
tip = el && el._ffz_tooltip,
|
||||||
visible = tip && tip.visible;
|
visible = tip && tip.visible;
|
||||||
|
|
||||||
visible && tip.hide();
|
visible && tip.hide();
|
||||||
|
|
|
@ -434,8 +434,8 @@ export default class MenuButton extends SiteModule {
|
||||||
setChildren(toggle, this.renderButtonIcon(profile));
|
setChildren(toggle, this.renderButtonIcon(profile));
|
||||||
toggle.dataset.title = this.renderButtonTip(profile);
|
toggle.dataset.title = this.renderButtonTip(profile);
|
||||||
|
|
||||||
if ( toggle['_ffz_tooltip$0']?.rerender )
|
if ( toggle['_ffz_tooltip']?.rerender )
|
||||||
toggle['_ffz_tooltip$0'].rerender();
|
toggle['_ffz_tooltip'].rerender();
|
||||||
|
|
||||||
this.emit('tooltips:cleanup');
|
this.emit('tooltips:cleanup');
|
||||||
|
|
||||||
|
|
|
@ -502,16 +502,6 @@ export default class Player extends Module {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/*this.settings.add('player.hide-squad-banner', {
|
|
||||||
default: false,
|
|
||||||
ui: {
|
|
||||||
path: 'Channel > Appearance >> General',
|
|
||||||
title: 'Hide the Squad Streaming Bar',
|
|
||||||
component: 'setting-check-box'
|
|
||||||
},
|
|
||||||
changed: () => this.SquadStreamBar.forceUpdate()
|
|
||||||
});*/
|
|
||||||
|
|
||||||
this.settings.add('player.hide-mouse', {
|
this.settings.add('player.hide-mouse', {
|
||||||
default: true,
|
default: true,
|
||||||
ui: {
|
ui: {
|
||||||
|
@ -541,25 +531,6 @@ export default class Player extends Module {
|
||||||
|
|
||||||
const t = this;
|
const t = this;
|
||||||
|
|
||||||
/*this.SquadStreamBar.ready(cls => {
|
|
||||||
const old_should_render = cls.prototype.shouldRenderSquadBanner;
|
|
||||||
|
|
||||||
cls.prototype.shouldRenderSquadBanner = function(...args) {
|
|
||||||
if ( t.settings.get('player.hide-squad-banner') )
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return old_should_render.call(this, ...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.SquadStreamBar.forceUpdate();
|
|
||||||
this.updateSquadContext();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.SquadStreamBar.on('mount', this.updateSquadContext, this);
|
|
||||||
this.SquadStreamBar.on('update', this.updateSquadContext, this);
|
|
||||||
this.SquadStreamBar.on('unmount', this.updateSquadContext, this);*/
|
|
||||||
|
|
||||||
|
|
||||||
this.Player.ready((cls, instances) => {
|
this.Player.ready((cls, instances) => {
|
||||||
const old_attach = cls.prototype.maybeAttachDomEventListeners;
|
const old_attach = cls.prototype.maybeAttachDomEventListeners;
|
||||||
|
|
||||||
|
@ -677,24 +648,8 @@ export default class Player extends Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
cls.prototype.ffzStopAutoplay = function() {
|
cls.prototype.ffzStopAutoplay = function() {
|
||||||
if ( t.settings.get('player.no-autoplay') || (! t.settings.get('player.home.autoplay') && t.router.current.name === 'front-page') ) {
|
if ( t.settings.get('player.no-autoplay') || (! t.settings.get('player.home.autoplay') && t.router.current.name === 'front-page') )
|
||||||
const player = this.props.mediaPlayerInstance,
|
this.stopPlayer(this.props.mediaPlayerInstance, this.props.playerEvents, this);
|
||||||
events = this.props.playerEvents;
|
|
||||||
|
|
||||||
if ( player && player.pause && player.getPlayerState?.() === 'Playing' )
|
|
||||||
player.pause();
|
|
||||||
else if ( events ) {
|
|
||||||
const immediatePause = () => {
|
|
||||||
if ( this.props.mediaPlayerInstance?.pause ) {
|
|
||||||
this.props.mediaPlayerInstance.pause();
|
|
||||||
off(events, 'Playing', immediatePause);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
t.log.info('Unable to immediately pause. Listening for playing event.');
|
|
||||||
on(events, 'Playing', immediatePause);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cls.prototype.ffzScheduleState = function() {
|
cls.prototype.ffzScheduleState = function() {
|
||||||
|
@ -858,6 +813,8 @@ export default class Player extends Module {
|
||||||
this.tryTheatreMode(inst);
|
this.tryTheatreMode(inst);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.PlayerSource.on('mount', this.checkCarousel, this);
|
||||||
|
this.PlayerSource.on('update', this.checkCarousel, this);
|
||||||
|
|
||||||
this.on('i18n:update', () => {
|
this.on('i18n:update', () => {
|
||||||
for(const inst of this.Player.instances) {
|
for(const inst of this.Player.instances) {
|
||||||
|
@ -867,6 +824,46 @@ export default class Player extends Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
stopPlayer(player, events, inst) {
|
||||||
|
if ( player && player.pause && (player.getPlayerState?.() || player.core?.getPlayerState?.()) === 'Playing' )
|
||||||
|
player.pause();
|
||||||
|
else if ( events && ! events._ffz_stopping ) {
|
||||||
|
events._ffz_stopping = true;
|
||||||
|
|
||||||
|
const immediatePause = () => {
|
||||||
|
if ( inst.props.mediaPlayerInstance?.pause ) {
|
||||||
|
inst.props.mediaPlayerInstance.pause();
|
||||||
|
off(events, 'Playing', immediatePause);
|
||||||
|
events._ffz_stopping = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.log.info('Unable to immediately pause. Listening for playing event.');
|
||||||
|
on(events, 'Playing', immediatePause);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
checkCarousel(inst) {
|
||||||
|
if ( this.settings.get('channel.hosting.enable') )
|
||||||
|
return;
|
||||||
|
|
||||||
|
if ( inst.props?.playerType === 'channel_home_carousel' ) {
|
||||||
|
if ( inst.props.content?.hostChannel === inst._ffz_cached_login )
|
||||||
|
return;
|
||||||
|
|
||||||
|
inst._ffz_cached_login = inst.props.content?.hostChannel;
|
||||||
|
if ( ! inst._ffz_cached_login )
|
||||||
|
return;
|
||||||
|
|
||||||
|
const player = inst.props.mediaPlayerInstance,
|
||||||
|
events = inst.props.playerEvents;
|
||||||
|
|
||||||
|
this.stopPlayer(player, events, inst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
updateAutoPlaybackRate(inst, val) {
|
updateAutoPlaybackRate(inst, val) {
|
||||||
const player = inst.props?.mediaPlayerInstance;
|
const player = inst.props?.mediaPlayerInstance;
|
||||||
if ( ! player )
|
if ( ! player )
|
||||||
|
|
151
src/utilities/compat/subpump.js
Normal file
151
src/utilities/compat/subpump.js
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Subpump
|
||||||
|
// It controls Twitch PubSub.
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
import Module from 'utilities/module';
|
||||||
|
import { FFZEvent } from 'utilities/events';
|
||||||
|
|
||||||
|
export class PubSubEvent extends FFZEvent {
|
||||||
|
constructor(data) {
|
||||||
|
super(data);
|
||||||
|
|
||||||
|
this._obj = undefined;
|
||||||
|
this._changed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
markChanged() {
|
||||||
|
this._changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get topic() {
|
||||||
|
return this.event.topic;
|
||||||
|
}
|
||||||
|
|
||||||
|
get message() {
|
||||||
|
if ( this._obj === undefined )
|
||||||
|
this._obj = JSON.parse(this.event.message);
|
||||||
|
|
||||||
|
return this._obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
set message(val) {
|
||||||
|
this._obj = val;
|
||||||
|
this._changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Subpump extends Module {
|
||||||
|
|
||||||
|
constructor(...args) {
|
||||||
|
super(...args);
|
||||||
|
this.instance = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
onEnable(tries = 0) {
|
||||||
|
const instances = window.__Twitch__pubsubInstances;
|
||||||
|
if ( ! instances ) {
|
||||||
|
if ( tries > 10 )
|
||||||
|
this.log.warn('Unable to find PubSub.');
|
||||||
|
else
|
||||||
|
new Promise(r => setTimeout(r, 50)).then(() => this.onEnable(tries + 1));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const [key, val] of Object.entries(instances))
|
||||||
|
if ( val?._client ) {
|
||||||
|
if ( this.instance ) {
|
||||||
|
this.log.warn('Multiple PubSub instances detected. Things might act weird.');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.instance = val;
|
||||||
|
this.hookClient(val._client);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! this.instance )
|
||||||
|
this.log.warn('Unable to find a PubSub instance.');
|
||||||
|
}
|
||||||
|
|
||||||
|
hookClient(client) {
|
||||||
|
const t = this,
|
||||||
|
orig_message = client._onMessage;
|
||||||
|
|
||||||
|
client._unbindPrimary(client._primarySocket);
|
||||||
|
|
||||||
|
client._onMessage = function(e) {
|
||||||
|
try {
|
||||||
|
if ( e.type === 'MESSAGE' && e.data?.topic ) {
|
||||||
|
const raw_topic = e.data.topic,
|
||||||
|
idx = raw_topic.indexOf('.'),
|
||||||
|
prefix = idx === -1 ? raw_topic : raw_topic.slice(0, idx),
|
||||||
|
trail = idx === -1 ? '' : raw_topic.slice(idx + 1);
|
||||||
|
|
||||||
|
const event = new PubSubEvent({
|
||||||
|
prefix,
|
||||||
|
trail,
|
||||||
|
event: e.data
|
||||||
|
});
|
||||||
|
|
||||||
|
t.emit(':pubsub-message', event);
|
||||||
|
if ( event.defaultPrevented )
|
||||||
|
return;
|
||||||
|
|
||||||
|
if ( event._changed )
|
||||||
|
e.data.message = JSON.stringify(event._obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch(err) {
|
||||||
|
this.log.error('Error processing PubSub event.', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
return orig_message.call(this, e);
|
||||||
|
};
|
||||||
|
|
||||||
|
client._bindPrimary(client._primarySocket);
|
||||||
|
|
||||||
|
const listener = client._listens,
|
||||||
|
orig_on = listener.on,
|
||||||
|
orig_off = listener.off;
|
||||||
|
|
||||||
|
listener.on = function(topic, fn, ctx) {
|
||||||
|
const has_topic = !! listener._events?.[topic],
|
||||||
|
out = orig_on.call(this, topic, fn, ctx);
|
||||||
|
|
||||||
|
if ( ! has_topic )
|
||||||
|
t.emit(':add-topic', topic)
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
listener.off = function(topic, fn) {
|
||||||
|
const has_topic = !! listener._events?.[topic],
|
||||||
|
out = orig_off.call(this, topic, fn);
|
||||||
|
|
||||||
|
if ( has_topic && ! listener._events?.[topic] )
|
||||||
|
t.emit(':remove-topic', topic);
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inject(topic, message) {
|
||||||
|
const listens = this.instance?._client?._listens;
|
||||||
|
if ( ! listens )
|
||||||
|
throw new Error('No PubSub instance available');
|
||||||
|
|
||||||
|
listens._trigger(topic, JSON.stringify(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
get topics() {
|
||||||
|
const events = this.instance?._client?._listens._events;
|
||||||
|
if ( ! events )
|
||||||
|
return [];
|
||||||
|
|
||||||
|
return Object.keys(events);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -105,8 +105,8 @@ export class Tooltip {
|
||||||
if ( this.options.manual ) {
|
if ( this.options.manual ) {
|
||||||
// Do nothing~!
|
// Do nothing~!
|
||||||
} else if ( this.live || this.elements.size > 5 ) {
|
} else if ( this.live || this.elements.size > 5 ) {
|
||||||
parent.removeEventListener('mouseover', this._onMouseOver);
|
this.parent.removeEventListener('mouseover', this._onMouseOver);
|
||||||
parent.removeEventListener('mouseout', this._onMouseOut);
|
this.parent.removeEventListener('mouseout', this._onMouseOut);
|
||||||
} else
|
} else
|
||||||
for(const el of this.elements) {
|
for(const el of this.elements) {
|
||||||
el.removeEventListener('mouseenter', this._onMouseOver);
|
el.removeEventListener('mouseenter', this._onMouseOver);
|
||||||
|
@ -119,6 +119,7 @@ export class Tooltip {
|
||||||
this.hide(tip);
|
this.hide(tip);
|
||||||
|
|
||||||
el[this._accessor] = null;
|
el[this._accessor] = null;
|
||||||
|
el._ffz_tooltip = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.elements = null;
|
this.elements = null;
|
||||||
|
@ -205,12 +206,18 @@ export class Tooltip {
|
||||||
target = tip.target;
|
target = tip.target;
|
||||||
|
|
||||||
this.elements.add(target);
|
this.elements.add(target);
|
||||||
|
target._ffz_tooltip = tip;
|
||||||
|
|
||||||
// Set this early in case content uses it early.
|
// Set this early in case content uses it early.
|
||||||
tip._promises = [];
|
tip._promises = [];
|
||||||
tip.waitForDom = () => tip.element ? Promise.resolve() : new Promise(s => {tip._promises.push(s)});
|
tip.waitForDom = () => tip.element ? Promise.resolve() : new Promise(s => {tip._promises.push(s)});
|
||||||
tip.update = () => tip._update(); // tip.popper && tip.popper.scheduleUpdate();
|
tip.update = () => tip._update(); // tip.popper && tip.popper.scheduleUpdate();
|
||||||
tip.show = () => this.show(tip);
|
tip.show = () => {
|
||||||
|
let tip = target[this._accessor];
|
||||||
|
if ( ! tip )
|
||||||
|
tip = target[this._accessor] = {target};
|
||||||
|
this.show(tip);
|
||||||
|
};
|
||||||
tip.hide = () => this.hide(tip);
|
tip.hide = () => this.hide(tip);
|
||||||
tip.rerender = () => {
|
tip.rerender = () => {
|
||||||
if ( tip.visible ) {
|
if ( tip.visible ) {
|
||||||
|
@ -385,6 +392,10 @@ export class Tooltip {
|
||||||
if ( this.live && this.elements )
|
if ( this.live && this.elements )
|
||||||
this.elements.delete(tip.target);
|
this.elements.delete(tip.target);
|
||||||
|
|
||||||
|
if ( tip.target._ffz_tooltip === tip )
|
||||||
|
tip.target._ffz_tooltip = null;
|
||||||
|
|
||||||
|
tip.target[this._accessor] = null;
|
||||||
tip._update = tip.rerender = tip.update = noop;
|
tip._update = tip.rerender = tip.update = noop;
|
||||||
tip.element = null;
|
tip.element = null;
|
||||||
tip.visible = false;
|
tip.visible = false;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue