Merge branch 'master' into 4990-improve-multi-select-drag-and-drop-implementation

pull/5370/head
Joseph Izang 4 months ago committed by GitHub
commit 9b6ee97cce
  1. 62
      apps/learneth/src/pages/StepDetail/index.tsx
  2. 1
      apps/learneth/src/redux/models/remixide.ts
  3. 4
      apps/learneth/src/redux/models/workshop.ts
  4. 4
      apps/remix-ide/src/app.js
  5. 190
      apps/remix-ide/src/app/providers/environment-explorer.tsx
  6. 6
      apps/remix-ide/src/app/providers/style/environment-explorer.css
  7. BIN
      apps/remix-ide/src/assets/img/EnvironmentExplorerLogo.webp
  8. 50
      apps/remix-ide/src/blockchain/blockchain.tsx
  9. 5
      apps/remix-ide/src/blockchain/execution-context.js
  10. 18
      apps/remix-ide/src/remixAppManager.js
  11. 6
      libs/remix-ui/git/src/lib/listeners.ts
  12. 5
      libs/remix-ui/grid-view/src/lib/remix-ui-grid-cell.css
  13. 13
      libs/remix-ui/grid-view/src/lib/remix-ui-grid-cell.tsx
  14. 38
      libs/remix-ui/grid-view/src/lib/remix-ui-grid-section.tsx
  15. 3
      libs/remix-ui/grid-view/src/lib/remix-ui-grid-view.tsx
  16. 4
      libs/remix-ui/run-tab/src/lib/actions/events.ts
  17. 55
      libs/remix-ui/run-tab/src/lib/components/environment.tsx

@ -5,11 +5,14 @@ import rehypeRaw from 'rehype-raw'
import BackButton from '../../components/BackButton'
import { useAppSelector, useAppDispatch } from '../../redux/hooks'
import './index.scss'
import remixClient from '../../remix-client'
function StepDetailPage() {
const navigate = useNavigate()
const location = useLocation()
const dispatch = useAppDispatch()
const [clonedStep, setClonedStep] = React.useState(null)
const queryParams = new URLSearchParams(location.search)
const id = queryParams.get('id') as string
const stepId = Number(queryParams.get('stepId'))
@ -20,18 +23,35 @@ function StepDetailPage() {
const entity = detail[selectedId].entities[id]
const steps = entity.steps
const step = steps[stepId]
console.log(step)
useEffect(() => {
setClonedStep(null)
const clonedStep = JSON.parse(JSON.stringify(step))
const loadFiles = async () => {
async function loadFile(step, fileType) {
if (step[fileType] && step[fileType].file && !step[fileType].content) {
clonedStep[fileType].content = (await remixClient.call('contentImport', 'resolve', step[fileType].file)).content;
}
}
const fileTypes = ['markdown', 'solidity', 'test', 'answer', 'js', 'vy'];
for (const fileType of fileTypes) {
await loadFile(step, fileType);
}
}
loadFiles().then(() => {
setClonedStep(clonedStep)
dispatch({
type: 'remixide/displayFile',
payload: step,
payload: clonedStep,
})
dispatch({
type: 'remixide/save',
payload: { errors: [], success: false },
})
window.scrollTo(0, 0)
})
}, [step])
useEffect(() => {
@ -40,6 +60,18 @@ function StepDetailPage() {
}
}, [errors, success])
if (!clonedStep) {
return (<div className='pb-4'>
<div className="fixed-top">
<div className="bg-light">
<BackButton entity={entity} />
</div>
</div>
loading...
</div>
)
}
return (
<div className='pb-4'>
<div className="fixed-top">
@ -51,13 +83,13 @@ function StepDetailPage() {
{errorLoadingFile ? (
<>
<div className="errorloadingspacer"></div>
<h1 className="pl-3 pr-3 pt-3 pb-1">{step.name}</h1>
<h1 className="pl-3 pr-3 pt-3 pb-1">{clonedStep.name}</h1>
<button
className="w-100nav-item rounded-0 nav-link btn btn-success test"
onClick={() => {
dispatch({
type: 'remixide/displayFile',
payload: step,
payload: clonedStep,
})
}}
>
@ -68,13 +100,13 @@ function StepDetailPage() {
) : (
<>
<div className="menuspacer"></div>
<h1 className="pr-3 pl-3 pt-3 pb-1">{step.name}</h1>
<h1 className="pr-3 pl-3 pt-3 pb-1">{clonedStep.name}</h1>
</>
)}
<div className="container-fluid">
<Markdown rehypePlugins={[rehypeRaw]}>{step.markdown?.content}</Markdown>
<Markdown rehypePlugins={[rehypeRaw]}>{clonedStep.markdown?.content}</Markdown>
</div>
{step.test?.content ? (
{clonedStep.test?.content ? (
<>
<nav className="nav nav-pills nav-fill">
{errorLoadingFile ? (
@ -83,7 +115,7 @@ function StepDetailPage() {
onClick={() => {
dispatch({
type: 'remixide/displayFile',
payload: step,
payload: clonedStep,
})
}}
>
@ -98,19 +130,19 @@ function StepDetailPage() {
onClick={() => {
dispatch({
type: 'remixide/testStep',
payload: step,
payload: clonedStep,
})
}}
>
Check Answer
</button>
{step.answer?.content && (
{clonedStep.answer?.content && (
<button
className="nav-item rounded-0 nav-link btn btn-warning test"
onClick={() => {
dispatch({
type: 'remixide/showAnswer',
payload: step,
payload: clonedStep,
})
}}
>
@ -130,13 +162,13 @@ function StepDetailPage() {
>
Next
</button>
{step.answer?.content && (
{clonedStep.answer?.content && (
<button
className="nav-item rounded-0 nav-link btn btn-warning test"
onClick={() => {
dispatch({
type: 'remixide/showAnswer',
payload: step,
payload: clonedStep,
})
}}
>
@ -185,13 +217,13 @@ function StepDetailPage() {
) : (
<>
<nav className="nav nav-pills nav-fill">
{!errorLoadingFile && step.answer?.content && (
{!errorLoadingFile && clonedStep.answer?.content && (
<button
className="nav-item rounded-0 nav-link btn btn-warning test"
onClick={() => {
dispatch({
type: 'remixide/showAnswer',
payload: step,
payload: clonedStep,
})
}}
>

@ -84,7 +84,6 @@ const Model: ModelType = {
const { detail, selectedId } = yield select((state) => state.workshop)
const workshop = detail[selectedId]
console.log('loading ', step, workshop)
path = `.learneth/${workshop.name}/${step.name}/${path}`
try {

@ -23,7 +23,7 @@ const Model: ModelType = {
},
effects: {
*init(_, { put }) {
const cache = localStorage.getItem('workshop.state')
const cache = null // don't use cache because remote might change
if (cache) {
const workshopState = JSON.parse(cache)
@ -90,7 +90,7 @@ const Model: ModelType = {
const key = stepKeysWithFile[k]
if (step[key]) {
try {
step[key].content = (yield remixClient.call('contentImport', 'resolve', step[key].file)).content
step[key].content = null // we load this later
} catch (error) {
console.error(error)
}

@ -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,

@ -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<any> = () => {}
constructor() {
super(profile)
this.providersFlat = {}
this.providers = {
'Injected': [],
'Remix VMs': [],
'Externals': []
}
}
async onActivation(): Promise<void> {
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<any>): void {
this.dispatch = dispatch
this.renderComponent()
}
render() {
return (
<div className="bg-dark" id="environmentExplorer">
<PluginViewWrapper plugin={this} />
</div>
)
}
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 (
<RemixUIGridView
plugin={this}
styleList={""}
logo={profile.icon}
enableFilter={true}
showUntagged={true}
showPin={true}
title={profile.description}
description="Choose how you would like to interact with a chain."
>
<RemixUIGridSection
plugin={this}
title='Injected'
hScrollable={true}
>
{this.providers['Injected'].map(provider => {
return <RemixUIGridCell
plugin={this}
title={provider.name}
classList='EECellStyle'
pinned={this.pinnedProviders.includes(provider.name)}
pinStateCallback={async (pinned: boolean) => {
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
}
}}
>
<div>{provider.name}</div>
</RemixUIGridCell>
})}
</RemixUIGridSection>
<RemixUIGridSection
plugin={this}
title='Remix VMs'
hScrollable= {true}
>{this.providers['Remix VMs'].map(provider => {
return <RemixUIGridCell
plugin={this}
title={provider.name}
classList='EECellStyle'
pinned={this.pinnedProviders.includes(provider.name)}
pinStateCallback={async (pinned: boolean) => {
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
}
}}
>
<div>{provider.name}</div>
</RemixUIGridCell>
})}</RemixUIGridSection>
<RemixUIGridSection
plugin={this}
title='Externals'
hScrollable= {true}
>{this.providers['Externals'].map(provider => {
return <RemixUIGridCell
plugin={this}
title={provider.name}
classList='EECellStyle'
pinned={this.pinnedProviders.includes(provider.name)}
pinStateCallback={async (pinned: boolean) => {
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
}
}}
>
<div>{provider.name}</div>
</RemixUIGridCell>
})}</RemixUIGridSection>
</RemixUIGridView>
)
}
}

@ -0,0 +1,6 @@
.EECellStyle {
min-height: 4rem;
max-width: 10rem;
min-width: 9rem;
max-height: 5rem;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

@ -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<void>
provider:{
sendAsync: (payload: any) => Promise<void>
}
}
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,8 +529,12 @@ export class Blockchain extends Plugin {
}
changeExecutionContext(context, 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) {
return this.executionContext.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) {

@ -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
}

@ -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) => {

@ -223,12 +223,6 @@ const calculateLocalStorage = async () => {
const quotaMB = bytesToMB(estimate.quota);
const availableMB = bytesToMB(estimate.quota - estimate.usage);
const percentageUsed = calculatePercentage(estimate.usage, estimate.quota);
console.log(`Used storage: ${usedMB} MB`);
console.log(`Total quota: ${quotaMB} MB`);
console.log(`Available storage: ${availableMB} MB`);
console.log(`Percentage used: ${percentageUsed}%`);
storage = {
used: usedMB,
total: quotaMB,

@ -23,12 +23,13 @@
.remixui_grid_cell_pin:focus {
outline: none;
}
.remixui_grid_cell_pin {
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 {

@ -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 (
<div className='mr-2 mt-3' onClick={() => {
<div data-values='gridCell' className='' onClick={() => {
if (props.expandViewEl)
props.handleExpand(!expand)
else return
}}>
{ anyEnabled && <div className='d-flex flex-column'>
{ anyEnabled && <div className='mr-2 mt-3 d-flex flex-column'>
<div className='d-flex flex-grid'>
<div className={"d-flex mx-0 p-2 bg-light border border-secondary remixui_grid_cell_container " + props.classList || ''} data-id={"remixUIGS" + props.title}>
<div className="d-flex remixui_grid_cell flex-column">
@ -77,9 +77,10 @@ export const RemixUIGridCell = (props: RemixUIGridCellProps) => {
</div>
{ filterCon.showPin && <button
className={`${pinned ? 'fa-duotone' : 'fa-light'}` + ` fa-map-pin text-info border-0 mb-0 remixui_grid_cell_pin`}
onClick={() => {
setPinned(!pinned)
props.pinStateCallback()
style={{ fontSize: 'large' }}
onClick={async () => {
if (!props.pinStateCallback) setPinned(!pinned)
if (await props.pinStateCallback(!pinned)) setPinned(!pinned)
}}
></button>}
{ props.tagList && <div className={`d-flex flex-column align-items-begin ` +`${filterCon.showPin ? 'remixui_grid_cell_tags' : 'remixui_grid_cell_tags_no_pin'}`}>

@ -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 (
<div
className={`d-flex px-4 py-2 flex-column w-100 remixui_grid_section_container ${props.classList}`}
@ -29,6 +66,7 @@ export const RemixUIGridSection = (props: RemixUIGridSectionProps) => {
<div className="d-flex flex-column w-100 remixui_grid_section">
{ props.title && <h6 className='mt-1 mb-0 align-items-left '>{ props.title }</h6> }
<div className={(props.hScrollable) ? `d-flex flex-row pb-2 overflow-auto` : `d-flex flex-wrap`}>
{ !hasChildCell(children) && <span> No items found </span>}
{ props.children }
</div>
{ props.expandedCell && <div>

@ -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"

@ -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))

@ -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) {
<div className="udapp_crow">
<label id="selectExEnv" className="udapp_settingsLabel">
<FormattedMessage id="udapp.environment" />
<CustomTooltip placement={'auto-end'} tooltipClasses="text-nowrap" tooltipId="info-recorder" tooltipText={<FormattedMessage id="udapp.tooltipText2" />}>
<a href="https://chainlist.org/" target="_blank">
<i className={'ml-2 fas fa-plug'} aria-hidden="true"></i>
@ -54,9 +58,42 @@ export function EnvironmentUI(props: EnvironmentProps) {
)}
</Dropdown.Toggle>
<Dropdown.Menu as={CustomMenu} className="w-100 custom-dropdown-items" data-id="custom-dropdown-items">
{props.providers.providerList.map(({ displayName, name }, index) => (
{props.providers.providerList.length === 0 && <Dropdown.Item>
<span className="">
No provider pinned
</span>
</Dropdown.Item>}
{ (props.providers.providerList.filter((provider) => { return provider.isInjected })).map(({ name, displayName }) => (
<Dropdown.Item
key={name}
onClick={() => {
handleChangeExEnv(name)
}}
data-id={`dropdown-item-${name}`}
>
<span className="">
{displayName}
</span>
</Dropdown.Item>
))}
{ props.providers.providerList.filter((provider) => { return provider.isInjected }).length !== 0 && <Dropdown.Divider className='border-secondary'></Dropdown.Divider> }
{ (props.providers.providerList.filter((provider) => { return provider.isVM })).map(({ displayName, name }) => (
<Dropdown.Item
key={name}
onClick={() => {
handleChangeExEnv(name)
}}
data-id={`dropdown-item-${name}`}
>
<span className="">
{displayName}
</span>
</Dropdown.Item>
))}
{ props.providers.providerList.filter((provider) => { return provider.isVM }).length !== 0 && <Dropdown.Divider className='border-secondary'></Dropdown.Divider> }
{ (props.providers.providerList.filter((provider) => { return !(provider.isVM || provider.isInjected) })).map(({ displayName, name }) => (
<Dropdown.Item
key={index}
key={name}
onClick={() => {
handleChangeExEnv(name)
}}
@ -68,6 +105,18 @@ export function EnvironmentUI(props: EnvironmentProps) {
</span>
</Dropdown.Item>
))}
<Dropdown.Divider className='border-secondary'></Dropdown.Divider>
<Dropdown.Item
key={10000}
onClick={() => {
props.setExecutionContext({ context: 'item-another-chain' })
}}
data-id={`dropdown-item-another-chain`}
>
<span className="">
Pin another chain...
</span>
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
</div>

Loading…
Cancel
Save