diff --git a/apps/remix-ide/src/app/components/panel.ts b/apps/remix-ide/src/app/components/panel.ts index 9bbd41e79a..0be7d94cb5 100644 --- a/apps/remix-ide/src/app/components/panel.ts +++ b/apps/remix-ide/src/app/components/panel.ts @@ -31,14 +31,12 @@ export class AbstractPanel extends HostPlugin { } addView (profile, view) { - console.log(profile, view) if (this.plugins[profile.name]) throw new Error(`Plugin ${profile.name} already rendered`) this.plugins[profile.name] = { profile: profile, view: view, active: false } - console.log(this.plugins) } removeView (profile) { @@ -61,20 +59,11 @@ export class AbstractPanel extends HostPlugin { */ showContent (name) { if (!this.plugins[name]) throw new Error(`Plugin ${name} is not yet activated`) - // hiding the current view and display the `moduleName` - /* - if (this.active) { - this.contents[this.active].style.display = 'none' - } - this.contents[name].style.display = 'flex' - this.contents[name].style.paddingTop = '20%' - this.contents[name].style.flexDirection = 'column' - */ + Object.values(this.plugins).forEach(plugin => { plugin.active = false }) this.plugins[name].active = true - console.log(this.plugins) } focus (name) { diff --git a/apps/remix-ide/src/remixAppManager.js b/apps/remix-ide/src/remixAppManager.js index e1b18a91ba..28e668f0f6 100644 --- a/apps/remix-ide/src/remixAppManager.js +++ b/apps/remix-ide/src/remixAppManager.js @@ -1,6 +1,6 @@ /* global localStorage, fetch */ import { PluginManager } from '@remixproject/engine' -import { IframePlugin } from '@remixproject/engine-web' +import { IframeReactPlugin } from '@remix-ui/app' import { EventEmitter } from 'events' import QueryParams from './lib/query-params' import { PermissionHandler } from './app/ui/persmission-handler' @@ -130,7 +130,7 @@ export class RemixAppManager extends PluginManager { } } return plugins.map(plugin => { - return new IframePlugin(plugin) + return new IframeReactPlugin(plugin) }) } diff --git a/libs/remix-ui/app/src/index.ts b/libs/remix-ui/app/src/index.ts index e47f8690e7..fd6aed5d67 100644 --- a/libs/remix-ui/app/src/index.ts +++ b/libs/remix-ui/app/src/index.ts @@ -1 +1,2 @@ export { default as RemixApp } from './lib/remix-app/remix-app' +export { default as IframeReactPlugin } from './lib/remix-app/plugins/IFrameReactPlugin' diff --git a/libs/remix-ui/app/src/lib/remix-app/components/panels/iFramePluginView.tsx b/libs/remix-ui/app/src/lib/remix-app/components/panels/iFramePluginView.tsx new file mode 100644 index 0000000000..f56ea4f863 --- /dev/null +++ b/libs/remix-ui/app/src/lib/remix-app/components/panels/iFramePluginView.tsx @@ -0,0 +1,29 @@ +import React, { useRef, useState } from 'react' +import IframeReactPlugin from '../../plugins/IFrameReactPlugin' + +interface IFramePluginViewProps { + plugin: IframeReactPlugin +} + +const IFramePluginView = (props: IFramePluginViewProps) => { + const ref = useRef() + const [loading, isLoading] = useState(true) + + const loaded = () => { + props.plugin.shake(ref.current) + isLoading(false) + } + + const loader =
+
+ Loading... +
+
+ + return (<> +
{loader}
+ + ) +} + +export default IFramePluginView diff --git a/libs/remix-ui/app/src/lib/remix-app/plugins/IFrameReactPlugin.tsx b/libs/remix-ui/app/src/lib/remix-app/plugins/IFrameReactPlugin.tsx new file mode 100644 index 0000000000..27a807a5b5 --- /dev/null +++ b/libs/remix-ui/app/src/lib/remix-app/plugins/IFrameReactPlugin.tsx @@ -0,0 +1,85 @@ +import type { Message, Profile, ExternalProfile, LocationProfile } from '@remixproject/plugin-utils' +import { PluginConnector } from '@remixproject/engine' +import React from 'react' // eslint-disable-line +import IFramePluginView from '../components/panels/iFramePluginView' + +export type IframeProfile = Profile & LocationProfile & ExternalProfile + +/** + * Connect an Iframe client to the engine. + * @dev This implements the ViewPlugin as it cannot extends two class. Maybe use a mixin at some point + */ +class IframeReactPlugin extends PluginConnector { + // Listener is needed to remove the listener + private readonly listener = ['message', (e: MessageEvent) => this.getEvent(e), false] as const + private container: any + private origin: string + private source: Window + private url: string + + constructor (public profile: IframeProfile) { + super(profile) + } + + /** Implement "activate" of the ViewPlugin */ + connect (url: string) { + this.profile.url = url + this.render() + } + + addToView () { + this.call(this.profile.location, 'addView', this.profile, this.container) + } + + shake (iframe: any) { + return new Promise((resolve, reject) => { + // Wait for the iframe to load and handshake + + if (!iframe.contentWindow) { + reject(new Error(`${this.name} plugin cannot find url ${this.profile.url}`)) + } + this.origin = new URL(iframe.src).origin + this.source = iframe.contentWindow + window.addEventListener(...this.listener) + this.handshake() + .then(resolve) + .catch(reject) + // + }) + } + + /** Implement "deactivate" of the ViewPlugin */ + disconnect () { + console.trace('disconnect') + window.removeEventListener(...this.listener) + return this.call(this.profile.location, 'removeView', this.profile) + .catch(console.error) + } + + /** Get message from the iframe */ + private async getEvent (event: MessageEvent) { + if (event.source !== this.source) return // Filter only messages that comes from this iframe + if (event.origin !== this.origin) return // Filter only messages that comes from this origin + const message: Message = event.data + this.getMessage(message) + } + + /** + * Post a message to the iframe of this plugin + * @param message The message to post + */ + protected send (message: Partial) { + if (!this.source) { + throw new Error('No window attached to Iframe yet') + } + this.source.postMessage(message, this.origin) + } + + /** Create and return the iframe */ + render () { + this.container = + this.addToView() + } +} + +export default IframeReactPlugin diff --git a/libs/remix-ui/plugin-manager/src/lib/components/LocalPluginForm.tsx b/libs/remix-ui/plugin-manager/src/lib/components/LocalPluginForm.tsx index 3cada8585b..4711c52924 100644 --- a/libs/remix-ui/plugin-manager/src/lib/components/LocalPluginForm.tsx +++ b/libs/remix-ui/plugin-manager/src/lib/components/LocalPluginForm.tsx @@ -6,6 +6,7 @@ import { IframePlugin, WebsocketPlugin } from '@remixproject/engine-web' import { localPluginReducerActionType, localPluginToastReducer } from '../reducers/pluginManagerReducer' import { canActivate, FormStateProps, PluginManagerComponent } from '../../types' +import { IframeReactPlugin } from '@remix-ui/app' interface LocalPluginFormProps { closeModal: () => void @@ -79,7 +80,7 @@ function LocalPluginForm ({ closeModal, visible, pluginManager }: LocalPluginFor icon: 'assets/img/localPlugin.webp', canActivate: typeof canactivate === 'string' ? canactivate.split(',').filter(val => val).map(val => { return val.trim() }) : [] } - const localPlugin = type === 'iframe' ? new IframePlugin(initialState) : new WebsocketPlugin(initialState) + const localPlugin = type === 'iframe' ? new IframeReactPlugin(initialState) : new WebsocketPlugin(initialState) localPlugin.profile.hash = `local-${name}` targetPlugin.description = localPlugin.profile.description !== undefined ? localPlugin.profile.description : '' targetPlugin.events = localPlugin.profile.events !== undefined ? localPlugin.profile.events : [] diff --git a/libs/remix-ui/plugin-manager/src/types.d.ts b/libs/remix-ui/plugin-manager/src/types.d.ts index d30ae31457..66ee57bc77 100644 --- a/libs/remix-ui/plugin-manager/src/types.d.ts +++ b/libs/remix-ui/plugin-manager/src/types.d.ts @@ -4,6 +4,7 @@ import { EventEmitter } from 'events' import { Engine } from '@remixproject/engine/lib/engine' import { PluginBase, Profile } from '@remixproject/plugin-utils' import { IframePlugin, ViewPlugin, WebsocketPlugin } from '@remixproject/engine-web' +import { IframeReactPlugin } from '@remix-ui/app' /* eslint-disable camelcase */ interface SetPluginOptionType { @@ -88,7 +89,7 @@ export class PluginManagerComponent extends ViewPlugin extends Plugin implements render(): HTMLDivElement getAndFilterPlugins: (filter?: string, profiles?: Profile[]) => void triggerEngineEventListener: () => void - activateAndRegisterLocalPlugin: (localPlugin: IframePlugin | WebsocketPlugin) => Promise + activateAndRegisterLocalPlugin: (localPlugin: IframePlugin | IframeReactPlugin | WebsocketPlugin) => Promise activeProfiles: string[] _paq: any }