1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-09-21 12:16:57 +00:00
FrankerFaceZ/src/sites/twitch-twilight/modules/loadable.tsx
2025-06-10 17:12:47 +02:00

207 lines
4.8 KiB
TypeScript

'use strict';
import type SettingsManager from 'root/src/settings';
import type { FineWrapper } from 'root/src/utilities/compat/fine';
import type Fine from 'root/src/utilities/compat/fine';
import type { ReactStateNode } from 'root/src/utilities/compat/react-types';
// ============================================================================
// Loadable Stuff
// ============================================================================
import Module, { GenericModule } from 'utilities/module';
import type { AnyFunction } from 'utilities/types';
import type Twilight from '..';
declare module 'utilities/types' {
interface ModuleEventMap {
}
interface ModuleMap {
'site.loadable': Loadable
}
interface SettingsTypeMap {
'chat.hype.show-pinned': boolean;
'layout.turbo-cta': boolean;
'layout.combos': boolean;
}
}
type LoadableNode = ReactStateNode<{
component: string;
loader: any;
}, {
Component?: AnyFunction;
}>;
type ErrorBoundaryNode = ReactStateNode<{
name: string;
onError: any;
children: any;
}> & {
onErrorBoundaryTestEmit: any
}
export default class Loadable extends Module {
// Dependencies
settings: SettingsManager = null as any;
site: Twilight = null as any;
fine: Fine = null as any;
// State
overrides: Map<string, boolean>;
// Fine
ErrorBoundaryComponent: FineWrapper<ErrorBoundaryNode>;
LoadableComponent: FineWrapper<LoadableNode>;
constructor(name?: string, parent?: GenericModule) {
super(name, parent);
this.should_enable = true;
this.inject('settings');
this.inject('site.fine');
this.inject('site');
this.LoadableComponent = this.fine.define(
'loadable-component',
n =>
(n as LoadableNode).props?.component &&
(n as LoadableNode).props.loader
);
this.ErrorBoundaryComponent = this.fine.define(
'error-boundary-component',
n =>
(n as ErrorBoundaryNode).props?.name &&
(n as ErrorBoundaryNode).props?.onError &&
(n as ErrorBoundaryNode).props?.children &&
(n as ErrorBoundaryNode).onErrorBoundaryTestEmit
);
this.overrides = new Map();
}
onEnable() {
this.settings.getChanges('chat.hype.show-pinned', val => {
this.toggle('PaidPinnedChatMessageList', val);
});
this.settings.getChanges('layout.turbo-cta', val => {
this.toggle('TopNav__TurboButton_Available', val);
});
this.settings.getChanges('layout.combos', val => {
this.toggle('CombosIngressButton_Available', !val);
});
this.ErrorBoundaryComponent.ready((cls, instances) => {
this.log.debug('Found Error Boundary component wrapper.');
const t = this,
proto = cls.prototype as ErrorBoundaryNode,
old_render = proto.render;
(proto as any)._ffz_wrapped_render = old_render;
proto.render = function() {
try {
const type = this.props.name;
if ( t.overrides.has(type) && ! t.shouldRender(type) )
return null;
} catch(err) {
/* no-op */
console.error(err);
}
return old_render.call(this);
}
this.ErrorBoundaryComponent.updateInstances();
});
this.LoadableComponent.ready((cls, instances) => {
this.log.debug('Found Loadable component wrapper.');
const t = this,
proto = cls.prototype,
old_render = proto.render;
proto.render = function() {
try {
const type = this.props.component;
if ( t.overrides.has(type) && this.state ) {
let cmp = this.state.Component;
if ( typeof cmp === 'function' && ! (cmp as any).ffzWrapped ) {
const React = t.site.getReact(),
createElement = React && React.createElement;
if ( createElement ) {
if ( ! (cmp as any).ffzWrapper ) {
const th = this;
function FFZWrapper(props: any) {
if ( t.shouldRender(th.props.component) )
return createElement(cmp, props);
return null;
}
FFZWrapper.ffzWrapped = true;
FFZWrapper.displayName = `FFZWrapper(${this.props.component})`;
(cmp as any).ffzWrapper = FFZWrapper;
}
this.state.Component = (cmp as any).ffzWrapper;
}
}
}
} catch(err) {
/* no-op */
console.error(err);
}
return old_render.call(this);
}
this.LoadableComponent.updateInstances();
});
}
toggle(cmp: string, state: boolean | null = null) {
const existing = this.overrides.get(cmp) ?? true;
if ( state == null )
state = ! existing;
else
state = !! state;
if ( state !== existing ) {
this.overrides.set(cmp, state);
this.update(cmp);
}
}
update(cmp: string) {
for(const inst of this.LoadableComponent.instances) {
const type = inst.props?.component;
if ( type && type === cmp ) {
inst.forceUpdate();
}
}
for(const inst of this.ErrorBoundaryComponent.instances) {
const name = inst.props?.name;
if ( name && name === cmp ) {
inst.forceUpdate();
}
}
}
shouldRender(cmp: string) {
return this.overrides.get(cmp) ?? true;
}
}