diff --git a/apps/remix-ide/src/app.js b/apps/remix-ide/src/app.js index 3e0eacdeb05..a7b0b5adc44 100644 --- a/apps/remix-ide/src/app.js +++ b/apps/remix-ide/src/app.js @@ -40,6 +40,7 @@ import {HardhatProvider} from './app/providers/hardhat-provider' import {GanacheProvider} from './app/providers/ganache-provider' import {FoundryProvider} from './app/providers/foundry-provider' import {ExternalHttpProvider} from './app/providers/external-http-provider' +import { EnvironmentExplorer } from './app/providers/environment-explorer' import { FileDecorator } from './app/plugins/file-decorator' import { CodeFormat } from './app/plugins/code-format' import { SolidityUmlGen } from './app/plugins/solidity-umlgen' @@ -276,6 +277,8 @@ class AppComponent { const ganacheProvider = new GanacheProvider(blockchain) const foundryProvider = new FoundryProvider(blockchain) const externalHttpProvider = new ExternalHttpProvider(blockchain) + + const environmentExplorer = new EnvironmentExplorer() // ----------------- convert offset to line/column service ----------- const offsetToLineColumnConverter = new OffsetToLineColumnConverter() Registry.getInstance().put({ @@ -350,6 +353,7 @@ class AppComponent { ganacheProvider, foundryProvider, externalHttpProvider, + environmentExplorer, this.walkthroughService, search, solidityumlgen, diff --git a/apps/remix-ide/src/app/providers/environment-explorer.tsx b/apps/remix-ide/src/app/providers/environment-explorer.tsx new file mode 100644 index 00000000000..f07246e88fd --- /dev/null +++ b/apps/remix-ide/src/app/providers/environment-explorer.tsx @@ -0,0 +1,190 @@ +import React from 'react' // eslint-disable-line +import { ViewPlugin } from '@remixproject/engine-web' +import { PluginViewWrapper } from '@remix-ui/helper' +import { RemixUIGridView } from '@remix-ui/remix-ui-grid-view' +import { RemixUIGridSection } from '@remix-ui/remix-ui-grid-section' +import { RemixUIGridCell } from '@remix-ui/remix-ui-grid-cell' +import './style/environment-explorer.css' +import type { Provider } from '../../blockchain/blockchain' + +import * as packageJson from '../../../../../package.json' + +const _paq = (window._paq = window._paq || []) + +const profile = { + name: 'environmentExplorer', + displayName: 'Environment Explorer', + icon: 'assets/img/EnvironmentExplorerLogo.webp', + description: 'Explore providers and customize web3 provider list', + location: 'mainPanel', + documentation: 'https://remix-ide.readthedocs.io/en/latest/run.html', + version: packageJson.version, + maintainedBy: 'Remix', + permission: true, + events: [], + methods: [] +} + +type ProvidersSection = `Injected` | 'Remix VMs' | 'Externals' + +export class EnvironmentExplorer extends ViewPlugin { + providers: { [key in ProvidersSection]: Provider[] } + providersFlat: { [key: string]: Provider } + pinnedProviders: string[] + dispatch: React.Dispatch = () => {} + + constructor() { + super(profile) + this.providersFlat = {} + this.providers = { + 'Injected': [], + 'Remix VMs': [], + 'Externals': [] + } + } + + async onActivation(): Promise { + this.providersFlat = await this.call('blockchain', 'getAllProviders') + this.pinnedProviders = await this.call('blockchain', 'getPinnedProviders') + this.renderComponent() + } + + addProvider (provider: Provider) { + if (provider.isInjected) { + this.providers['Injected'].push(provider) + } else if (provider.isVM) { + this.providers['Remix VMs'].push(provider) + } else { + this.providers['Externals'].push(provider) + } + } + + setDispatch(dispatch: React.Dispatch): void { + this.dispatch = dispatch + this.renderComponent() + } + render() { + return ( +
+ +
+ ) + } + + renderComponent() { + this.dispatch({ + ...this + }) + } + + updateComponent(state: any) { + this.providers = { + 'Injected': [], + 'Remix VMs': [], + 'Externals': [] + } + console.log(this.providersFlat) + for (const [key, provider] of Object.entries(this.providersFlat)) { + this.addProvider(provider) + } + return ( + + + {this.providers['Injected'].map(provider => { + return { + if (pinned) { + this.emit('providerPinned', provider.name, provider) + return true + } + const providerName = await this.call('blockchain', 'getProvider') + if (providerName !== provider.name) { + this.emit('providerUnpinned', provider.name, provider) + return true + } else { + this.call('notification', 'toast', 'Cannot unpin the current selected provider') + return false + } + }} + > +
{provider.name}
+
+ })} +
+ {this.providers['Remix VMs'].map(provider => { + return { + if (pinned) { + this.emit('providerPinned', provider.name, provider) + return true + } + const providerName = await this.call('blockchain', 'getProvider') + if (providerName !== provider.name) { + this.emit('providerUnpinned', provider.name, provider) + return true + } else { + this.call('notification', 'toast', 'Cannot unpin the current selected provider') + return false + } + }} + > +
{provider.name}
+
+ })}
+ {this.providers['Externals'].map(provider => { + return { + if (pinned) { + this.emit('providerPinned', provider.name, provider) + return true + } + const providerName = await this.call('blockchain', 'getProvider') + if (providerName !== provider.name) { + this.emit('providerUnpinned', provider.name, provider) + return true + } else { + this.call('notification', 'toast', 'Cannot unpin the current selected provider') + return false + } + }} + > +
{provider.name}
+
+ })}
+
+ ) + } +} diff --git a/apps/remix-ide/src/app/providers/style/environment-explorer.css b/apps/remix-ide/src/app/providers/style/environment-explorer.css new file mode 100644 index 00000000000..1ff92d83288 --- /dev/null +++ b/apps/remix-ide/src/app/providers/style/environment-explorer.css @@ -0,0 +1,6 @@ +.EECellStyle { + min-height: 4rem; + max-width: 10rem; + min-width: 9rem; + max-height: 5rem; +} diff --git a/apps/remix-ide/src/assets/img/EnvironmentExplorerLogo.webp b/apps/remix-ide/src/assets/img/EnvironmentExplorerLogo.webp new file mode 100644 index 00000000000..8fc0b06788c Binary files /dev/null and b/apps/remix-ide/src/assets/img/EnvironmentExplorerLogo.webp differ diff --git a/apps/remix-ide/src/blockchain/blockchain.tsx b/apps/remix-ide/src/blockchain/blockchain.tsx index bbe6d302f12..01cf1dd50bc 100644 --- a/apps/remix-ide/src/blockchain/blockchain.tsx +++ b/apps/remix-ide/src/blockchain/blockchain.tsx @@ -23,7 +23,7 @@ const profile = { name: 'blockchain', displayName: 'Blockchain', description: 'Blockchain - Logic', - methods: ['getCode', 'getTransactionReceipt', 'addProvider', 'removeProvider', 'getCurrentFork', 'getAccounts', 'web3VM', 'web3', 'getProvider', 'getCurrentNetworkStatus'], + methods: ['getCode', 'getTransactionReceipt', 'addProvider', 'removeProvider', 'getCurrentFork', 'getAccounts', 'web3VM', 'web3', 'getProvider', 'getCurrentNetworkStatus', 'getAllProviders', 'getPinnedProviders'], version: packageJson.version } @@ -44,6 +44,21 @@ export type Transaction = { timestamp?: number } +export type Provider = { + options: { [key: string]: string } + dataId: string + name: string + displayName: string + fork: string + isInjected: boolean + isVM: boolean + title: string + init: () => Promise + provider:{ + sendAsync: (payload: any) => Promise + } +} + export class Blockchain extends Plugin { active: boolean event: EventManager @@ -62,6 +77,7 @@ export class Blockchain extends Plugin { providers: {[key: string]: VMProvider | InjectedProvider | NodeProvider} transactionContextAPI: TransactionContextAPI registeredPluginEvents: string[] + pinnedProviders: string[] // NOTE: the config object will need to be refactored out in remix-lib constructor(config: Config) { @@ -93,6 +109,7 @@ export class Blockchain extends Plugin { this.networkcallid = 0 this.networkStatus = { network: { name: ' - ', id: ' - ' } } this.registeredPluginEvents = [] + this.pinnedProviders = ['vm-cancun', 'vm-shanghai', 'vm-mainnet-fork', 'vm-london', 'vm-berlin', 'vm-paris', 'walletconnect', 'injected-MetaMask', 'basic-http-provider', 'ganache-provider', 'hardhat-provider', 'foundry-provider'] this.setupEvents() this.setupProviders() } @@ -116,6 +133,14 @@ export class Blockchain extends Plugin { }) } }) + + this.on('environmentExplorer', 'providerPinned', (name, provider) => { + this.emit('shouldAddProvidertoUdapp', name, provider) + }) + + this.on('environmentExplorer', 'providerUnpinned', (name, provider) => { + this.emit('shouldRemoveProviderFromUdapp', name, provider) + }) } onDeactivation() { @@ -136,12 +161,12 @@ export class Blockchain extends Plugin { }) }) - this.executionContext.event.register('addProvider', (network) => { - this._triggerEvent('addProvider', [network]) + this.executionContext.event.register('providerAdded', (network) => { + this._triggerEvent('providerAdded', [network]) }) - this.executionContext.event.register('removeProvider', (name) => { - this._triggerEvent('removeProvider', [name]) + this.executionContext.event.register('providerRemoved', (name) => { + this._triggerEvent('providerRemoved', [name]) }) setInterval(() => { @@ -504,7 +529,11 @@ export class Blockchain extends Plugin { } changeExecutionContext(context, confirmCb, infoCb, cb) { - return this.executionContext.executionContextChange(context, null, confirmCb, infoCb, cb) + if (context.context === 'item-another-chain') { + this.call('manager', 'activatePlugin', 'environmentExplorer').then(() => this.call('tabs', 'focus', 'environmentExplorer')) + } else { + return this.executionContext.executionContextChange(context, null, confirmCb, infoCb, cb) + } } detectNetwork(cb) { @@ -611,7 +640,8 @@ export class Blockchain extends Plugin { this.executionContext.listenOnLastBlock() } - addProvider(provider) { + addProvider(provider: Provider) { + if (this.pinnedProviders.includes(provider.name)) this.emit('shouldAddProvidertoUdapp', provider.name, provider) this.executionContext.addProvider(provider) } @@ -619,6 +649,14 @@ export class Blockchain extends Plugin { this.executionContext.removeProvider(name) } + getAllProviders() { + return this.executionContext.getAllProviders() + } + + getPinnedProviders() { + return this.pinnedProviders + } + // TODO : event should be triggered by Udapp instead of TxListener /** Listen on New Transaction. (Cannot be done inside constructor because txlistener doesn't exist yet) */ startListening(txlistener) { diff --git a/apps/remix-ide/src/blockchain/execution-context.js b/apps/remix-ide/src/blockchain/execution-context.js index b54b8d371bf..fd127fb20c5 100644 --- a/apps/remix-ide/src/blockchain/execution-context.js +++ b/apps/remix-ide/src/blockchain/execution-context.js @@ -123,10 +123,13 @@ export class ExecutionContext { addProvider (network) { if (network && network.name && !this.customNetWorks[network.name]) { this.customNetWorks[network.name] = network - this.event.trigger('addProvider', [network]) } } + getAllProviders () { + return this.customNetWorks + } + internalWeb3 () { return web3 } diff --git a/apps/remix-ide/src/remixAppManager.js b/apps/remix-ide/src/remixAppManager.js index 20b69d984f3..a42ae836f17 100644 --- a/apps/remix-ide/src/remixAppManager.js +++ b/apps/remix-ide/src/remixAppManager.js @@ -82,11 +82,10 @@ let requiredModules = [ // services + layout views + system views 'pinnedPanel', 'pluginStateLogger', 'remixGuide', - 'matomo' + 'matomo', + 'walletconnect' ] - - // dependentModules shouldn't be manually activated (e.g hardhat is activated by remixd) const dependentModules = ['foundry', 'hardhat', 'truffle', 'slither'] @@ -133,7 +132,7 @@ export function isNative(name) { 'circuit-compiler', 'compilationDetails', 'vyperCompilationDetails', - 'remixGuide', + //'remixGuide', 'walletconnect' ] return nativePlugins.includes(name) || requiredModules.includes(name) || isInjectedProvider(name) || isVM(name) @@ -389,7 +388,16 @@ class PluginLoader { constructor() { const queryParams = new QueryParams() - this.donotAutoReload = ['remixd'] // that would be a bad practice to force loading some plugins at page load. + // some plugins should not be activated at page load. + this.donotAutoReload = [ + 'remixd', + 'environmentExplorer', + 'templateSelection', + 'compilationDetails', + 'walletconnect', + 'dapp-draft', + 'solidityumlgen' + ] this.loaders = {} this.loaders.localStorage = { set: (plugin, actives) => { diff --git a/libs/remix-ui/grid-view/src/lib/remix-ui-grid-cell.css b/libs/remix-ui/grid-view/src/lib/remix-ui-grid-cell.css index 46e31866a85..ed613fd8b39 100644 --- a/libs/remix-ui/grid-view/src/lib/remix-ui-grid-cell.css +++ b/libs/remix-ui/grid-view/src/lib/remix-ui-grid-cell.css @@ -23,12 +23,13 @@ .remixui_grid_cell_pin:focus { outline: none; } + .remixui_grid_cell_pin { - width: 1rem; + width: 1rem; height: 1rem; position: relative; right: 1rem; - top: -0.5rem; + top: -0.8rem; background: transparent; } @@ -41,7 +42,7 @@ .remixui_grid_cell_tags_no_pin { position: relative; right: 0rem; - top: 0.1rem; + top: -6.5rem; } .remixui_grid_cell_tag { diff --git a/libs/remix-ui/grid-view/src/lib/remix-ui-grid-cell.tsx b/libs/remix-ui/grid-view/src/lib/remix-ui-grid-cell.tsx index e3d979e0cb7..cbbe2696033 100644 --- a/libs/remix-ui/grid-view/src/lib/remix-ui-grid-cell.tsx +++ b/libs/remix-ui/grid-view/src/lib/remix-ui-grid-cell.tsx @@ -34,8 +34,8 @@ export const RemixUIGridCell = (props: RemixUIGridCellProps) => { useEffect(() => { if (props.tagList) setAnyEnabled(props.tagList.some((key) => filterCon.keyValueMap[key]?.enabled)) else setAnyEnabled(filterCon?.keyValueMap['no tag']?.enabled) + if (!props.tagList || props.tagList.length == 0) setAnyEnabled(true) if (filterCon.filter != '') setAnyEnabled(anyEnabled && props.title.toLowerCase().includes(filterCon.filter)) - console.log("pin ", pinned) }, [filterCon, props.tagList]) @@ -54,12 +54,12 @@ export const RemixUIGridCell = (props: RemixUIGridCellProps) => { */ return ( -
{ +
{ if (props.expandViewEl) props.handleExpand(!expand) else return }}> - { anyEnabled &&
+ { anyEnabled &&
@@ -77,9 +77,10 @@ export const RemixUIGridCell = (props: RemixUIGridCellProps) => {
{ filterCon.showPin && } { props.tagList &&
diff --git a/libs/remix-ui/grid-view/src/lib/remix-ui-grid-section.tsx b/libs/remix-ui/grid-view/src/lib/remix-ui-grid-section.tsx index ca391c7685b..93d98950e28 100644 --- a/libs/remix-ui/grid-view/src/lib/remix-ui-grid-section.tsx +++ b/libs/remix-ui/grid-view/src/lib/remix-ui-grid-section.tsx @@ -1,6 +1,7 @@ import React, {useState, useEffect, useContext, useRef, ReactNode} from 'react' // eslint-disable-line import './remix-ui-grid-section.css' +import FiltersContext from "./filtersContext" declare global { interface Window { @@ -19,7 +20,43 @@ interface RemixUIGridSectionProps { expandedCell?: any } +const hasChildCell = (children: ReactNode): boolean => { + let found = false + + const isElement = (child: ReactNode): child is React.ReactElement => { + return React.isValidElement(child) + } + + const traverse = (child: ReactNode) => { + console.log('found ', children) + + if (found) return + + if (isElement(child)) { + if (child.props.classList === 'EECellStyle') { + found = true + console.log('found ', child.props.className) + return + } + + if (child.props.children) { + React.Children.forEach(child.props.children, traverse) + } + } + } + + React.Children.forEach(children, traverse) + return found +} + export const RemixUIGridSection = (props: RemixUIGridSectionProps) => { + const [children, setChildren] = useState(props.children) + const filterCon = useContext(FiltersContext) + + useEffect(() => { + setChildren(props.children) + }, [props.children]) + return (
{
{ props.title &&
{ props.title }
}
+ { !hasChildCell(children) && No items found } { props.children }
{ props.expandedCell &&
diff --git a/libs/remix-ui/grid-view/src/lib/remix-ui-grid-view.tsx b/libs/remix-ui/grid-view/src/lib/remix-ui-grid-view.tsx index 6c6cc27bbde..b8c83738695 100644 --- a/libs/remix-ui/grid-view/src/lib/remix-ui-grid-view.tsx +++ b/libs/remix-ui/grid-view/src/lib/remix-ui-grid-view.tsx @@ -51,7 +51,6 @@ export const RemixUIGridView = (props: RemixUIGridViewProps) => { searchInputRef.current.value = '' } else { setState((prevState) => { - console.log("update filter", searchInputRef.current.value) return { ...prevState, searchDisable: searchInputRef.current.value === '', @@ -120,7 +119,7 @@ export const RemixUIGridView = (props: RemixUIGridViewProps) => { ref={searchInputRef} type="text" style={{ minWidth: '100px' }} - className="border form-control border-right-0 mr-4" + className="border form-control mr-4" id="GVFilterInput" placeholder={"Filter the list"} data-id="RemixGVFilterInput" diff --git a/libs/remix-ui/run-tab/src/lib/actions/events.ts b/libs/remix-ui/run-tab/src/lib/actions/events.ts index b5b4037eb2a..41deac6c6f0 100644 --- a/libs/remix-ui/run-tab/src/lib/actions/events.ts +++ b/libs/remix-ui/run-tab/src/lib/actions/events.ts @@ -69,9 +69,9 @@ export const setupEvents = (plugin: RunTab) => { } }) - plugin.blockchain.event.register('addProvider', provider => addExternalProvider(dispatch, provider)) + plugin.on('blockchain', 'shouldAddProvidertoUdapp', (name, provider) => addExternalProvider(dispatch, provider)) - plugin.blockchain.event.register('removeProvider', name => removeExternalProvider(dispatch, name)) + plugin.on('blockchain', 'shouldRemoveProviderFromUdapp', (name, provider) => removeExternalProvider(dispatch, name)) plugin.blockchain.events.on('newProxyDeployment', (address, date, contractName) => addNewProxyDeployment(dispatch, address, date, contractName)) diff --git a/libs/remix-ui/run-tab/src/lib/components/environment.tsx b/libs/remix-ui/run-tab/src/lib/components/environment.tsx index 9f07f26186d..17509eaf031 100644 --- a/libs/remix-ui/run-tab/src/lib/components/environment.tsx +++ b/libs/remix-ui/run-tab/src/lib/components/environment.tsx @@ -6,6 +6,11 @@ import { Dropdown } from 'react-bootstrap' import { CustomMenu, CustomToggle, CustomTooltip } from '@remix-ui/helper' export function EnvironmentUI(props: EnvironmentProps) { + + Object.entries(props.providers.providerList.filter((provider) => { return provider.isVM })) + Object.entries(props.providers.providerList.filter((provider) => { return provider.isInjected })) + Object.entries(props.providers.providerList.filter((provider) => { return !(provider.isVM || provider.isInjected) })) + const handleChangeExEnv = (env: string) => { const provider = props.providers.providerList.find((exEnv) => exEnv.name === env) const context = provider.name @@ -23,7 +28,6 @@ export function EnvironmentUI(props: EnvironmentProps) {