2025-04-22 02:37:36 +02:00
import ExtensionMode from '../../../common/enums/ExtensionMode.enum' ;
2024-06-05 01:08:50 +02:00
import { EventBusConnector } from '../EventBus' ;
2020-12-03 01:35:22 +01:00
if ( process . env . CHANNEL !== 'stable' ) {
console . info ( "Loading: UI" ) ;
}
2024-06-05 01:08:50 +02:00
const csuiVersions = {
'normal' : 'csui' , // csui-overlay-normal.html, maps to csui.html
2025-01-25 21:08:09 +01:00
'light' : 'csui-light' , // csui-overlay-light.html, maps to csui-light.html
'dark' : 'csui-dark' // csui-overlay-dark.html, maps to csui-dark.html
2024-06-05 01:08:50 +02:00
} ;
2025-01-05 00:36:41 +01:00
const MAX _IFRAME _ERROR _COUNT = 5 ;
2020-12-03 00:34:50 +01:00
class UI {
constructor (
interfaceId ,
2024-12-17 02:02:49 +01:00
uiConfig , // {parentElement?, eventBus?, isGlobal?, playerData}
2020-12-03 00:34:50 +01:00
) {
this . interfaceId = interfaceId ;
2020-12-03 01:35:22 +01:00
this . uiConfig = uiConfig ;
2022-03-21 00:50:54 +01:00
this . lastProbeResponseTs = null ;
2024-06-02 16:06:26 +02:00
this . isGlobal = uiConfig . isGlobal ? ? false ;
2025-06-18 01:15:45 +02:00
this . isIframe = window . self !== window . top ;
2020-12-03 01:35:22 +01:00
2022-03-22 01:23:15 +01:00
this . eventBus = uiConfig . eventBus ;
2024-06-05 01:08:50 +02:00
this . disablePointerEvents = false ;
this . saveState = undefined ;
2024-12-17 02:02:49 +01:00
this . playerData = uiConfig . playerData ;
this . uiSettings = uiConfig . uiSettings ;
2025-04-22 02:37:36 +02:00
this . siteSettings = uiConfig . siteSettings ;
2024-12-17 02:02:49 +01:00
2025-01-05 00:36:41 +01:00
this . iframeErrorCount = 0 ;
this . iframeConfirmed = false ;
2025-01-06 03:05:18 +01:00
this . iframeRejected = false ;
2025-01-25 20:51:22 +01:00
2025-03-30 23:48:24 +02:00
this . delayedDestroyTimer = null ;
2025-01-25 20:51:22 +01:00
// TODO: at some point, UI should be different for global popup and in-player UI
this . csuiScheme = this . getCsuiScheme ( ) ;
const csuiVersion = this . getCsuiVersion ( this . csuiScheme . contentScheme ) ;
this . uiURI = chrome . runtime . getURL ( ` /csui/ ${ csuiVersion } .html ` ) ;
this . extensionBase = chrome . runtime . getURL ( '' ) . replace ( /\/$/ , "" ) ;
2025-03-30 23:48:24 +02:00
// UI will be initialized when setUiVisibility is called
2025-04-22 02:37:36 +02:00
console . log ( 'ui config:' , uiConfig ) ;
2025-03-30 23:48:24 +02:00
this . init ( ) ;
2020-12-03 00:34:50 +01:00
}
2025-04-22 02:37:36 +02:00
canRun ( ) {
if ( this . isGlobal ) {
return true ;
}
return this . siteSettings ? . data . enableUI . fullscreen === ExtensionMode . Enabled
|| this . siteSettings ? . data . enableUI . theater === ExtensionMode . Enabled
|| this . siteSettings ? . data . enableUI . normal === ExtensionMode . Enabled ;
}
2020-12-03 00:34:50 +01:00
async init ( ) {
2025-04-22 02:37:36 +02:00
if ( ! this . canRun ( ) ) {
console . log ( 'ui config: canRun returned false' , this . siteSettings ? . data . enableUI . fullscreen === ExtensionMode . Enabled , this . siteSettings ? . data . enableUI . theater === ExtensionMode . Enabled , this . siteSettings ? . data . enableUI . normal === ExtensionMode . Enabled )
return ;
}
console . log ( 'ui config: canRun returned truie' , this . siteSettings ? . data . enableUI . fullscreen === ExtensionMode . Enabled , this . siteSettings ? . data . enableUI . theater === ExtensionMode . Enabled , this . siteSettings ? . data . enableUI . normal === ExtensionMode . Enabled )
2025-03-30 23:48:24 +02:00
this . initUIContainer ( ) ;
this . loadIframe ( ) ;
2024-06-05 01:08:50 +02:00
this . initMessaging ( ) ;
}
2025-01-25 20:51:22 +01:00
/ * *
* Returns color scheme we need to use .
*
* contentScheme is used to select the correct HTML template .
* iframeScheme gets applied to the iframe as style
* @ returns { contentScheme : string , iframeScheme : string }
* /
getCsuiScheme ( ) {
return {
contentScheme : window . getComputedStyle ( document . body , null ) . getPropertyValue ( 'color-scheme' ) ,
iframeScheme : document . documentElement . style . colorScheme || document . body . style . colorScheme || undefined
} ;
}
/ * *
* Returns correct template for given preferredScheme parameter
* @ param { * } preferredScheme
* @ returns
* /
getCsuiVersion ( preferredScheme ) {
return csuiVersions [ preferredScheme ] ? ? csuiVersions . normal ;
}
2025-03-30 23:48:24 +02:00
initUIContainer ( ) {
2020-12-03 00:34:50 +01:00
const random = Math . round ( Math . random ( ) * 69420 ) ;
2022-01-07 00:50:58 +01:00
const uwid = ` uw-ultrawidify- ${ this . interfaceId } -root- ${ random } `
2020-12-03 00:34:50 +01:00
const rootDiv = document . createElement ( 'div' ) ;
2020-12-15 00:26:19 +01:00
if ( this . uiConfig . additionalStyle ) {
rootDiv . setAttribute ( 'style' , this . uiConfig . additionalStyle ) ;
}
2020-12-05 03:30:43 +01:00
rootDiv . setAttribute ( 'id' , uwid ) ;
2020-12-12 00:38:51 +01:00
rootDiv . classList . add ( 'uw-ultrawidify-container-root' ) ;
2025-03-30 23:48:24 +02:00
rootDiv . style . width = "100%" ;
rootDiv . style . height = "100%" ;
2024-06-05 01:08:50 +02:00
rootDiv . style . position = this . isGlobal ? "fixed" : "absolute" ;
rootDiv . style . zIndex = this . isGlobal ? '90009' : '90000' ;
2022-06-14 00:26:59 +02:00
rootDiv . style . border = 0 ;
rootDiv . style . top = 0 ;
2022-06-15 00:53:43 +02:00
rootDiv . style . pointerEvents = 'none' ;
2020-12-04 02:02:25 +01:00
if ( this . uiConfig ? . parentElement ) {
2020-12-03 00:34:50 +01:00
this . uiConfig . parentElement . appendChild ( rootDiv ) ;
} else {
document . body . appendChild ( rootDiv ) ;
}
2025-03-30 23:48:24 +02:00
this . rootDiv = rootDiv ;
}
2020-12-03 00:34:50 +01:00
2025-03-30 23:48:24 +02:00
loadIframe ( ) {
2022-03-21 00:50:54 +01:00
// in onMouseMove, we currently can't access this because we didn't
// do things the most properly
const uiURI = this . uiURI ;
const iframe = document . createElement ( 'iframe' ) ;
iframe . setAttribute ( 'src' , uiURI ) ;
2024-06-05 01:08:50 +02:00
iframe . setAttribute ( "allowTransparency" , 'true' ) ;
// iframe.style.width = "100%";
// iframe.style.height = "100%";
2022-03-21 00:50:54 +01:00
iframe . style . position = "absolute" ;
2024-06-05 01:08:50 +02:00
iframe . style . zIndex = this . isGlobal ? '90009' : '90000' ;
2022-03-21 00:50:54 +01:00
iframe . style . border = 0 ;
iframe . style . pointerEvents = 'none' ;
2025-01-12 23:24:01 +01:00
iframe . style . opacity = 0 ;
2024-06-05 01:08:50 +02:00
iframe . style . backgroundColor = 'transparent !important' ;
2022-03-21 00:50:54 +01:00
2025-01-25 20:51:22 +01:00
// If colorScheme is defined via CSS on the HTML or BODY elements, then we need to also
// put a matching style to the iframe itself. Using the correct UI template is not enough.
if ( this . csuiScheme . iframeScheme ) {
iframe . style . colorScheme = this . csuiScheme . iframeScheme ;
}
2022-03-22 01:23:15 +01:00
/ * s o w e h a v e a p r o b l e m : w e w a n t i f r a m e t o b e c l i c k t h r o u g h e v e r y w h e r e e x c e p t
* on our actual overlay . There ' s no nice way of doing that , so we need some
* extra javascript to deal with this .
*
* There ' s a second problem : while iframe is in clickable mode , onMouseMove
* will not work ( as all events will be hijacked by the iframe ) . This means
* that iframe also needs to run its own instance of onMouseMove .
* /
2022-03-21 00:50:54 +01:00
2023-07-11 00:48:34 +02:00
// set uiIframe for handleMessage
this . uiIframe = iframe ;
2024-06-05 01:08:50 +02:00
// set not visible by default
2025-03-30 23:48:24 +02:00
// this.setUiVisibility(false);
2024-06-05 01:08:50 +02:00
2023-07-11 00:48:34 +02:00
const fn = ( event ) => {
// remove self on fucky wuckies
if ( ! iframe ? . contentWindow ) {
document . removeEventListener ( 'mousemove' , fn , true ) ;
return ;
}
2024-12-26 17:41:00 +01:00
const rect = this . uiIframe . getBoundingClientRect ( ) ;
2024-12-27 23:12:41 +01:00
2024-12-26 17:41:00 +01:00
const offsets = {
top : window . scrollY + rect . top ,
left : window . scrollX + rect . left
} ;
2023-07-11 00:48:34 +02:00
const coords = {
2024-12-26 17:41:00 +01:00
x : event . pageX - offsets . left ,
y : event . pageY - offsets . top ,
frameOffset : offsets ,
2023-07-11 00:48:34 +02:00
} ;
2024-12-17 02:02:49 +01:00
const playerData = this . canShowUI ( coords ) ;
2023-07-11 00:48:34 +02:00
// ask the iframe to check whether there's a clickable element
this . uiIframe . contentWindow . postMessage (
{
action : 'uwui-probe' ,
coords ,
2024-12-17 02:02:49 +01:00
playerDimensions : playerData . playerDimensions ,
canShowUI : playerData . canShowUI ,
ts : + new Date ( ) // this should be accurate enough for our purposes,
2023-07-11 00:48:34 +02:00
} ,
uiURI
) ;
}
2024-06-05 01:08:50 +02:00
// NOTE: you cannot communicate with UI iframe inside onload function.
// onload triggers after iframe is initialized, but BEFORE vue finishes
// setting up all the components.
// If we need to have any data inside the vue component, we need to
// request that data from vue components.
2022-03-22 01:23:15 +01:00
iframe . onload = function ( ) {
2023-07-11 00:48:34 +02:00
document . addEventListener ( 'mousemove' , fn , true ) ;
2020-12-04 02:02:25 +01:00
}
2021-10-26 23:13:11 +02:00
2025-05-19 01:16:33 +02:00
this . eventBus . forwardToIframe (
this . uiIframe ,
( action , payload ) => {
this . sendToIframe ( action , payload , { } )
}
) ;
2025-03-30 23:48:24 +02:00
this . rootDiv . appendChild ( iframe ) ;
}
unloadIframe ( ) {
2025-05-19 01:16:33 +02:00
this . eventBus . cancelIframeForwarding ( this . uiIframe ) ;
2025-03-30 23:48:24 +02:00
window . removeEventListener ( 'message' , this . messageHandlerFn ) ;
this . uiIframe ? . remove ( ) ;
delete this . uiIframe ;
2024-06-05 01:08:50 +02:00
}
2024-12-17 02:02:49 +01:00
2025-03-30 23:48:24 +02:00
2024-06-05 01:08:50 +02:00
initMessaging ( ) {
2023-07-11 00:48:34 +02:00
// subscribe to events coming back to us. Unsubscribe if iframe vanishes.
2024-12-30 23:02:55 +01:00
window . addEventListener ( 'message' , this . messageHandlerFn ) ;
2022-03-21 00:50:54 +01:00
2022-03-29 01:53:16 +02:00
/ * s e t u p e v e n t b u s t u n n e l f r o m c o n t e n t s c r i p t t o U I — n e c e s s a r y i f w e w a n t t o r e c e i v e
* like current zoom levels & current aspect ratio & stuff . Some of these things are
* necessary for UI display in the popup .
* /
2022-09-27 01:48:08 +02:00
2024-12-30 23:02:55 +01:00
this . eventBus . subscribeMulti (
2024-06-02 16:06:26 +02:00
{
2024-12-30 23:02:55 +01:00
'uw-config-broadcast' : {
function : ( config , routingData ) => {
this . sendToIframe ( 'uw-config-broadcast' , config , routingData ) ;
}
} ,
'uw-set-ui-state' : {
function : ( config , routingData ) => {
2025-06-18 01:15:45 +02:00
if ( config . globalUiVisible !== undefined && ! this . isIframe ) {
2024-12-30 23:02:55 +01:00
if ( this . isGlobal ) {
this . setUiVisibility ( config . globalUiVisible ) ;
} else {
this . setUiVisibility ( ! config . globalUiVisible ) ;
}
2024-06-05 01:08:50 +02:00
}
2024-12-30 23:02:55 +01:00
this . sendToIframe ( 'uw-set-ui-state' , { ... config , isGlobal : this . isGlobal } , routingData ) ;
2024-06-05 01:08:50 +02:00
}
2024-12-30 23:02:55 +01:00
} ,
2025-01-13 01:22:49 +01:00
'uw-get-page-stats' : {
function : ( config , routingData ) => {
console . log ( 'got get page stats!' ) ;
this . eventBus . send (
'uw-page-stats' ,
{
pcsDark : window . matchMedia ( '(prefers-color-scheme: dark)' ) . matches ,
pcsLight : window . matchMedia ( '(prefers-color-scheme: light)' ) . matches ,
colorScheme : window . getComputedStyle ( document . body , null ) . getPropertyValue ( 'color-scheme' )
} ,
{
comms : {
forwardTo : 'popup'
}
}
) ;
}
} ,
2024-12-30 23:02:55 +01:00
'uw-restore-ui-state' : {
function : ( config , routingData ) => {
if ( ! this . isGlobal ) {
this . setUiVisibility ( true ) ;
this . sendToIframe ( 'uw-restore-ui-state' , config , routingData ) ;
}
2024-06-05 01:08:50 +02:00
}
2024-06-02 16:06:26 +02:00
}
2024-12-30 23:02:55 +01:00
} ,
this
) ;
}
messageHandlerFn = ( message ) => {
if ( ! this . uiIframe ? . contentWindow ) {
window . removeEventListener ( 'message' , this . messageHandlerFn ) ;
return ;
}
this . handleMessage ( message ) ;
2022-03-20 20:40:11 +01:00
}
2022-03-21 00:50:54 +01:00
2024-06-05 01:08:50 +02:00
setUiVisibility ( visible ) {
2025-03-30 23:48:24 +02:00
// console.log('uwui - setting ui visibility!', visible, this.isGlobal ? 'global' : 'page', this.uiIframe, this.rootDiv);
// if (!this.uiIframe || !this.rootDiv) {
// this.init();
// }
2024-06-05 01:08:50 +02:00
if ( visible ) {
2025-03-30 23:48:24 +02:00
this . rootDiv . style . width = '100%' ;
this . rootDiv . style . height = '100%' ;
2024-06-05 01:08:50 +02:00
this . uiIframe . style . width = '100%' ;
this . uiIframe . style . height = '100%' ;
2025-03-30 23:48:24 +02:00
// if (this.delayedDestroyTimer) {
// clearTimeout(this.delayedDestroyTimer);
// }
2024-06-05 01:08:50 +02:00
} else {
2025-03-30 23:48:24 +02:00
this . rootDiv . style . width = '0px' ;
this . rootDiv . style . height = '0px' ;
2024-06-05 01:08:50 +02:00
this . uiIframe . style . width = '0px' ;
this . uiIframe . style . height = '0px' ;
2025-03-30 23:48:24 +02:00
// destroy after 30 seconds of UI being hidden
// this.delayedDestroyTimer = setTimeout( () => this.unloadIframe(), 30000);
2024-06-05 01:08:50 +02:00
}
}
2023-07-11 00:48:34 +02:00
async enable ( ) {
// if root element is not present, we need to init the UI.
2025-03-30 23:48:24 +02:00
if ( ! this . rootDiv ) {
2023-07-11 00:48:34 +02:00
await this . init ( ) ;
}
// otherwise, we don't have to do anything
}
disable ( ) {
2025-03-30 23:48:24 +02:00
if ( this . rootDiv ) {
2023-07-11 00:48:34 +02:00
this . destroy ( ) ;
}
}
2022-03-22 01:23:15 +01:00
2024-12-17 02:02:49 +01:00
/ * *
* Checks whether mouse is moving over either :
* * < video > element
* * player element ( )
* * uwui - clickable element
* /
2024-12-27 23:12:41 +01:00
canShowUI ( ) {
2024-12-17 02:02:49 +01:00
const playerCssClass = 'uw-ultrawidify-player-css' ;
const result = {
playerDimensions : undefined ,
2024-12-27 23:12:41 +01:00
canShowUI : false ,
2024-12-17 02:02:49 +01:00
}
2025-04-22 02:37:36 +02:00
if ( this . playerData ? . environment && this . siteSettings . data . enableUI [ this . playerData ? . environment ] !== ExtensionMode . Enabled ) {
return result ;
}
2024-12-17 02:02:49 +01:00
if ( this . playerData ? . dimensions ) {
result . playerDimensions = this . playerData . dimensions ;
}
// if player is not wide enough, we do nothing
if (
! this . isGlobal && // this.isGlobal is basically 'yes, do as I say'
! document . fullscreenElement && // if we are in full screen, we allow it in every case as player detection is not 100% reliable,
result . playerDimensions ? . width && // which makes playerDimensions.width unreliable as well (we assume nobody uses browser in
// fullscreen mode unless watching videos)
result . playerDimensions . width < window . screen . width * ( this . uiSettings . inPlayer . minEnabledWidth ? ? 0 )
) {
return result ;
}
2024-12-27 23:12:41 +01:00
result . canShowUI = true ;
2025-03-30 23:48:24 +02:00
2024-12-17 02:02:49 +01:00
return result ;
}
2022-03-21 00:50:54 +01:00
/ * *
* Handles events received from the iframe .
* @ param { * } event
* /
handleMessage ( event ) {
if ( event . origin === this . extensionBase ) {
2024-06-05 01:08:50 +02:00
switch ( event . data . action ) {
case 'uwui-clickable' :
if ( event . data . ts < this . lastProbeResponseTs ) {
return ;
}
if ( this . disablePointerEvents ) {
return ;
}
this . lastProbeResponseTs = event . data . ts ;
2025-01-05 00:36:41 +01:00
// If iframe returns 'yes, we are clickable' and iframe is currently set to pointerEvents=auto,
// but hasMouse is false, then UI is attached to the wrong element. This probably means our
// detected player element is wrong. We need to perform this check if we aren't in global UI
/ * *
* action : 'uwui-clickable' ,
* clickable : isClickable ,
* hoverStats : {
* isOverTriggerZone ,
* isOverMenuTrigger ,
* isOverUIArea ,
* hasMouse : ! ! document . querySelector ( ':hover' ) ,
* } ,
* ts : + new Date ( )
* /
if ( ! this . global ) {
if (
this . uiIframe . style . pointerEvents === 'auto'
) {
if (
2025-01-06 03:05:18 +01:00
event . data . hoverStats . isOverMenuTrigger
2025-01-05 00:36:41 +01:00
&& ! event . data . hoverStats . hasMouse
) {
if ( ! this . iframeConfirmed ) {
2025-01-06 03:05:18 +01:00
if ( this . iframeErrorCount ++ > MAX _IFRAME _ERROR _COUNT && ! this . iframeRejected ) {
this . iframeRejected = true ;
this . eventBus . send ( 'change-player-element' ) ;
return ;
2025-01-05 00:36:41 +01:00
}
}
2025-01-06 03:05:18 +01:00
} else if ( event . data . hoverStats . isOverMenuTrigger && event . data . hoverStats . hasMouse ) {
2025-01-05 00:36:41 +01:00
this . iframeConfirmed = true ;
}
}
}
2024-06-05 01:08:50 +02:00
this . uiIframe . style . pointerEvents = event . data . clickable ? 'auto' : 'none' ;
2025-01-13 01:22:49 +01:00
this . uiIframe . style . opacity = event . data . opacity || this . isGlobal ? '100' : '0' ;
2024-06-05 01:08:50 +02:00
break ;
case 'uw-bus-tunnel' :
const busCommand = event . data . payload ;
2025-05-19 01:16:33 +02:00
this . eventBus . send (
busCommand . action ,
busCommand . config ,
{
... busCommand ? . context ,
borderCrossings : {
... busCommand ? . context ? . borderCrossings ,
iframe : true ,
}
}
) ;
2024-06-05 01:08:50 +02:00
break ;
case 'uwui-get-role' :
this . sendToIframeLowLevel ( 'uwui-set-role' , { role : this . isGlobal ? 'global' : 'player' } ) ;
break ;
case 'uwui-interface-ready' :
this . setUiVisibility ( ! this . isGlobal ) ;
break ;
2025-01-12 23:24:01 +01:00
case 'uwui-hidden' :
2025-01-13 01:22:49 +01:00
this . uiIframe . style . opacity = event . data . opacity || this . isGlobal ? '100' : '0' ;
2025-01-12 23:24:01 +01:00
break ;
2024-06-05 01:08:50 +02:00
case 'uwui-global-window-hidden' :
if ( ! this . isGlobal ) {
return ; // This shouldn't even happen in non-global windows
}
this . setUiVisibility ( false ) ;
this . eventBus . send ( 'uw-restore-ui-state' , { } ) ;
2022-03-21 00:50:54 +01:00
}
}
2020-12-03 00:34:50 +01:00
}
2024-06-02 16:06:26 +02:00
/ * *
2024-06-05 01:08:50 +02:00
* Sends messages to iframe . Messages sent with this function _generally _
* bypass eventBus on the receiving end .
* @ param { * } action
* @ param { * } payload
* @ param { * } uiURI
2024-06-02 16:06:26 +02:00
* /
2024-06-05 01:08:50 +02:00
sendToIframeLowLevel ( action , payload , uiURI = this . uiURI ) {
2024-06-02 16:06:26 +02:00
// because existence of UI is not guaranteed — UI is not shown when extension is inactive.
// If extension is inactive due to "player element isn't big enough to justify it", however,
// we can still receive eventBus messages.
2025-03-30 23:48:24 +02:00
if ( this . rootDiv && this . uiIframe ) {
2024-06-05 01:08:50 +02:00
this . uiIframe . contentWindow ? . postMessage (
2024-06-02 16:06:26 +02:00
{
2024-06-05 01:08:50 +02:00
action ,
payload
2024-06-02 16:06:26 +02:00
} ,
uiURI
)
} ;
}
2024-06-05 01:08:50 +02:00
/ * *
* Sends message to iframe . Messages sent with this function will be routed to eventbus .
* /
sendToIframe ( action , actionConfig , routingData , uiURI = this . uiURI ) {
// if (routingData) {
// if (routingData.crossedConnections?.includes(EventBusConnector.IframeBoundaryIn)) {
// console.warn('Denied message propagation. It has already crossed INTO an iframe once.');
// return;
// }
// }
// if (!routingData) {
// routingData = { };
// }
// if (!routingData.crossedConnections) {
// routingData.crossedConnections = [];
// }
// routingData.crossedConnections.push(EventBusConnector.IframeBoundaryIn);
this . sendToIframeLowLevel (
'uw-bus-tunnel' ,
{
action ,
config : actionConfig ,
routingData
} ,
uiURI
) ;
}
2020-12-03 00:34:50 +01:00
/ * *
* Replaces ui config and re - inits the UI
2021-10-22 00:30:36 +02:00
* @ param { * } newUiConfig
2020-12-03 00:34:50 +01:00
* /
replace ( newUiConfig ) {
this . uiConfig = newUiConfig ;
2023-07-11 00:48:34 +02:00
2025-03-30 23:48:24 +02:00
if ( this . rootDiv ) {
this . destroy ( ) ;
2023-07-11 00:48:34 +02:00
this . init ( ) ;
}
2020-12-03 00:34:50 +01:00
}
destroy ( ) {
2025-03-30 23:48:24 +02:00
this . unloadIframe ( ) ;
2024-12-30 23:02:55 +01:00
this . eventBus . unsubscribeAll ( this ) ;
2020-12-04 02:02:25 +01:00
// this.comms?.destroy();
2025-03-30 23:48:24 +02:00
this . rootDiv ? . remove ( ) ;
2023-07-11 00:48:34 +02:00
2025-03-30 23:48:24 +02:00
delete this . uiIframe ;
delete this . rootDiv ;
2020-12-03 00:34:50 +01:00
}
}
2020-12-03 01:35:22 +01:00
if ( process . env . CHANNEL !== 'stable' ) {
console . info ( "UI.js loaded" ) ;
}
2020-12-03 01:16:57 +01:00
export default UI ;