1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-06-27 12:55:55 +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) {
super(...args);
this.should_enable = true;
this.inject('settings');
this.inject('i18n');
this.load_requires = ['settings'];
this.__module_data.addons = this.__data;
this.has_dev = false;
this.reload_required = false;
@ -257,7 +252,7 @@ export default class AddonManager extends Module {
await this.loadAddon(id);
const module = this.resolve(`addon.${id}`);
const module = await this.resolve(`addon.${id}`, true);
if ( module && ! module.enabled )
await module.enable();
}
@ -267,7 +262,7 @@ export default class AddonManager extends Module {
if ( ! addon )
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.loaded )
await module.load();
@ -284,9 +279,9 @@ export default class AddonManager extends Module {
}));
// 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 )
await module.load();

View file

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

View file

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

View file

@ -28,8 +28,8 @@ class FrankerFaceZ extends Module {
FrankerFaceZ.instance = this;
this.name = 'frankerfacez';
this.__state = 0;
this.__modules.core = this;
this.__data.state = 0;
//this.__modules.core = this;
// Timing
//this.inject('timing', Timing);
@ -59,7 +59,7 @@ class FrankerFaceZ extends Module {
this.inject('socket', SocketClient);
//this.inject('pubsub', PubSubClient);
this.inject('site', Site);
this.inject('addons', AddonManager);
this.inject('addon', AddonManager);
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() {
// 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);
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() {
const promises = [];
/* eslint guard-for-in: off */
for(const key in this.__modules) {
const module = this.__modules[key];
if ( module instanceof Module && module.should_enable )
promises.push(module.enable());
for(const [key, data] of Object.entries(this.__module_data)) {
if ( data.source?.should_enable )
promises.push(this.resolve(key, true).then(module => {
return module.enable();
}));
}
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 = /[ .,!]/;
export default class Chat extends Module {
static should_enable = true;
constructor(...args) {
super(...args);
this.should_enable = true;
this.inject('settings');
this.inject('i18n');
this.inject('tooltips');

View file

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

View file

@ -23,6 +23,8 @@
import I18N_MD from '../i18n.md';
export default {
props: ['item', 'context'],
data() {
return {
md: I18N_MD
@ -31,7 +33,7 @@ export default {
methods: {
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.
export default class MainMenu extends Module {
constructor(...args) {
super(...args);
this.inject('settings');
this.inject('i18n');
this.inject('site');
this.inject('vue');
this.load_requires = ['vue'];
this.inject('vue', true);
this.Mixin = this.SettingMixin = SettingMixin;
this.ProviderMixin = ProviderMixin;
//this.should_enable = true;
this.exclusive = 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(?:\?.*)?$/;
export default class Metadata extends Module {
static should_enable = true;
constructor(...args) {
super(...args);
this.inject('settings');
this.inject('i18n');
this.inject('tooltips');
this.should_enable = true;
this.definitions = {};
this.settings.add('metadata.clip-download', {
@ -154,7 +154,7 @@ export default class Metadata extends Module {
if ( !(created instanceof Date) )
created = new Date(created);
const now = Date.now() - socket._time_drift;
const now = Date.now() - (socket?._time_drift || 0);
return {
created,
@ -259,7 +259,7 @@ export default class Metadata extends Module {
return;
const Player = this.resolve('site.player'),
player = Player.current;
player = Player?.current;
if ( ! player )
return;
@ -272,7 +272,7 @@ export default class Metadata extends Module {
if ( this.settings.get('metadata.clip-download.force') )
return src;
const user = this.resolve('site').getUser?.(),
const user = this.resolve('site')?.getUser?.(),
is_self = user?.id == data.channel.id;
if ( is_self || data.getUserSelfImmediate(data.refresh)?.isEditor )
@ -305,7 +305,7 @@ export default class Metadata extends Module {
setup() {
const Player = this.resolve('site.player'),
socket = this.resolve('socket'),
player = Player.current;
player = Player?.current;
let stats;
@ -408,7 +408,7 @@ export default class Metadata extends Module {
click() {
const Player = this.resolve('site.player'),
ui = Player.playerUI;
ui = Player?.playerUI;
if ( ! ui )
return;
@ -553,13 +553,15 @@ export default class Metadata extends Module {
}
updateMetadata(keys) {
// TODO: Convert to an event. This is exactly
// what events were made for.
const channel = this.resolve('site.channel');
if ( channel )
if ( channel?.InfoBar )
for(const el of channel.InfoBar.instances)
channel.updateMetadata(el, keys);
const player = this.resolve('site.player');
if ( player )
if ( player?.Player )
for(const inst of player.Player.instances)
player.updateMetadata(inst, keys);
}

View file

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

View file

@ -230,7 +230,7 @@ import displace from 'displacejs';
import Parser from '@ffz/icu-msgparser';
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 PER_PAGE = 20;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -11,11 +11,7 @@ const CHAT_EVENTS = [
];
export default class BTTVCompat extends Module {
constructor(...args) {
super(...args);
this.should_enable = true;
}
static should_enable = true;
onEnable() {
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 {
static should_enable = true;
constructor(...args) {
super(...args);
this.should_enable = true;
this.inject('i18n');
this.inject('settings');
this.inject('site.apollo');
this.inject('site.css_tweaks');
this.inject('site.elemental');
@ -31,7 +29,6 @@ export default class Channel extends Module {
this.inject('metadata');
this.inject('socket');
this.settings.add('channel.panel-tips', {
default: false,
ui: {
@ -88,15 +85,15 @@ export default class Channel extends Module {
},
changed: val => ! val && this.InfoBar.each(el => this.updateBar(el))
});
}
onEnable() {
this.ChannelPanels = this.fine.define(
'channel-panels',
n => n.layoutMasonry && n.updatePanelOrder && n.onExtensionPoppedOut,
USER_PAGES
);
this.ChannelTrailer = this.elemental.define(
'channel-trailer', '.channel-trailer-player__wrapper',
USER_PAGES,
@ -127,9 +124,7 @@ export default class Channel extends Module {
this.apollo.registerModifier('UseHosting', strip_host, false);
this.apollo.registerModifier('PlayerTrackingContextQuery', strip_host, false);
}
onEnable() {
this.updateChannelColor();
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) {
super(...args);
this.inject('settings');
this.inject('i18n');
this.inject('chat');
this.inject('chat.badges');
this.inject('chat.emotes');
@ -249,8 +247,9 @@ export default class EmoteMenu extends Module {
component: 'setting-check-box'
}
});
}
async onEnable() {
this.EmoteMenu = this.fine.define(
'chat-emote-menu',
n => n.subscriptionProductHasEmotes,
@ -261,9 +260,8 @@ export default class EmoteMenu extends Module {
this.MenuWrapper = this.fine.wrap('ffz-emote-menu');
//this.MenuSection = this.fine.wrap('ffz-menu-section');
//this.MenuEmote = this.fine.wrap('ffz-menu-emote');
}
async onEnable() {
this.on('i18n:update', () => this.EmoteMenu.forceUpdate());
this.on('chat.emotes:update-default-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();
} else {
const menu = t.resolve('main_menu');
t.resolve('main_menu', true).then(menu => {
if ( ! menu )
return;
if ( menu ) {
menu.requestPage('chat.emote_menu');
if ( menu.showing )
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 {
static should_enable = true;
constructor(...args) {
super(...args);
this.should_enable = true;
this.colors = new ColorAdjuster;
this.inverse_colors = new ColorAdjuster;
this.inject('settings');
this.inject('i18n');
this.inject('experiments');
this.inject('site');
this.inject('site.router');
this.inject('site.fine');
@ -183,91 +179,9 @@ export default class ChatHook extends Module {
this.inject(Input);
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.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
@ -769,6 +683,90 @@ export default class ChatHook extends Module {
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.defineClasses);
this.grabTypes();

View file

@ -19,8 +19,6 @@ export default class Input extends Module {
this.inject('chat.actions');
this.inject('chat.emotes');
this.inject('chat.emoji');
this.inject('i18n');
this.inject('settings');
this.inject('site.fine');
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
this.EmoteUsageCount = {
TriHard: 196568036,
@ -140,6 +110,34 @@ export default class Input extends Module {
}
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-above', () => this.ChatInput.forceUpdate());
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) {
super(...args);
this.inject('settings');
this.inject('i18n');
this.inject('chat');
this.inject('site');
this.inject('site.fine');
this.inject('site.web_munch');
this.inject(RichContent);
this.inject('experiments');
this.inject('chat.actions');
this.inject('chat.overrides');
}
async onEnable() {
this.ChatLine = this.fine.define(
'chat-line',
n => n.renderMessageBody && n.props && ! n.onExtensionNameClick && !has(n.props, 'hasModPermissions'),
@ -52,9 +51,7 @@ export default class ChatLine extends Module {
'whisper-line',
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:update-lines', this.updateLines, this);
this.on('i18n:update', this.updateLines, this);

View file

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

View file

@ -21,18 +21,10 @@ export default class Scroller extends Module {
constructor(...args) {
super(...args);
this.inject('settings');
this.inject('i18n');
this.inject('chat');
this.inject('site.fine');
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', {
default: 0,
ui: {
@ -116,6 +108,12 @@ export default class Scroller extends Module {
}
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.chat.context.on('changed:chat.actions.inline', this.updateUseKeys, this);

View file

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

View file

@ -11,7 +11,6 @@ export default class ViewerCards extends Module {
super(...args);
this.inject('chat');
this.inject('settings');
this.inject('site.css_tweaks');
this.inject('site.fine');
@ -42,14 +41,15 @@ export default class ViewerCards extends Module {
return ctx.get('chat.viewer-cards.color');
}
})
}
onEnable() {
this.ViewerCard = this.fine.define(
'chat-viewer-card',
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.color', 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';
export default class CompatEmoteMenu extends Module {
static should_enable = true;
constructor(...args) {
super(...args);
this.should_enable = true;
this.inject('site.chat');
this.inject('chat.emotes');
}

View file

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

View file

@ -10,15 +10,17 @@ import { get, has } from 'utilities/object';
import Twilight from 'site';
export default class Dashboard extends Module {
static should_enable = true;
constructor(...args) {
super(...args);
this.should_enable = true;
this.inject('settings');
this.inject('site.fine');
this.inject('site.channel');
}
onEnable() {
this.SunlightBroadcast = this.fine.define(
'sunlight-bcast',
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'),
Twilight.SUNLIGHT_ROUTES
);
}
onEnable() {
this.SunlightManager.on('mount', this.updateSunlight, this);
this.SunlightManager.on('update', this.updateSunlight, 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.css_tweaks');
this.inject('i18n');
this.inject('settings');
this.settings.add('directory.following.group-hosts', {
default: true,

View file

@ -16,15 +16,6 @@ export default class Game extends SiteModule {
this.inject('site.fine');
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', {
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',
@ -41,6 +32,12 @@ export default class Game extends SiteModule {
}
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('update', this.updateGameHeader, this);
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 {
static should_enable = true;
constructor(...args) {
super(...args);
this.should_enable = true;
this.inject('site.elemental');
this.inject('site.fine');
this.inject('site.router');
this.inject('site.css_tweaks');
this.inject('site.twitch_data');
this.inject('i18n');
this.inject('settings');
//this.inject(Following);
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', {
default: 2,
@ -197,6 +184,17 @@ export default class Directory extends SiteModule {
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('dir-live-ind', this.settings.get('directory.hide-live'));
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 {
static should_enable = true;
constructor(...args) {
super(...args);
this.should_enable = true;
this.inject('site');
this.inject('site.fine');
this.inject('site.apollo');
this.inject('i18n');
this.inject('metadata');
this.inject('settings');
this.inject('socket');
this.inject('site.router');
this.inject('chat');
this.settings.add('metadata.featured-follow', {
default: true,
@ -47,8 +43,10 @@ export default class FeaturedFollow extends Module {
});
this.follow_data = {};
}
this.socket.on(':command:follow_buttons', data => {
onEnable() {
this.on('socket:command:follow_buttons', data => {
for(const channel_login in data)
if ( has(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');
});
}
onEnable() {
this.metadata.definitions.following = {
order: 150,
button: true,
modview: true,
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'),
_follows = this.getFollowsForLogin(data.channel.login);

View file

@ -20,11 +20,12 @@ const HOST_ERRORS = {
};
export default class HostButton extends Module {
static should_enable = true;
constructor(...args) {
super(...args);
this.should_enable = true;
this.inject('site');
this.inject('site.fine');
this.inject('site.chat');
@ -152,7 +153,7 @@ export default class HostButton extends Module {
},
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'),
_autoHosts = this.fetchAutoHosts(),
_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'];
export default class Layout extends Module {
static should_enable = true;
constructor(...args) {
super(...args);
this.should_enable = true;
this.inject('settings');
this.inject('site.fine');
this.inject('site.css_tweaks');
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', {
default: false,
ui: {
@ -213,6 +186,33 @@ export default class Layout extends Module {
}
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'));
this.on(':update-nav', this.updateNavLinks, this);

View file

@ -12,16 +12,16 @@ import Twilight from 'site';
export default class MenuButton extends SiteModule {
static should_enable = true;
constructor(...args) {
super(...args);
this.inject('i18n');
this.inject('settings');
this.inject('site.fine');
this.inject('site.elemental');
//this.inject('addons');
this.should_enable = true;
this._pill_content = null;
this._has_update = false;
this._important_update = false;
@ -38,41 +38,6 @@ export default class MenuButton extends SiteModule {
},
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() {
@ -176,12 +141,12 @@ export default class MenuButton extends SiteModule {
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 )
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 )
return this.i18n.t('site.menu_button.addon-dev', 'a-dev');
@ -214,6 +179,41 @@ export default class MenuButton extends SiteModule {
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.on('mount', this.updateButton, this);
this.NavBar.on('update', this.updateButton, this);
@ -577,22 +577,20 @@ export default class MenuButton extends SiteModule {
}
loadMenu(event, btn, page) {
const menu = this.resolve('main_menu');
if ( ! menu )
return;
if ( page )
menu.requestPage(page);
if ( menu.showing )
return;
async loadMenu(event, btn, page) {
this.loading = true;
menu.enable(event).then(() => {
this.loading = false;
try {
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.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.'
};
this.loading = false;
this.once(':clicked', this.loadMenu);
});
}
this.loading = false;
}
onDisable() {

View file

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

View file

@ -19,45 +19,15 @@ export const PLAYER_ROUTES = [
})();*/
export default class Player extends PlayerBase {
static should_enable = true;
constructor(...args) {
super(...args);
this.should_enable = true;
// Dependency Injection
this.inject('site.router');
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() {
// 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();
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';
export default class SubButton extends Module {
static should_enable = true;
constructor(...args) {
super(...args);
this.should_enable = true;
this.inject('i18n');
this.inject('settings');
this.inject('site.fine');
this.settings.add('sub-button.prime-notice', {
@ -29,15 +28,15 @@ export default class SubButton extends Module {
changed: () => this.SubButton.forceUpdate()
});
}
onEnable() {
this.SubButton = this.fine.define(
'sub-button',
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']
);
}
onEnable() {
this.settings.on(':changed:layout.swap-sidebars', () => this.SubButton.forceUpdate())
this.SubButton.ready((cls, instances) => {

View file

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

View file

@ -13,14 +13,12 @@ import Module from 'utilities/module';
export default class VideoChatHook extends Module {
static should_enable = true;
constructor(...args) {
super(...args);
this.should_enable = true;
this.inject('i18n');
this.inject('settings');
this.inject('site');
this.inject('site.router');
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.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
this.settings.add('chat.video-chat.timestamps', {
@ -72,6 +52,24 @@ export default class VideoChatHook extends Module {
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.timestamps', this.updateLines, this);
this.on('chat:updated-lines', this.updateLines, this);

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff