1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-06-27 21:05:53 +00:00

Continued WIP for async. Yes, this is a garbage giant commit that touches 47 files for over a thousand changed lines. Fight me.

This actually loads, though some add-ons will need some work due to Module changes.

* Modules aren't instantiated immediately when they're registered.
* A Module is only instantiated when it's resolved, with construct = true.
* Modules have `construct_requires` for injecting dependencies within the constructor super call
* All modules have `settings`, `i18n`, and `experiments` as their default construct requires.
* Requirements lists are frozen after the constructor super call.
* Injected dependencies are not injected immediately, even if the module is available. This enforces coding standards.
* All Module data is stored in `__module_data` now, rather than defining an ever growing number of properties on every module.
* `should_enable` is now a static property, because Modules aren't instantiated. To be replaced with module metadata.
* `addons` is now `addon`, with an alias to `addons`. This is done so add-ons can continue loading in the `addon` namespace without complaining.
* HierarchicalEventEmitter forbids `.` within a name, now.
* `abs_path(path, origin)` now accepts an optional origin parameter which overrides the current module's path as the source for normalizing the path
This commit is contained in:
SirStendec 2021-02-28 03:02:24 -05:00
parent 0fd86b1bb8
commit 1bc64586ef
47 changed files with 1085 additions and 985 deletions

View file

@ -20,12 +20,7 @@ export default class AddonManager extends Module {
constructor(...args) { constructor(...args) {
super(...args); super(...args);
this.should_enable = true; this.__module_data.addons = this.__data;
this.inject('settings');
this.inject('i18n');
this.load_requires = ['settings'];
this.has_dev = false; this.has_dev = false;
this.reload_required = false; this.reload_required = false;
@ -257,7 +252,7 @@ export default class AddonManager extends Module {
await this.loadAddon(id); await this.loadAddon(id);
const module = this.resolve(`addon.${id}`); const module = await this.resolve(`addon.${id}`, true);
if ( module && ! module.enabled ) if ( module && ! module.enabled )
await module.enable(); await module.enable();
} }
@ -267,7 +262,7 @@ export default class AddonManager extends Module {
if ( ! addon ) if ( ! addon )
throw new Error(`Unknown add-on id: ${id}`); throw new Error(`Unknown add-on id: ${id}`);
let module = this.resolve(`addon.${id}`); let module = await this.resolve(`addon.${id}`, true);
if ( module ) { if ( module ) {
if ( ! module.loaded ) if ( ! module.loaded )
await module.load(); await module.load();
@ -284,9 +279,9 @@ export default class AddonManager extends Module {
})); }));
// Error if this takes more than 5 seconds. // Error if this takes more than 5 seconds.
await timeout(this.waitFor(`addon.${id}:instanced`), 5000); //await timeout(this.waitFor(`addon.${id}:instanced`), 5000);
module = this.resolve(`addon.${id}`); module = await this.resolve(`addon.${id}`, true);
if ( module && ! module.loaded ) if ( module && ! module.loaded )
await module.load(); await module.load();

View file

@ -21,8 +21,7 @@ class FFZBridge extends Module {
FFZBridge.instance = this; FFZBridge.instance = this;
this.name = 'ffz_bridge'; this.name = 'ffz_bridge';
this.__state = 0; this.__data.state = 0;
this.__modules.core = this;
// ======================================================================== // ========================================================================
// Error Reporting and Logging // Error Reporting and Logging

View file

@ -424,11 +424,12 @@ export class TranslationManager extends Module {
this.emit(':update'); this.emit(':update');
} }
const mod = this.resolve('translation_ui'); this.resolve('translation_ui', true).then(mod => {
if ( popout ) if ( popout )
mod.openPopout(); mod.openPopout();
else else
mod.enable(); return mod.enable();
});
} }

View file

@ -28,8 +28,8 @@ class FrankerFaceZ extends Module {
FrankerFaceZ.instance = this; FrankerFaceZ.instance = this;
this.name = 'frankerfacez'; this.name = 'frankerfacez';
this.__state = 0; this.__data.state = 0;
this.__modules.core = this; //this.__modules.core = this;
// Timing // Timing
//this.inject('timing', Timing); //this.inject('timing', Timing);
@ -59,7 +59,7 @@ class FrankerFaceZ extends Module {
this.inject('socket', SocketClient); this.inject('socket', SocketClient);
//this.inject('pubsub', PubSubClient); //this.inject('pubsub', PubSubClient);
this.inject('site', Site); this.inject('site', Site);
this.inject('addons', AddonManager); this.inject('addon', AddonManager);
this.register('vue', Vue); this.register('vue', Vue);
@ -134,7 +134,7 @@ ${typeof x[1] === 'string' ? x[1] : JSON.stringify(x[1], null, 4)}`).join('\n\n'
async discoverModules() { async discoverModules() {
// TODO: Actually do async modules. // TODO: Actually do async modules.
const ctx = await require.context('src/modules', true, /(?:^(?:\.\/)?[^/]+|index)\.jsx?$/ /*, 'lazy-once' */); const ctx = await require.context('src/modules', true, /^(?:\.\/)?([^/]+)(?:\/index)?\.jsx?$/ /*, 'lazy-once' */);
const modules = this.populate(ctx, this.core_log); const modules = this.populate(ctx, this.core_log);
this.core_log.info(`Loaded descriptions of ${Object.keys(modules).length} modules.`); this.core_log.info(`Loaded descriptions of ${Object.keys(modules).length} modules.`);
@ -143,11 +143,12 @@ ${typeof x[1] === 'string' ? x[1] : JSON.stringify(x[1], null, 4)}`).join('\n\n'
async enableInitialModules() { async enableInitialModules() {
const promises = []; const promises = [];
/* eslint guard-for-in: off */
for(const key in this.__modules) { for(const [key, data] of Object.entries(this.__module_data)) {
const module = this.__modules[key]; if ( data.source?.should_enable )
if ( module instanceof Module && module.should_enable ) promises.push(this.resolve(key, true).then(module => {
promises.push(module.enable()); return module.enable();
}));
} }
await Promise.all(promises); await Promise.all(promises);

View file

@ -28,11 +28,11 @@ const ERROR_IMAGE = 'https://static-cdn.jtvnw.net/emoticons/v1/58765/2.0';
const EMOTE_CHARS = /[ .,!]/; const EMOTE_CHARS = /[ .,!]/;
export default class Chat extends Module { export default class Chat extends Module {
static should_enable = true;
constructor(...args) { constructor(...args) {
super(...args); super(...args);
this.should_enable = true;
this.inject('settings'); this.inject('settings');
this.inject('i18n'); this.inject('i18n');
this.inject('tooltips'); this.inject('tooltips');

View file

@ -85,7 +85,7 @@ export default class Overrides extends Module {
}, },
content: async (t, tip) => { content: async (t, tip) => {
const vue = this.resolve('vue'), const vue = await this.resolve('vue', true),
_editor = import(/* webpackChunkName: "overrides" */ './override-editor.vue'); _editor = import(/* webpackChunkName: "overrides" */ './override-editor.vue');
const [, editor] = await Promise.all([vue.enable(), _editor]); const [, editor] = await Promise.all([vue.enable(), _editor]);

View file

@ -23,6 +23,8 @@
import I18N_MD from '../i18n.md'; import I18N_MD from '../i18n.md';
export default { export default {
props: ['item', 'context'],
data() { data() {
return { return {
md: I18N_MD md: I18N_MD
@ -31,7 +33,7 @@ export default {
methods: { methods: {
open() { open() {
window.FrankerFaceZ.get().resolve('i18n').openUI(); this.context.getFFZ().resolve('i18n').openUI();
} }
} }
} }

View file

@ -23,21 +23,17 @@ function format_term(term) {
// separate the concept of navigation from visible pages. // separate the concept of navigation from visible pages.
export default class MainMenu extends Module { export default class MainMenu extends Module {
constructor(...args) { constructor(...args) {
super(...args); super(...args);
this.inject('settings');
this.inject('i18n');
this.inject('site'); this.inject('site');
this.inject('vue');
this.load_requires = ['vue']; this.inject('vue', true);
this.Mixin = this.SettingMixin = SettingMixin; this.Mixin = this.SettingMixin = SettingMixin;
this.ProviderMixin = ProviderMixin; this.ProviderMixin = ProviderMixin;
//this.should_enable = true;
this.exclusive = false; this.exclusive = false;
this.new_seen = false; this.new_seen = false;

View file

@ -15,14 +15,14 @@ import Module from 'utilities/module';
const CLIP_URL = /^https:\/\/[^/]+\.(?:twitch\.tv|twitchcdn\.net)\/.+?\.mp4(?:\?.*)?$/; const CLIP_URL = /^https:\/\/[^/]+\.(?:twitch\.tv|twitchcdn\.net)\/.+?\.mp4(?:\?.*)?$/;
export default class Metadata extends Module { export default class Metadata extends Module {
static should_enable = true;
constructor(...args) { constructor(...args) {
super(...args); super(...args);
this.inject('settings');
this.inject('i18n');
this.inject('tooltips'); this.inject('tooltips');
this.should_enable = true;
this.definitions = {}; this.definitions = {};
this.settings.add('metadata.clip-download', { this.settings.add('metadata.clip-download', {
@ -154,7 +154,7 @@ export default class Metadata extends Module {
if ( !(created instanceof Date) ) if ( !(created instanceof Date) )
created = new Date(created); created = new Date(created);
const now = Date.now() - socket._time_drift; const now = Date.now() - (socket?._time_drift || 0);
return { return {
created, created,
@ -259,7 +259,7 @@ export default class Metadata extends Module {
return; return;
const Player = this.resolve('site.player'), const Player = this.resolve('site.player'),
player = Player.current; player = Player?.current;
if ( ! player ) if ( ! player )
return; return;
@ -272,7 +272,7 @@ export default class Metadata extends Module {
if ( this.settings.get('metadata.clip-download.force') ) if ( this.settings.get('metadata.clip-download.force') )
return src; return src;
const user = this.resolve('site').getUser?.(), const user = this.resolve('site')?.getUser?.(),
is_self = user?.id == data.channel.id; is_self = user?.id == data.channel.id;
if ( is_self || data.getUserSelfImmediate(data.refresh)?.isEditor ) if ( is_self || data.getUserSelfImmediate(data.refresh)?.isEditor )
@ -305,7 +305,7 @@ export default class Metadata extends Module {
setup() { setup() {
const Player = this.resolve('site.player'), const Player = this.resolve('site.player'),
socket = this.resolve('socket'), socket = this.resolve('socket'),
player = Player.current; player = Player?.current;
let stats; let stats;
@ -408,7 +408,7 @@ export default class Metadata extends Module {
click() { click() {
const Player = this.resolve('site.player'), const Player = this.resolve('site.player'),
ui = Player.playerUI; ui = Player?.playerUI;
if ( ! ui ) if ( ! ui )
return; return;
@ -553,13 +553,15 @@ export default class Metadata extends Module {
} }
updateMetadata(keys) { updateMetadata(keys) {
// TODO: Convert to an event. This is exactly
// what events were made for.
const channel = this.resolve('site.channel'); const channel = this.resolve('site.channel');
if ( channel ) if ( channel?.InfoBar )
for(const el of channel.InfoBar.instances) for(const el of channel.InfoBar.instances)
channel.updateMetadata(el, keys); channel.updateMetadata(el, keys);
const player = this.resolve('site.player'); const player = this.resolve('site.player');
if ( player ) if ( player?.Player )
for(const inst of player.Player.instances) for(const inst of player.Player.instances)
player.updateMetadata(inst, keys); player.updateMetadata(inst, keys);
} }

View file

@ -15,10 +15,6 @@ export default class TooltipProvider extends Module {
super(...args); super(...args);
this.types = {}; this.types = {};
this.inject('i18n');
this.should_enable = true;
this.types.json = target => { this.types.json = target => {
const title = target.dataset.title; const title = target.dataset.title;
return [ return [

View file

@ -230,7 +230,7 @@ import displace from 'displacejs';
import Parser from '@ffz/icu-msgparser'; import Parser from '@ffz/icu-msgparser';
import { saveAs } from 'file-saver'; import { saveAs } from 'file-saver';
import { deep_equals, deep_copy, sleep } from 'utilities/object'; import { deep_equals, deep_copy } from 'utilities/object';
const parser = new Parser(); const parser = new Parser();
const PER_PAGE = 20; const PER_PAGE = 20;

View file

@ -13,12 +13,9 @@ export default class TranslationUI extends Module {
constructor(...args) { constructor(...args) {
super(...args); super(...args);
this.inject('settings');
this.inject('i18n');
this.inject('site'); this.inject('site');
this.inject('vue');
this.load_requires = ['vue']; this.inject('vue', true);
this.dialog = new Dialog(() => this.buildDialog()); this.dialog = new Dialog(() => this.buildDialog());
} }

View file

@ -25,8 +25,7 @@ class FFZPlayer extends Module {
FFZPlayer.instance = this; FFZPlayer.instance = this;
this.name = 'ffz_player'; this.name = 'ffz_player';
this.__state = 0; this.__data.state = 0;
this.__modules.core = this;
// ======================================================================== // ========================================================================
// Error Reporting and Logging // Error Reporting and Logging

View file

@ -16,13 +16,10 @@ const CLASSES = {
export default class CSSTweaks extends Module { export default class CSSTweaks extends Module {
constructor(...args) { constructor(...args) {
super(...args); super(...args);
this.should_enable = true;
this.inject('settings');
this.style = new ManagedStyle; this.style = new ManagedStyle;
this.chunks = {}; this.chunks = {};
this.chunks_loaded = false; this.chunks_loaded = false;

View file

@ -143,7 +143,7 @@ export default class Metadata extends Module {
setup() { setup() {
const Player = this.resolve('site.player'), const Player = this.resolve('site.player'),
player = Player.current; player = Player?.current;
let stats; let stats;
@ -241,7 +241,7 @@ export default class Metadata extends Module {
click() { click() {
const Player = this.resolve('site.player'), const Player = this.resolve('site.player'),
ui = Player.playerUI; ui = Player?.playerUI;
if ( ! ui ) if ( ! ui )
return; return;
@ -400,7 +400,7 @@ export default class Metadata extends Module {
updateMetadata(keys) { updateMetadata(keys) {
const player = this.resolve('site.player'); const player = this.resolve('site.player');
if ( player ) if ( player?.Player )
for(const inst of player.Player.instances) for(const inst of player.Player.instances)
player.updateMetadata(inst, keys); player.updateMetadata(inst, keys);
} }

View file

@ -23,6 +23,7 @@ export default class Player extends PlayerBase {
'player-source', 'player-source',
n => n.setSrc && n.setInitialPlaybackSettings n => n.setSrc && n.setInitialPlaybackSettings
); );
}
wantsMetadata() { wantsMetadata() {
return this.settings.get('player.embed-metadata'); return this.settings.get('player.embed-metadata');

View file

@ -30,10 +30,10 @@ export default class Twilight extends BaseSite {
constructor(...args) { constructor(...args) {
super(...args); super(...args);
this.inject(WebMunch); this.inject(WebMunch, true);
this.inject(Fine); this.inject(Fine);
this.inject(Elemental); this.inject(Elemental);
this.inject('router', FineRouter); this.inject('router', FineRouter, true);
this.inject(Apollo, false); this.inject(Apollo, false);
this.inject(TwitchData); this.inject(TwitchData);
this.inject(Switchboard); this.inject(Switchboard);
@ -43,7 +43,7 @@ export default class Twilight extends BaseSite {
} }
async populateModules() { async populateModules() {
const ctx = await require.context('site/modules', true, /(?:^(?:\.\/)?[^/]+|index)\.jsx?$/); const ctx = await require.context('site/modules', true, /^(?:\.\/)?([^/]+)(?:\/index)?\.jsx?$/);
const modules = await this.populate(ctx, this.log); const modules = await this.populate(ctx, this.log);
this.log.info(`Loaded descriptions of ${Object.keys(modules).length} modules.`); this.log.info(`Loaded descriptions of ${Object.keys(modules).length} modules.`);
} }
@ -64,8 +64,6 @@ export default class Twilight extends BaseSite {
} }
onEnable() { onEnable() {
this.settings = this.resolve('settings');
const thing = this.fine.searchNode(null, n => n?.pendingProps?.store?.getState), const thing = this.fine.searchNode(null, n => n?.pendingProps?.store?.getState),
store = this.store = thing?.pendingProps?.store; store = this.store = thing?.pendingProps?.store;
@ -134,12 +132,13 @@ export default class Twilight extends BaseSite {
const params = new URL(window.location).searchParams; const params = new URL(window.location).searchParams;
if ( params ) { if ( params ) {
if ( params.has('ffz-settings') ) if ( params.has('ffz-settings') )
this.resolve('main_menu').openExclusive(); this.resolve('main_menu', true).then(mod => mod.openExclusive());
if ( params.has('ffz-translate') ) { if ( params.has('ffz-translate') ) {
const translation = this.resolve('translation_ui'); this.resolve('translation_ui', true).then(mod => {
translation.dialog.exclusive = true; mod.dialog.exclusive = true;
translation.enable(); mod.enable();
});
} }
} }
} }

View file

@ -8,12 +8,11 @@ import Module from 'utilities/module';
export default class BitsButton extends Module { export default class BitsButton extends Module {
static should_enable = true;
constructor(...args) { constructor(...args) {
super(...args); super(...args);
this.should_enable = true;
this.inject('settings');
this.inject('site.fine'); this.inject('site.fine');
this.settings.add('layout.display-bits-button', { this.settings.add('layout.display-bits-button', {
@ -35,14 +34,14 @@ export default class BitsButton extends Module {
changed: () => this.BitsButton.forceUpdate() changed: () => this.BitsButton.forceUpdate()
}); });
}
onEnable() {
this.BitsButton = this.fine.define( this.BitsButton = this.fine.define(
'bits-button', 'bits-button',
n => n.toggleBalloon && n.toggleShowTutorial n => n.toggleBalloon && n.toggleShowTutorial
); );
}
onEnable() {
this.BitsButton.ready(cls => { this.BitsButton.ready(cls => {
const t = this, const t = this,
old_render = cls.prototype.render; old_render = cls.prototype.render;

View file

@ -11,11 +11,7 @@ const CHAT_EVENTS = [
]; ];
export default class BTTVCompat extends Module { export default class BTTVCompat extends Module {
constructor(...args) { static should_enable = true;
super(...args);
this.should_enable = true;
}
onEnable() { onEnable() {
this.on('core:dom-update', this.handleDomUpdate, this); this.on('core:dom-update', this.handleDomUpdate, this);

View file

@ -14,13 +14,11 @@ const USER_PAGES = ['user', 'user-home', 'user-about', 'video', 'user-video', 'u
export default class Channel extends Module { export default class Channel extends Module {
static should_enable = true;
constructor(...args) { constructor(...args) {
super(...args); super(...args);
this.should_enable = true;
this.inject('i18n');
this.inject('settings');
this.inject('site.apollo'); this.inject('site.apollo');
this.inject('site.css_tweaks'); this.inject('site.css_tweaks');
this.inject('site.elemental'); this.inject('site.elemental');
@ -31,7 +29,6 @@ export default class Channel extends Module {
this.inject('metadata'); this.inject('metadata');
this.inject('socket'); this.inject('socket');
this.settings.add('channel.panel-tips', { this.settings.add('channel.panel-tips', {
default: false, default: false,
ui: { ui: {
@ -88,15 +85,15 @@ export default class Channel extends Module {
}, },
changed: val => ! val && this.InfoBar.each(el => this.updateBar(el)) changed: val => ! val && this.InfoBar.each(el => this.updateBar(el))
}); });
}
onEnable() {
this.ChannelPanels = this.fine.define( this.ChannelPanels = this.fine.define(
'channel-panels', 'channel-panels',
n => n.layoutMasonry && n.updatePanelOrder && n.onExtensionPoppedOut, n => n.layoutMasonry && n.updatePanelOrder && n.onExtensionPoppedOut,
USER_PAGES USER_PAGES
); );
this.ChannelTrailer = this.elemental.define( this.ChannelTrailer = this.elemental.define(
'channel-trailer', '.channel-trailer-player__wrapper', 'channel-trailer', '.channel-trailer-player__wrapper',
USER_PAGES, USER_PAGES,
@ -127,9 +124,7 @@ export default class Channel extends Module {
this.apollo.registerModifier('UseHosting', strip_host, false); this.apollo.registerModifier('UseHosting', strip_host, false);
this.apollo.registerModifier('PlayerTrackingContextQuery', strip_host, false); this.apollo.registerModifier('PlayerTrackingContextQuery', strip_host, false);
}
onEnable() {
this.updateChannelColor(); this.updateChannelColor();
this.css_tweaks.toggle('panel-links', this.settings.get('channel.panel-tips')); this.css_tweaks.toggle('panel-links', this.settings.get('channel.panel-tips'));

View file

@ -117,8 +117,6 @@ export default class EmoteMenu extends Module {
constructor(...args) { constructor(...args) {
super(...args); super(...args);
this.inject('settings');
this.inject('i18n');
this.inject('chat'); this.inject('chat');
this.inject('chat.badges'); this.inject('chat.badges');
this.inject('chat.emotes'); this.inject('chat.emotes');
@ -249,8 +247,9 @@ export default class EmoteMenu extends Module {
component: 'setting-check-box' component: 'setting-check-box'
} }
}); });
}
async onEnable() {
this.EmoteMenu = this.fine.define( this.EmoteMenu = this.fine.define(
'chat-emote-menu', 'chat-emote-menu',
n => n.subscriptionProductHasEmotes, n => n.subscriptionProductHasEmotes,
@ -261,9 +260,8 @@ export default class EmoteMenu extends Module {
this.MenuWrapper = this.fine.wrap('ffz-emote-menu'); this.MenuWrapper = this.fine.wrap('ffz-emote-menu');
//this.MenuSection = this.fine.wrap('ffz-menu-section'); //this.MenuSection = this.fine.wrap('ffz-menu-section');
//this.MenuEmote = this.fine.wrap('ffz-menu-emote'); //this.MenuEmote = this.fine.wrap('ffz-menu-emote');
}
async onEnable() {
this.on('i18n:update', () => this.EmoteMenu.forceUpdate()); this.on('i18n:update', () => this.EmoteMenu.forceUpdate());
this.on('chat.emotes:update-default-sets', this.maybeUpdate, this); this.on('chat.emotes:update-default-sets', this.maybeUpdate, this);
this.on('chat.emotes:update-user-sets', this.maybeUpdate, this); this.on('chat.emotes:update-user-sets', this.maybeUpdate, this);
@ -1147,15 +1145,16 @@ export default class EmoteMenu extends Module {
win.focus(); win.focus();
} else { } else {
const menu = t.resolve('main_menu'); t.resolve('main_menu', true).then(menu => {
if ( ! menu )
return;
if ( menu ) {
menu.requestPage('chat.emote_menu'); menu.requestPage('chat.emote_menu');
if ( menu.showing ) if ( menu.showing )
return; return;
}
t.emit('site.menu_button:clicked'); t.emit('site.menu_button:clicked');
});
} }
} }

View file

@ -155,18 +155,14 @@ const MISBEHAVING_EVENTS = [
export default class ChatHook extends Module { export default class ChatHook extends Module {
static should_enable = true;
constructor(...args) { constructor(...args) {
super(...args); super(...args);
this.should_enable = true;
this.colors = new ColorAdjuster; this.colors = new ColorAdjuster;
this.inverse_colors = new ColorAdjuster; this.inverse_colors = new ColorAdjuster;
this.inject('settings');
this.inject('i18n');
this.inject('experiments');
this.inject('site'); this.inject('site');
this.inject('site.router'); this.inject('site.router');
this.inject('site.fine'); this.inject('site.fine');
@ -183,91 +179,9 @@ export default class ChatHook extends Module {
this.inject(Input); this.inject(Input);
this.inject(ViewerCards); this.inject(ViewerCards);
this.ChatService = this.fine.define(
'chat-service',
n => n.join && n.connectHandlers,
Twilight.CHAT_ROUTES
);
this.ChatBuffer = this.fine.define(
'chat-buffer',
n => n.updateHandlers && n.delayedMessageBuffer && n.handleMessage,
Twilight.CHAT_ROUTES
);
this.ChatController = this.fine.define(
'chat-controller',
n => n.hostingHandler && n.onRoomStateUpdated,
Twilight.CHAT_ROUTES
);
this.ChatContainer = this.fine.define(
'chat-container',
n => n.closeViewersList && n.onChatInputFocus,
Twilight.CHAT_ROUTES
);
this.ChatBufferConnector = this.fine.define(
'chat-buffer-connector',
n => n.clearBufferHandle && n.syncBufferedMessages,
Twilight.CHAT_ROUTES
);
this.joined_raids = new Set; this.joined_raids = new Set;
this.RaidController = this.fine.define(
'raid-controller',
n => n.handleLeaveRaid && n.handleJoinRaid,
Twilight.CHAT_ROUTES
);
this.InlineCallout = this.fine.define(
'inline-callout',
n => n.showCTA && n.toggleContextMenu && n.actionClick,
Twilight.CHAT_ROUTES
);
this.PinnedCallout = this.fine.define(
'pinned-callout',
n => n.getCalloutTitle && n.buildCalloutProps && n.pin,
Twilight.CHAT_ROUTES
);
this.CalloutSelector = this.fine.define(
'callout-selector',
n => n.selectCalloutComponent && n.props && n.props.callouts,
Twilight.CHAT_ROUTES
);
this.PointsButton = this.fine.define(
'points-button',
n => n.renderIcon && n.renderFlame && n.handleIconAnimationComplete,
Twilight.CHAT_ROUTES
);
this.PointsClaimButton = this.fine.define(
'points-claim-button',
n => n.getClaim && n.onClick && n.props && n.props.claimCommunityPoints,
Twilight.CHAT_ROUTES
);
this.CommunityChestBanner = this.fine.define(
'community-chest-banner',
n => n.getLastGifterText && n.getBannerText && has(n, 'finalCount'),
Twilight.CHAT_ROUTES
);
this.PointsInfo = this.fine.define(
'points-info',
n => n.pointIcon !== undefined && n.pointName !== undefined,
Twilight.CHAT_ROUTES
);
this.GiftBanner = this.fine.define(
'gift-banner',
n => n.getBannerText && n.onGiftMoreClick,
Twilight.CHAT_ROUTES
);
// Settings // Settings
@ -769,6 +683,90 @@ export default class ChatHook extends Module {
onEnable() { onEnable() {
this.ChatService = this.fine.define(
'chat-service',
n => n.join && n.connectHandlers,
Twilight.CHAT_ROUTES
);
this.ChatBuffer = this.fine.define(
'chat-buffer',
n => n.updateHandlers && n.delayedMessageBuffer && n.handleMessage,
Twilight.CHAT_ROUTES
);
this.ChatController = this.fine.define(
'chat-controller',
n => n.hostingHandler && n.onRoomStateUpdated,
Twilight.CHAT_ROUTES
);
this.ChatContainer = this.fine.define(
'chat-container',
n => n.closeViewersList && n.onChatInputFocus,
Twilight.CHAT_ROUTES
);
this.ChatBufferConnector = this.fine.define(
'chat-buffer-connector',
n => n.clearBufferHandle && n.syncBufferedMessages,
Twilight.CHAT_ROUTES
);
this.RaidController = this.fine.define(
'raid-controller',
n => n.handleLeaveRaid && n.handleJoinRaid,
Twilight.CHAT_ROUTES
);
this.InlineCallout = this.fine.define(
'inline-callout',
n => n.showCTA && n.toggleContextMenu && n.actionClick,
Twilight.CHAT_ROUTES
);
this.PinnedCallout = this.fine.define(
'pinned-callout',
n => n.getCalloutTitle && n.buildCalloutProps && n.pin,
Twilight.CHAT_ROUTES
);
this.CalloutSelector = this.fine.define(
'callout-selector',
n => n.selectCalloutComponent && n.props && n.props.callouts,
Twilight.CHAT_ROUTES
);
this.PointsButton = this.fine.define(
'points-button',
n => n.renderIcon && n.renderFlame && n.handleIconAnimationComplete,
Twilight.CHAT_ROUTES
);
this.PointsClaimButton = this.fine.define(
'points-claim-button',
n => n.getClaim && n.onClick && n.props && n.props.claimCommunityPoints,
Twilight.CHAT_ROUTES
);
this.CommunityChestBanner = this.fine.define(
'community-chest-banner',
n => n.getLastGifterText && n.getBannerText && has(n, 'finalCount'),
Twilight.CHAT_ROUTES
);
this.PointsInfo = this.fine.define(
'points-info',
n => n.pointIcon !== undefined && n.pointName !== undefined,
Twilight.CHAT_ROUTES
);
this.GiftBanner = this.fine.define(
'gift-banner',
n => n.getBannerText && n.onGiftMoreClick,
Twilight.CHAT_ROUTES
);
this.on('site.web_munch:loaded', this.grabTypes); this.on('site.web_munch:loaded', this.grabTypes);
this.on('site.web_munch:loaded', this.defineClasses); this.on('site.web_munch:loaded', this.defineClasses);
this.grabTypes(); this.grabTypes();

View file

@ -19,8 +19,6 @@ export default class Input extends Module {
this.inject('chat.actions'); this.inject('chat.actions');
this.inject('chat.emotes'); this.inject('chat.emotes');
this.inject('chat.emoji'); this.inject('chat.emoji');
this.inject('i18n');
this.inject('settings');
this.inject('site.fine'); this.inject('site.fine');
this.inject('site.web_munch'); this.inject('site.web_munch');
@ -83,34 +81,6 @@ export default class Input extends Module {
} }
}); });
// Components
this.ChatInput = this.fine.define(
'chat-input',
n => n && n.setLocalChatInputRef && n.setLocalAutocompleteInputRef,
Twilight.CHAT_ROUTES
);
this.EmoteSuggestions = this.fine.define(
'tab-emote-suggestions',
n => n && n.getMatchedEmotes,
Twilight.CHAT_ROUTES
);
this.MentionSuggestions = this.fine.define(
'tab-mention-suggestions',
n => n && n.getMentions && n.renderMention,
Twilight.CHAT_ROUTES
);
this.CommandSuggestions = this.fine.define(
'tab-cmd-suggestions',
n => n && n.getMatches && n.doesCommandMatchTerm,
Twilight.CHAT_ROUTES
);
// Implement Twitch's unfinished emote usage object for prioritizing sorting // Implement Twitch's unfinished emote usage object for prioritizing sorting
this.EmoteUsageCount = { this.EmoteUsageCount = {
TriHard: 196568036, TriHard: 196568036,
@ -140,6 +110,34 @@ export default class Input extends Module {
} }
async onEnable() { async onEnable() {
// Components
this.ChatInput = this.fine.define(
'chat-input',
n => n && n.setLocalChatInputRef && n.setLocalAutocompleteInputRef,
Twilight.CHAT_ROUTES
);
this.EmoteSuggestions = this.fine.define(
'tab-emote-suggestions',
n => n && n.getMatchedEmotes,
Twilight.CHAT_ROUTES
);
this.MentionSuggestions = this.fine.define(
'tab-mention-suggestions',
n => n && n.getMentions && n.renderMention,
Twilight.CHAT_ROUTES
);
this.CommandSuggestions = this.fine.define(
'tab-cmd-suggestions',
n => n && n.getMatches && n.doesCommandMatchTerm,
Twilight.CHAT_ROUTES
);
this.chat.context.on('changed:chat.actions.room', () => this.ChatInput.forceUpdate()); this.chat.context.on('changed:chat.actions.room', () => this.ChatInput.forceUpdate());
this.chat.context.on('changed:chat.actions.room-above', () => this.ChatInput.forceUpdate()); this.chat.context.on('changed:chat.actions.room-above', () => this.ChatInput.forceUpdate());
this.chat.context.on('changed:chat.tab-complete.emotes-without-colon', enabled => { this.chat.context.on('changed:chat.tab-complete.emotes-without-colon', enabled => {

View file

@ -24,18 +24,17 @@ export default class ChatLine extends Module {
constructor(...args) { constructor(...args) {
super(...args); super(...args);
this.inject('settings');
this.inject('i18n');
this.inject('chat'); this.inject('chat');
this.inject('site'); this.inject('site');
this.inject('site.fine'); this.inject('site.fine');
this.inject('site.web_munch'); this.inject('site.web_munch');
this.inject(RichContent); this.inject(RichContent);
this.inject('experiments');
this.inject('chat.actions'); this.inject('chat.actions');
this.inject('chat.overrides'); this.inject('chat.overrides');
}
async onEnable() {
this.ChatLine = this.fine.define( this.ChatLine = this.fine.define(
'chat-line', 'chat-line',
n => n.renderMessageBody && n.props && ! n.onExtensionNameClick && !has(n.props, 'hasModPermissions'), n => n.renderMessageBody && n.props && ! n.onExtensionNameClick && !has(n.props, 'hasModPermissions'),
@ -52,9 +51,7 @@ export default class ChatLine extends Module {
'whisper-line', 'whisper-line',
n => n.props && n.props.message && has(n.props, 'reportOutgoingWhisperRendered') n => n.props && n.props.message && has(n.props, 'reportOutgoingWhisperRendered')
) )
}
async onEnable() {
this.on('chat.overrides:changed', id => this.updateLinesByUser(id), this); this.on('chat.overrides:changed', id => this.updateLinesByUser(id), this);
this.on('chat:update-lines', this.updateLines, this); this.on('chat:update-lines', this.updateLines, this);
this.on('i18n:update', this.updateLines, this); this.on('i18n:update', this.updateLines, this);

View file

@ -14,7 +14,6 @@ export default class RichContent extends Module {
super(...args); super(...args);
this.inject('chat'); this.inject('chat');
this.inject('i18n');
this.inject('site.web_munch'); this.inject('site.web_munch');
this.RichContent = null; this.RichContent = null;

View file

@ -21,18 +21,10 @@ export default class Scroller extends Module {
constructor(...args) { constructor(...args) {
super(...args); super(...args);
this.inject('settings');
this.inject('i18n');
this.inject('chat'); this.inject('chat');
this.inject('site.fine'); this.inject('site.fine');
this.inject('site.web_munch'); this.inject('site.web_munch');
this.ChatScroller = this.fine.define(
'chat-scroller',
n => n.saveScrollRef && n.handleScrollEvent && ! n.renderLines && n.resume,
Twilight.CHAT_ROUTES
);
this.settings.add('chat.scroller.freeze', { this.settings.add('chat.scroller.freeze', {
default: 0, default: 0,
ui: { ui: {
@ -116,6 +108,12 @@ export default class Scroller extends Module {
} }
async onEnable() { async onEnable() {
this.ChatScroller = this.fine.define(
'chat-scroller',
n => n.saveScrollRef && n.handleScrollEvent && ! n.renderLines && n.resume,
Twilight.CHAT_ROUTES
);
this.on('i18n:update', () => this.ChatScroller.forceUpdate()); this.on('i18n:update', () => this.ChatScroller.forceUpdate());
this.chat.context.on('changed:chat.actions.inline', this.updateUseKeys, this); this.chat.context.on('changed:chat.actions.inline', this.updateUseKeys, this);

View file

@ -12,13 +12,13 @@ export default class SettingsMenu extends Module {
constructor(...args) { constructor(...args) {
super(...args); super(...args);
this.inject('settings');
this.inject('i18n');
this.inject('chat'); this.inject('chat');
this.inject('chat.badges'); this.inject('chat.badges');
this.inject('site.fine'); this.inject('site.fine');
this.inject('site.web_munch'); this.inject('site.web_munch');
}
async onEnable() {
this.SettingsMenu = this.fine.define( this.SettingsMenu = this.fine.define(
'chat-settings', 'chat-settings',
n => n.renderUniversalOptions && n.onBadgesChanged, n => n.renderUniversalOptions && n.onBadgesChanged,
@ -30,9 +30,7 @@ export default class SettingsMenu extends Module {
n => n.hideChatIdentityMenu && n.toggleBalloonRef, n => n.hideChatIdentityMenu && n.toggleBalloonRef,
Twilight.CHAT_ROUTES Twilight.CHAT_ROUTES
);*/ );*/
}
async onEnable() {
this.on('i18n:update', () => this.SettingsMenu.forceUpdate()); this.on('i18n:update', () => this.SettingsMenu.forceUpdate());
this.chat.context.on('changed:chat.scroller.freeze', () => this.SettingsMenu.forceUpdate()); this.chat.context.on('changed:chat.scroller.freeze', () => this.SettingsMenu.forceUpdate());
@ -353,17 +351,19 @@ export default class SettingsMenu extends Module {
} else { } else {
const target = event.currentTarget, const target = event.currentTarget,
page = target && target.dataset && target.dataset.page, page = target && target.dataset && target.dataset.page;
menu = this.resolve('main_menu');
this.resolve('main_menu', true).then(menu => {
if ( ! menu )
return;
if ( menu ) {
if ( page ) if ( page )
menu.requestPage(page); menu.requestPage(page);
if ( menu.showing ) if ( menu.showing )
return; return;
}
this.emit('site.menu_button:clicked'); this.emit('site.menu_button:clicked');
});
} }
this.closeMenu(inst); this.closeMenu(inst);

View file

@ -11,7 +11,6 @@ export default class ViewerCards extends Module {
super(...args); super(...args);
this.inject('chat'); this.inject('chat');
this.inject('settings');
this.inject('site.css_tweaks'); this.inject('site.css_tweaks');
this.inject('site.fine'); this.inject('site.fine');
@ -42,14 +41,15 @@ export default class ViewerCards extends Module {
return ctx.get('chat.viewer-cards.color'); return ctx.get('chat.viewer-cards.color');
} }
}) })
}
onEnable() {
this.ViewerCard = this.fine.define( this.ViewerCard = this.fine.define(
'chat-viewer-card', 'chat-viewer-card',
n => n.trackViewerCardOpen && n.onWhisperButtonClick n => n.trackViewerCardOpen && n.onWhisperButtonClick
); );
}
onEnable() {
this.chat.context.on('changed:chat.viewer-cards.highlight-chat', this.refreshStyle, this); this.chat.context.on('changed:chat.viewer-cards.highlight-chat', this.refreshStyle, this);
this.chat.context.on('changed:chat.viewer-cards.color', this.refreshStyle, this); this.chat.context.on('changed:chat.viewer-cards.color', this.refreshStyle, this);
this.on('..:update-colors', this.refreshStyle, this); this.on('..:update-colors', this.refreshStyle, this);

View file

@ -11,11 +11,12 @@ import Module from 'utilities/module';
import {has, sleep} from 'utilities/object'; import {has, sleep} from 'utilities/object';
export default class CompatEmoteMenu extends Module { export default class CompatEmoteMenu extends Module {
static should_enable = true;
constructor(...args) { constructor(...args) {
super(...args); super(...args);
this.should_enable = true;
this.inject('site.chat'); this.inject('site.chat');
this.inject('chat.emotes'); this.inject('chat.emotes');
} }

View file

@ -47,13 +47,12 @@ const CLASSES = {
export default class CSSTweaks extends Module { export default class CSSTweaks extends Module {
static should_enable = true;
constructor(...args) { constructor(...args) {
super(...args); super(...args);
this.should_enable = true;
this.inject('settings');
this.style = new ManagedStyle; this.style = new ManagedStyle;
this.chunks = {}; this.chunks = {};
this.chunks_loaded = false; this.chunks_loaded = false;

View file

@ -10,15 +10,17 @@ import { get, has } from 'utilities/object';
import Twilight from 'site'; import Twilight from 'site';
export default class Dashboard extends Module { export default class Dashboard extends Module {
static should_enable = true;
constructor(...args) { constructor(...args) {
super(...args); super(...args);
this.should_enable = true;
this.inject('settings');
this.inject('site.fine'); this.inject('site.fine');
this.inject('site.channel'); this.inject('site.channel');
}
onEnable() {
this.SunlightBroadcast = this.fine.define( this.SunlightBroadcast = this.fine.define(
'sunlight-bcast', 'sunlight-bcast',
n => n.getGame && n.getTitle && n.props?.data, n => n.getGame && n.getTitle && n.props?.data,
@ -30,9 +32,7 @@ export default class Dashboard extends Module {
n => n.props?.channelID && n.handleChange && has(n, 'hasVisitedStreamManager'), n => n.props?.channelID && n.handleChange && has(n, 'hasVisitedStreamManager'),
Twilight.SUNLIGHT_ROUTES Twilight.SUNLIGHT_ROUTES
); );
}
onEnable() {
this.SunlightManager.on('mount', this.updateSunlight, this); this.SunlightManager.on('mount', this.updateSunlight, this);
this.SunlightManager.on('update', this.updateSunlight, this); this.SunlightManager.on('update', this.updateSunlight, this);
this.SunlightManager.on('unmount', this.removeSunlight, this); this.SunlightManager.on('unmount', this.removeSunlight, this);

View file

@ -20,9 +20,6 @@ export default class Following extends SiteModule {
this.inject('site.apollo'); this.inject('site.apollo');
this.inject('site.css_tweaks'); this.inject('site.css_tweaks');
this.inject('i18n');
this.inject('settings');
this.settings.add('directory.following.group-hosts', { this.settings.add('directory.following.group-hosts', {
default: true, default: true,

View file

@ -16,15 +16,6 @@ export default class Game extends SiteModule {
this.inject('site.fine'); this.inject('site.fine');
this.inject('site.apollo'); this.inject('site.apollo');
this.inject('i18n');
this.inject('settings');
this.GameHeader = this.fine.define(
'game-header',
n => n.props && n.props.data && n.getBannerImage && n.getDirectoryCountAndTags,
['dir-game-index', 'dir-community', 'dir-game-videos', 'dir-game-clips', 'dir-game-details']
);
this.settings.addUI('directory.game.blocked-games', { this.settings.addUI('directory.game.blocked-games', {
path: 'Directory > Categories @{"description": "Please note that due to limitations in Twitch\'s website, names here must be formatted exactly as displayed in your client. For best results, you can block or unblock categories directly from directory pages."} >> Blocked', path: 'Directory > Categories @{"description": "Please note that due to limitations in Twitch\'s website, names here must be formatted exactly as displayed in your client. For best results, you can block or unblock categories directly from directory pages."} >> Blocked',
component: 'game-list-editor', component: 'game-list-editor',
@ -41,6 +32,12 @@ export default class Game extends SiteModule {
} }
onEnable() { onEnable() {
this.GameHeader = this.fine.define(
'game-header',
n => n.props && n.props.data && n.getBannerImage && n.getDirectoryCountAndTags,
['dir-game-index', 'dir-community', 'dir-game-videos', 'dir-game-clips', 'dir-game-details']
);
this.GameHeader.on('mount', this.updateGameHeader, this); this.GameHeader.on('mount', this.updateGameHeader, this);
this.GameHeader.on('update', this.updateGameHeader, this); this.GameHeader.on('update', this.updateGameHeader, this);
this.GameHeader.on('unmount', () => { this.GameHeader.on('unmount', () => {

View file

@ -25,34 +25,21 @@ const DIR_ROUTES = ['front-page', 'dir', 'dir-community', 'dir-community-index',
export default class Directory extends SiteModule { export default class Directory extends SiteModule {
static should_enable = true;
constructor(...args) { constructor(...args) {
super(...args); super(...args);
this.should_enable = true;
this.inject('site.elemental'); this.inject('site.elemental');
this.inject('site.fine'); this.inject('site.fine');
this.inject('site.router'); this.inject('site.router');
this.inject('site.css_tweaks'); this.inject('site.css_tweaks');
this.inject('site.twitch_data'); this.inject('site.twitch_data');
this.inject('i18n');
this.inject('settings');
//this.inject(Following); //this.inject(Following);
this.inject(Game); this.inject(Game);
this.DirectoryCard = this.elemental.define(
'directory-card', 'article[data-a-target^="followed-vod-"],article[data-a-target^="card-"],div[data-a-target^="video-tower-card-"] article,div[data-a-target^="clips-card-"] article,.shelf-card__impression-wrapper article,.tw-tower div article',
DIR_ROUTES, null, 0, 0
);
this.DirectoryShelf = this.fine.define(
'directory-shelf',
n => n.shouldRenderNode && n.props && n.props.shelf,
DIR_ROUTES
);
this.settings.add('directory.hidden.style', { this.settings.add('directory.hidden.style', {
default: 2, default: 2,
@ -197,6 +184,17 @@ export default class Directory extends SiteModule {
onEnable() { onEnable() {
this.DirectoryCard = this.elemental.define(
'directory-card', 'article[data-a-target^="followed-vod-"],article[data-a-target^="card-"],div[data-a-target^="video-tower-card-"] article,div[data-a-target^="clips-card-"] article,.shelf-card__impression-wrapper article,.tw-tower div article',
DIR_ROUTES, null, 0, 0
);
this.DirectoryShelf = this.fine.define(
'directory-shelf',
n => n.shouldRenderNode && n.props && n.props.shelf,
DIR_ROUTES
);
this.css_tweaks.toggleHide('profile-hover', this.settings.get('directory.show-channel-avatars') === 2); this.css_tweaks.toggleHide('profile-hover', this.settings.get('directory.show-channel-avatars') === 2);
this.css_tweaks.toggleHide('dir-live-ind', this.settings.get('directory.hide-live')); this.css_tweaks.toggleHide('dir-live-ind', this.settings.get('directory.hide-live'));
this.css_tweaks.toggle('dir-reveal', this.settings.get('directory.hidden.reveal')); this.css_tweaks.toggle('dir-reveal', this.settings.get('directory.hidden.reveal'));

View file

@ -15,22 +15,18 @@ import FEATURED_UNFOLLOW from './featured_follow_unfollow.gql';
export default class FeaturedFollow extends Module { export default class FeaturedFollow extends Module {
static should_enable = true;
constructor(...args) { constructor(...args) {
super(...args); super(...args);
this.should_enable = true;
this.inject('site'); this.inject('site');
this.inject('site.fine'); this.inject('site.fine');
this.inject('site.apollo'); this.inject('site.apollo');
this.inject('i18n');
this.inject('metadata'); this.inject('metadata');
this.inject('settings');
this.inject('socket');
this.inject('site.router'); this.inject('site.router');
this.inject('chat');
this.settings.add('metadata.featured-follow', { this.settings.add('metadata.featured-follow', {
default: true, default: true,
@ -47,8 +43,10 @@ export default class FeaturedFollow extends Module {
}); });
this.follow_data = {}; this.follow_data = {};
}
this.socket.on(':command:follow_buttons', data => { onEnable() {
this.on('socket:command:follow_buttons', data => {
for(const channel_login in data) for(const channel_login in data)
if ( has(data, channel_login) ) if ( has(data, channel_login) )
this.follow_data[channel_login] = data[channel_login]; this.follow_data[channel_login] = data[channel_login];
@ -58,16 +56,14 @@ export default class FeaturedFollow extends Module {
this.metadata.updateMetadata('following'); this.metadata.updateMetadata('following');
}); });
}
onEnable() {
this.metadata.definitions.following = { this.metadata.definitions.following = {
order: 150, order: 150,
button: true, button: true,
modview: true, modview: true,
popup: async (data, tip, refresh_fn, add_callback) => { popup: async (data, tip, refresh_fn, add_callback) => {
const vue = this.resolve('vue'), const vue = await this.resolve('vue', true),
_featured_follow_vue = import(/* webpackChunkName: "featured-follow" */ './featured-follow.vue'), _featured_follow_vue = import(/* webpackChunkName: "featured-follow" */ './featured-follow.vue'),
_follows = this.getFollowsForLogin(data.channel.login); _follows = this.getFollowsForLogin(data.channel.login);

View file

@ -20,11 +20,12 @@ const HOST_ERRORS = {
}; };
export default class HostButton extends Module { export default class HostButton extends Module {
static should_enable = true;
constructor(...args) { constructor(...args) {
super(...args); super(...args);
this.should_enable = true;
this.inject('site'); this.inject('site');
this.inject('site.fine'); this.inject('site.fine');
this.inject('site.chat'); this.inject('site.chat');
@ -152,7 +153,7 @@ export default class HostButton extends Module {
}, },
popup: async (data, tip) => { popup: async (data, tip) => {
const vue = this.resolve('vue'), const vue = await this.resolve('vue'),
_host_options_vue = import(/* webpackChunkName: "host-options" */ './host-options.vue'), _host_options_vue = import(/* webpackChunkName: "host-options" */ './host-options.vue'),
_autoHosts = this.fetchAutoHosts(), _autoHosts = this.fetchAutoHosts(),
_autoHostSettings = this.fetchAutoHostSettings(); _autoHostSettings = this.fetchAutoHostSettings();

View file

@ -12,43 +12,16 @@ const PORTRAIT_ROUTES = ['user', 'video', 'user-video', 'user-clip', 'user-video
const MINIMAL_ROUTES = ['popout', 'embed-chat', 'dash-chat']; const MINIMAL_ROUTES = ['popout', 'embed-chat', 'dash-chat'];
export default class Layout extends Module { export default class Layout extends Module {
static should_enable = true;
constructor(...args) { constructor(...args) {
super(...args); super(...args);
this.should_enable = true;
this.inject('settings');
this.inject('site.fine'); this.inject('site.fine');
this.inject('site.css_tweaks'); this.inject('site.css_tweaks');
this.inject('site.elemental'); this.inject('site.elemental');
/*this.TopNav = this.fine.define(
'top-nav',
n => n.computeStyles && n.navigationLinkSize
);*/
/*this.RightColumn = this.fine.define(
'tw-rightcolumn',
n => n.hideOnBreakpoint && n.handleToggleVisibility
);*/
this.ResizeDetector = this.fine.define(
'resize-detector',
n => n.maybeDebounceOnScroll && n.setGrowDivRef && n.props.onResize
);
this.SideBar = this.elemental.define(
'sidebar',
'.side-bar-contents',
null,
{childNodes: true, subtree: true}, 1
);
/*this.SideBarChannels = this.fine.define(
'nav-cards',
t => t.getCardSlideInContent && t.props && has(t.props, 'tooltipContent')
);*/
this.settings.add('layout.portrait', { this.settings.add('layout.portrait', {
default: false, default: false,
ui: { ui: {
@ -213,6 +186,33 @@ export default class Layout extends Module {
} }
onEnable() { onEnable() {
/*this.TopNav = this.fine.define(
'top-nav',
n => n.computeStyles && n.navigationLinkSize
);*/
/*this.RightColumn = this.fine.define(
'tw-rightcolumn',
n => n.hideOnBreakpoint && n.handleToggleVisibility
);*/
this.ResizeDetector = this.fine.define(
'resize-detector',
n => n.maybeDebounceOnScroll && n.setGrowDivRef && n.props.onResize
);
this.SideBar = this.elemental.define(
'sidebar',
'.side-bar-contents',
null,
{childNodes: true, subtree: true}, 1
);
/*this.SideBarChannels = this.fine.define(
'nav-cards',
t => t.getCardSlideInContent && t.props && has(t.props, 'tooltipContent')
);*/
document.body.classList.toggle('ffz--portrait-invert', this.settings.get('layout.portrait-invert')); document.body.classList.toggle('ffz--portrait-invert', this.settings.get('layout.portrait-invert'));
this.on(':update-nav', this.updateNavLinks, this); this.on(':update-nav', this.updateNavLinks, this);

View file

@ -12,16 +12,16 @@ import Twilight from 'site';
export default class MenuButton extends SiteModule { export default class MenuButton extends SiteModule {
static should_enable = true;
constructor(...args) { constructor(...args) {
super(...args); super(...args);
this.inject('i18n');
this.inject('settings');
this.inject('site.fine'); this.inject('site.fine');
this.inject('site.elemental'); this.inject('site.elemental');
//this.inject('addons'); //this.inject('addons');
this.should_enable = true;
this._pill_content = null; this._pill_content = null;
this._has_update = false; this._has_update = false;
this._important_update = false; this._important_update = false;
@ -38,41 +38,6 @@ export default class MenuButton extends SiteModule {
}, },
changed: () => this.update() changed: () => this.update()
}); });
this.ModBar = this.fine.define(
'mod-view-bar',
n => n.actions && n.updateRoot && n.childContext,
['mod-view']
);
/*this.SunlightDash = this.fine.define(
'sunlight-dash',
n => n.getIsChannelEditor && n.getIsChannelModerator && n.getIsAdsEnabled && n.getIsSquadStreamsEnabled,
Twilight.SUNLIGHT_ROUTES
);*/
this.SunlightNav = this.elemental.define(
'sunlight-nav', '.sunlight-top-nav > .tw-flex > .tw-flex > .tw-justify-content-end > .tw-flex',
Twilight.SUNLIGHT_ROUTES,
{attributes: true}, 1
);
this.NavBar = this.fine.define(
'nav-bar',
n => n.renderOnsiteNotifications && n.renderTwitchPrimeCrown
);
this.SquadBar = this.fine.define(
'squad-nav-bar',
n => n.exitSquadMode && n.props && n.props.squadID,
['squad']
);
this.MultiController = this.fine.define(
'multi-controller',
n => n.handleAddStream && n.handleRemoveStream && n.getInitialStreamLayout,
['command-center']
);
} }
get loading() { get loading() {
@ -176,12 +141,12 @@ export default class MenuButton extends SiteModule {
const addons = this.resolve('addons'); const addons = this.resolve('addons');
if ( DEBUG && ! addons?.has_dev )
return this.i18n.t('site.menu_button.main-dev', 'm-dev');
if ( DEBUG && addons.has_dev ) if ( DEBUG && addons.has_dev )
return this.i18n.t('site.menu_button.dev', 'dev'); return this.i18n.t('site.menu_button.dev', 'dev');
if ( DEBUG && ! addons.has_dev )
return this.i18n.t('site.menu_button.main-dev', 'm-dev');
if ( ! DEBUG && addons.has_dev ) if ( ! DEBUG && addons.has_dev )
return this.i18n.t('site.menu_button.addon-dev', 'a-dev'); return this.i18n.t('site.menu_button.addon-dev', 'a-dev');
@ -214,6 +179,41 @@ export default class MenuButton extends SiteModule {
onEnable() { onEnable() {
this.ModBar = this.fine.define(
'mod-view-bar',
n => n.actions && n.updateRoot && n.childContext,
['mod-view']
);
/*this.SunlightDash = this.fine.define(
'sunlight-dash',
n => n.getIsChannelEditor && n.getIsChannelModerator && n.getIsAdsEnabled && n.getIsSquadStreamsEnabled,
Twilight.SUNLIGHT_ROUTES
);*/
this.SunlightNav = this.elemental.define(
'sunlight-nav', '.sunlight-top-nav > .tw-flex > .tw-flex > .tw-justify-content-end > .tw-flex',
Twilight.SUNLIGHT_ROUTES,
{attributes: true}, 1
);
this.NavBar = this.fine.define(
'nav-bar',
n => n.renderOnsiteNotifications && n.renderTwitchPrimeCrown
);
this.SquadBar = this.fine.define(
'squad-nav-bar',
n => n.exitSquadMode && n.props && n.props.squadID,
['squad']
);
this.MultiController = this.fine.define(
'multi-controller',
n => n.handleAddStream && n.handleRemoveStream && n.getInitialStreamLayout,
['command-center']
);
this.NavBar.ready(() => this.update()); this.NavBar.ready(() => this.update());
this.NavBar.on('mount', this.updateButton, this); this.NavBar.on('mount', this.updateButton, this);
this.NavBar.on('update', this.updateButton, this); this.NavBar.on('update', this.updateButton, this);
@ -577,22 +577,20 @@ export default class MenuButton extends SiteModule {
} }
loadMenu(event, btn, page) { async loadMenu(event, btn, page) {
const menu = this.resolve('main_menu');
if ( ! menu )
return;
if ( page )
menu.requestPage(page);
if ( menu.showing )
return;
this.loading = true; this.loading = true;
menu.enable(event).then(() => { try {
this.loading = false; const menu = await this.resolve('main_menu', true);
if ( menu ) {
if ( page )
menu.requestPage(page);
}).catch(err => { if ( ! menu.showing )
await menu.enable(event);
}
} catch(err) {
this.log.capture(err); this.log.capture(err);
this.log.error('Error enabling main menu.', err); this.log.error('Error enabling main menu.', err);
@ -601,9 +599,10 @@ export default class MenuButton extends SiteModule {
text: 'There was an error loading the FFZ Control Center. Please refresh and try again.' text: 'There was an error loading the FFZ Control Center. Please refresh and try again.'
}; };
this.loading = false;
this.once(':clicked', this.loadMenu); this.once(':clicked', this.loadMenu);
}); }
this.loading = false;
} }
onDisable() { onDisable() {

View file

@ -9,11 +9,12 @@ import {debounce} from 'utilities/object';
export default class ModView extends Module { export default class ModView extends Module {
static should_enable = true;
constructor(...args) { constructor(...args) {
super(...args); super(...args);
this.inject('i18n');
this.inject('settings');
this.inject('site.channel'); this.inject('site.channel');
this.inject('site.css_tweaks'); this.inject('site.css_tweaks');
this.inject('site.fine'); this.inject('site.fine');
@ -23,11 +24,14 @@ export default class ModView extends Module {
this.inject('metadata'); this.inject('metadata');
this.inject('socket'); this.inject('socket');
this.should_enable = true;
this._cached_channel = null; this._cached_channel = null;
this._cached_id = null; this._cached_id = null;
this.checkRoot = debounce(this.checkRoot, 250);
this.checkBar = debounce(this.checkBar, 250);
}
onEnable() {
this.Root = this.elemental.define( this.Root = this.elemental.define(
'mod-view-root', '.moderation-view-page', 'mod-view-root', '.moderation-view-page',
['mod-view'], ['mod-view'],
@ -40,11 +44,6 @@ export default class ModView extends Module {
{childNodes: true, subtree: true}, 1, 30000, false {childNodes: true, subtree: true}, 1, 30000, false
); );
this.checkRoot = debounce(this.checkRoot, 250);
this.checkBar = debounce(this.checkBar, 250);
}
onEnable() {
this.Root.on('mount', this.updateRoot, this); this.Root.on('mount', this.updateRoot, this);
this.Root.on('mutate', this.updateRoot, this); this.Root.on('mutate', this.updateRoot, this);
this.Root.on('unmount', this.removeRoot, this); this.Root.on('unmount', this.removeRoot, this);

View file

@ -19,45 +19,15 @@ export const PLAYER_ROUTES = [
})();*/ })();*/
export default class Player extends PlayerBase { export default class Player extends PlayerBase {
static should_enable = true;
constructor(...args) { constructor(...args) {
super(...args); super(...args);
this.should_enable = true;
// Dependency Injection // Dependency Injection
this.inject('site.router'); this.inject('site.router');
this.inject('metadata'); this.inject('metadata');
// React Components
/*this.SquadStreamBar = this.fine.define(
'squad-stream-bar',
n => n.shouldRenderSquadBanner && n.props && n.props.triggerPlayerReposition,
PLAYER_ROUTES
);*/
/*this.PersistentPlayer = this.fine.define(
'persistent-player',
n => n.state && n.state.playerStyles
);*/
this.Player = this.fine.define(
'highwind-player',
n => n.setPlayerActive && n.props?.playerEvents && n.props?.mediaPlayerInstance,
PLAYER_ROUTES
);
this.TheatreHost = this.fine.define(
'theatre-host',
n => n.toggleTheatreMode && n.props && n.props.onTheatreModeEnabled,
['user', 'user-home', 'video', 'user-video', 'user-clip']
);
this.PlayerSource = this.fine.define(
'player-source',
n => n.setSrc && n.setInitialPlaybackSettings,
PLAYER_ROUTES
);
} }
@ -157,6 +127,37 @@ export default class Player extends PlayerBase {
async onEnable() { async onEnable() {
// React Components
/*this.SquadStreamBar = this.fine.define(
'squad-stream-bar',
n => n.shouldRenderSquadBanner && n.props && n.props.triggerPlayerReposition,
PLAYER_ROUTES
);*/
/*this.PersistentPlayer = this.fine.define(
'persistent-player',
n => n.state && n.state.playerStyles
);*/
this.Player = this.fine.define(
'highwind-player',
n => n.setPlayerActive && n.props?.playerEvents && n.props?.mediaPlayerInstance,
PLAYER_ROUTES
);
this.TheatreHost = this.fine.define(
'theatre-host',
n => n.toggleTheatreMode && n.props && n.props.onTheatreModeEnabled,
['user', 'user-home', 'video', 'user-video', 'user-clip']
);
this.PlayerSource = this.fine.define(
'player-source',
n => n.setSrc && n.setInitialPlaybackSettings,
PLAYER_ROUTES
);
await super.onEnable(); await super.onEnable();
this.css_tweaks.toggle('theatre-no-whispers', this.settings.get('player.theatre.no-whispers')); this.css_tweaks.toggle('theatre-no-whispers', this.settings.get('player.theatre.no-whispers'));

View file

@ -8,13 +8,12 @@ import Module from 'utilities/module';
import {createElement} from 'utilities/dom'; import {createElement} from 'utilities/dom';
export default class SubButton extends Module { export default class SubButton extends Module {
static should_enable = true;
constructor(...args) { constructor(...args) {
super(...args); super(...args);
this.should_enable = true;
this.inject('i18n');
this.inject('settings');
this.inject('site.fine'); this.inject('site.fine');
this.settings.add('sub-button.prime-notice', { this.settings.add('sub-button.prime-notice', {
@ -29,15 +28,15 @@ export default class SubButton extends Module {
changed: () => this.SubButton.forceUpdate() changed: () => this.SubButton.forceUpdate()
}); });
}
onEnable() {
this.SubButton = this.fine.define( this.SubButton = this.fine.define(
'sub-button', 'sub-button',
n => n.handleSubMenuAction && n.isUserDataReady, n => n.handleSubMenuAction && n.isUserDataReady,
['user', 'user-home', 'user-video', 'user-clip', 'video', 'user-videos', 'user-clips', 'user-collections', 'user-events', 'user-followers', 'user-following'] ['user', 'user-home', 'user-video', 'user-clip', 'video', 'user-videos', 'user-clips', 'user-collections', 'user-events', 'user-followers', 'user-following']
); );
}
onEnable() {
this.settings.on(':changed:layout.swap-sidebars', () => this.SubButton.forceUpdate()) this.settings.on(':changed:layout.swap-sidebars', () => this.SubButton.forceUpdate())
this.SubButton.ready((cls, instances) => { this.SubButton.ready((cls, instances) => {

View file

@ -33,17 +33,17 @@ const ACCENT_COLORS = {
export default class ThemeEngine extends Module { export default class ThemeEngine extends Module {
static should_enable = true;
constructor(...args) { constructor(...args) {
super(...args); super(...args);
this.inject('settings');
this.inject('site'); this.inject('site');
this.inject('site.fine'); this.inject('site.fine');
this.inject('site.css_tweaks'); this.inject('site.css_tweaks');
this.inject('site.router'); this.inject('site.router');
this.should_enable = true;
// Font // Font
this.settings.add('theme.font.size', { this.settings.add('theme.font.size', {

View file

@ -13,14 +13,12 @@ import Module from 'utilities/module';
export default class VideoChatHook extends Module { export default class VideoChatHook extends Module {
static should_enable = true;
constructor(...args) { constructor(...args) {
super(...args); super(...args);
this.should_enable = true;
this.inject('i18n');
this.inject('settings');
this.inject('site'); this.inject('site');
this.inject('site.router'); this.inject('site.router');
this.inject('site.fine'); this.inject('site.fine');
@ -30,24 +28,6 @@ export default class VideoChatHook extends Module {
this.inject('site.chat', undefined, false, 'site_chat'); this.inject('site.chat', undefined, false, 'site_chat');
this.inject('site.chat.chat_line.rich_content'); this.inject('site.chat.chat_line.rich_content');
this.VideoChatController = this.fine.define(
'video-chat-controller',
n => n.onMessageScrollAreaMount && n.createReply,
['user-video', 'user-clip', 'video']
);
this.VideoChatMenu = this.fine.define(
'video-chat-menu',
n => n.onToggleMenu && n.getContent && n.props && has(n.props, 'isExpandedLayout'),
['user-video', 'user-clip', 'video']
);
this.VideoChatLine = this.fine.define(
'video-chat-line',
n => n.onReplyClickHandler && n.shouldFocusMessage,
['user-video', 'user-clip', 'video']
);
// Settings // Settings
this.settings.add('chat.video-chat.timestamps', { this.settings.add('chat.video-chat.timestamps', {
@ -72,6 +52,24 @@ export default class VideoChatHook extends Module {
async onEnable() { async onEnable() {
this.VideoChatController = this.fine.define(
'video-chat-controller',
n => n.onMessageScrollAreaMount && n.createReply,
['user-video', 'user-clip', 'video']
);
this.VideoChatMenu = this.fine.define(
'video-chat-menu',
n => n.onToggleMenu && n.getContent && n.props && has(n.props, 'isExpandedLayout'),
['user-video', 'user-clip', 'video']
);
this.VideoChatLine = this.fine.define(
'video-chat-line',
n => n.onReplyClickHandler && n.shouldFocusMessage,
['user-video', 'user-clip', 'video']
);
this.chat.context.on('changed:chat.video-chat.enabled', this.updateLines, this); this.chat.context.on('changed:chat.video-chat.enabled', this.updateLines, this);
this.chat.context.on('changed:chat.video-chat.timestamps', this.updateLines, this); this.chat.context.on('changed:chat.video-chat.timestamps', this.updateLines, this);
this.on('chat:updated-lines', this.updateLines, this); this.on('chat:updated-lines', this.updateLines, this);

View file

@ -1,13 +1,6 @@
import Module from 'utilities/module'; import Module from 'utilities/module';
export class Addon extends Module { export class Addon extends Module {
constructor(...args) {
super(...args);
this.inject('i18n');
this.inject('settings');
}
static register(id, info) { static register(id, info) {
if ( typeof id === 'object' ) { if ( typeof id === 'object' ) {
info = id; info = id;
@ -30,11 +23,14 @@ export class Addon extends Module {
ffz.addons.addAddon(info); ffz.addons.addAddon(info);
} }
const key = `addon.${id}`;
try { try {
ffz.register(`addon.${id}`, this); ffz.register(key, this);
} catch(err) { } catch(err) {
if ( err.message && err.message.includes('Name Collision for Module') ) { if ( err.message && err.message.includes('Name Collision for Module') ) {
const module = ffz.resolve(`addon.${id}`); const module = ffz.resolve(key);
if ( module ) if ( module )
module.external = true; module.external = true;
} }

View file

@ -522,7 +522,8 @@ export default class Fine extends Module {
_stopWaiting() { _stopWaiting() {
this.log.info('Stopping MutationObserver.'); if ( this._waiting_timer )
this.log.info('Stopping MutationObserver.');
if ( this._observer ) if ( this._observer )
this._observer.disconnect(); this._observer.disconnect();

View file

@ -21,6 +21,10 @@ String.prototype.toSnakeCase = function() {
.toLowerCase(); .toLowerCase();
} }
export function nameFromPath(path) {
const idx = path.lastIndexOf('.');
return idx === -1 ? path : path.slice(idx + 1);
}
export class EventEmitter { export class EventEmitter {
constructor() { constructor() {
@ -370,6 +374,9 @@ export class HierarchicalEventEmitter extends EventEmitter {
super(); super();
this.name = name || (this.constructor.name || '').toSnakeCase(); this.name = name || (this.constructor.name || '').toSnakeCase();
if ( this.name.includes('.') )
throw new Error('name cannot include path separator (.)');
this.parent = parent; this.parent = parent;
if ( parent ) { if ( parent ) {
@ -399,13 +406,21 @@ export class HierarchicalEventEmitter extends EventEmitter {
// Public Methods // Public Methods
// ======================================================================== // ========================================================================
abs_path(path) { abs_path(path, origin) {
if ( typeof path !== 'string' || ! path.length ) if ( typeof path !== 'string' || ! path.length )
throw new TypeError('path must be a non-empty string'); throw new TypeError('path must be a non-empty string');
let parts;
if ( origin ) {
if ( Array.isArray(origin) )
parts = origin;
else
parts = origin.split('.');
} else
parts = this.__path_parts;
const depth = parts.length;
let i = 0, chr; let i = 0, chr;
const parts = this.__path_parts,
depth = parts.length;
do { do {
chr = path.charAt(i); chr = path.charAt(i);
@ -419,8 +434,19 @@ export class HierarchicalEventEmitter extends EventEmitter {
} while ( ++i < path.length ); } while ( ++i < path.length );
const event = chr === ':'; const event = chr === ':';
if ( i === 0 ) if ( i === 0 ) {
return event && this.__path ? `${this.__path}${path}` : path; if ( event ) {
if ( origin ) {
if ( Array.isArray(origin) )
return `${origin.join('.')}${path}`;
return `${origin}${path}`;
} else if ( this.__path )
return `${this.__path}${path}`;
}
return path;
}
const prefix = parts.slice(0, depth - (i-1)).join('.'), const prefix = parts.slice(0, depth - (i-1)).join('.'),
remain = path.slice(i); remain = path.slice(i);

File diff suppressed because it is too large Load diff