pull/5013/head
Your Name 4 months ago
commit 4d3dd97c80
  1. 50
      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-e2e/src/tests/dgit_local.test.ts
  5. 4
      apps/remix-ide/ci/deploy_from_travis_remix-alpha.sh
  6. 4
      apps/remix-ide/ci/deploy_from_travis_remix-beta.sh
  7. 4
      apps/remix-ide/ci/deploy_from_travis_remix-live.sh
  8. 6
      apps/remix-ide/src/app.js
  9. 190
      apps/remix-ide/src/app/providers/environment-explorer.tsx
  10. 6
      apps/remix-ide/src/app/providers/style/environment-explorer.css
  11. BIN
      apps/remix-ide/src/assets/img/EnvironmentExplorerLogo.webp
  12. 52
      apps/remix-ide/src/blockchain/blockchain.tsx
  13. 5
      apps/remix-ide/src/blockchain/execution-context.js
  14. 18
      apps/remix-ide/src/remixAppManager.js
  15. 13
      libs/remix-api/src/lib/plugins/layout-api.ts
  16. 11
      libs/remix-api/src/lib/plugins/pinned-panel-api.ts
  17. 11
      libs/remix-api/src/lib/plugins/sidePanel-api.ts
  18. 6
      libs/remix-api/src/lib/remix-api.ts
  19. 1
      libs/remix-ui/editor/src/lib/remix-ui-editor.tsx
  20. 69
      libs/remix-ui/git/src/components/branchHeader.tsx
  21. 3
      libs/remix-ui/git/src/components/github/repositoryselect.tsx
  22. 2
      libs/remix-ui/git/src/components/github/selectandclonerepositories.tsx
  23. 2
      libs/remix-ui/git/src/components/gitui.tsx
  24. 2
      libs/remix-ui/git/src/components/panels/remotesimport.tsx
  25. 23
      libs/remix-ui/git/src/lib/listeners.ts
  26. 7
      libs/remix-ui/grid-view/src/lib/remix-ui-grid-cell.css
  27. 13
      libs/remix-ui/grid-view/src/lib/remix-ui-grid-cell.tsx
  28. 39
      libs/remix-ui/grid-view/src/lib/remix-ui-grid-section.tsx
  29. 3
      libs/remix-ui/grid-view/src/lib/remix-ui-grid-view.tsx
  30. 4
      libs/remix-ui/run-tab/src/lib/actions/events.ts
  31. 55
      libs/remix-ui/run-tab/src/lib/components/environment.tsx
  32. 17
      libs/remix-ui/statusbar/src/contexts/statusbarcontext.tsx
  33. 13
      libs/remix-ui/statusbar/src/css/statusbar.css
  34. 9
      libs/remix-ui/statusbar/src/lib/components/aiStatus.tsx
  35. 35
      libs/remix-ui/statusbar/src/lib/components/didYouKnow.tsx
  36. 5
      libs/remix-ui/statusbar/src/lib/components/gitStatus.tsx
  37. 2
      libs/remix-ui/statusbar/src/lib/components/scamAlertStatus.tsx
  38. 6
      libs/remix-ui/statusbar/src/lib/components/scamDetails.tsx
  39. 42
      libs/remix-ui/statusbar/src/lib/remixui-statusbar-panel.tsx
  40. 12
      libs/remix-ui/statusbar/src/lib/types/index.ts
  41. 0
      libs/remix-ui/statusbar/src/utils/index.ts
  42. 2
      libs/remix-ui/workspace/src/lib/components/flat-tree-drop.tsx
  43. 38
      libs/remix-ui/workspace/src/lib/components/flat-tree.tsx
  44. 31
      libs/remix-ui/workspace/src/lib/utils/getEventTarget.ts
  45. 5
      libs/remix-url-resolver/src/resolve.ts

@ -12,7 +12,6 @@ function StepDetailPage() {
const location = useLocation() const location = useLocation()
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const [clonedStep, setClonedStep] = React.useState(null) const [clonedStep, setClonedStep] = React.useState(null)
const [loading, setLoading] = React.useState(false)
const queryParams = new URLSearchParams(location.search) const queryParams = new URLSearchParams(location.search)
const id = queryParams.get('id') as string const id = queryParams.get('id') as string
const stepId = Number(queryParams.get('stepId')) const stepId = Number(queryParams.get('stepId'))
@ -23,39 +22,24 @@ function StepDetailPage() {
const entity = detail[selectedId].entities[id] const entity = detail[selectedId].entities[id]
const steps = entity.steps const steps = entity.steps
const step = steps[stepId] const step = steps[stepId]
console.log(step)
useEffect(() => { useEffect(() => {
setClonedStep(null)
const clonedStep = JSON.parse(JSON.stringify(step)) const clonedStep = JSON.parse(JSON.stringify(step))
const loadFiles = async () => { const loadFiles = async () => {
if (step.markdown && step.markdown.file && !step.markdown.content) { async function loadFile(step, fileType) {
console.log('loading md file', step.markdown.file) if (step[fileType] && step[fileType].file && !step[fileType].content) {
clonedStep.markdown.content = (await remixClient.call('contentImport', 'resolve', step.markdown.file)).content clonedStep[fileType].content = (await remixClient.call('contentImport', 'resolve', step[fileType].file)).content;
} }
if (step.solidity && step.solidity.file && !step.solidity.content) {
console.log('loading sol file', step.solidity.file)
clonedStep.solidity.content = (await remixClient.call('contentImport', 'resolve', step.solidity.file)).content
}
if (step.test && step.test.file && !step.test.content) {
console.log('loading test file', step.test.file)
clonedStep.test.content = (await remixClient.call('contentImport', 'resolve', step.test.file)).content
}
if (step.answer && step.answer.file && !step.answer.content) {
console.log('loading answer file', step.answer.file)
clonedStep.answer.content = (await remixClient.call('contentImport', 'resolve', step.answer.file)).content
} }
if(step.js && step.js.file && !step.js.content) {
console.log('loading js file', step.js.file) const fileTypes = ['markdown', 'solidity', 'test', 'answer', 'js', 'vy'];
clonedStep.js.content = (await remixClient.call('contentImport', 'resolve', step.js.file)).content for (const fileType of fileTypes) {
} await loadFile(step, fileType);
if(step.vy && step.vy.file && !step.vy.content) {
console.log('loading vy file', step.vy.file)
clonedStep.vy.content = (await remixClient.call('contentImport', 'resolve', step.vy.file)).content
} }
} }
loadFiles().then(() => { loadFiles().then(() => {
console.log('displayFile', clonedStep)
setClonedStep(clonedStep) setClonedStep(clonedStep)
dispatch({ dispatch({
type: 'remixide/displayFile', type: 'remixide/displayFile',
@ -75,8 +59,16 @@ function StepDetailPage() {
} }
}, [errors, success]) }, [errors, success])
if(!clonedStep) { if (!clonedStep) {
return null return (<div className='pb-4'>
<div className="fixed-top">
<div className="bg-light">
<BackButton entity={entity} />
</div>
</div>
loading...
</div>
)
} }
return ( return (

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

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

@ -60,6 +60,7 @@ module.exports = {
browser. browser.
addFile('test.txt', { content: 'hello world' }, 'README.md') addFile('test.txt', { content: 'hello world' }, 'README.md')
.clickLaunchIcon('dgit') .clickLaunchIcon('dgit')
.pause(3000)
.click('*[data-id="sourcecontrol-panel"]') .click('*[data-id="sourcecontrol-panel"]')
.waitForElementVisible({ .waitForElementVisible({
selector: "//*[@data-status='new-untracked' and @data-file='/test.txt']", selector: "//*[@data-status='new-untracked' and @data-file='/test.txt']",
@ -187,6 +188,7 @@ module.exports = {
'stage renamed file #group3': function (browser: NightwatchBrowser) { 'stage renamed file #group3': function (browser: NightwatchBrowser) {
browser browser
.clickLaunchIcon('dgit') .clickLaunchIcon('dgit')
.pause(3000)
.waitForElementVisible({ .waitForElementVisible({
selector: "//*[@data-status='deleted-unstaged' and @data-file='/test.txt']", selector: "//*[@data-status='deleted-unstaged' and @data-file='/test.txt']",
locateStrategy: 'xpath' locateStrategy: 'xpath'
@ -228,6 +230,7 @@ module.exports = {
'create a branch #group2': function (browser: NightwatchBrowser) { 'create a branch #group2': function (browser: NightwatchBrowser) {
browser browser
.clickLaunchIcon('dgit') .clickLaunchIcon('dgit')
.pause(3000)
.click('*[data-id="branches-panel"]') .click('*[data-id="branches-panel"]')
.waitForElementVisible('*[data-id="newbranchname"]') .waitForElementVisible('*[data-id="newbranchname"]')
.setValue('*[data-id="newbranchname"]', 'testbranch') .setValue('*[data-id="newbranchname"]', 'testbranch')
@ -244,6 +247,7 @@ module.exports = {
'publish the branch #group2': function (browser: NightwatchBrowser) { 'publish the branch #group2': function (browser: NightwatchBrowser) {
browser browser
.clickLaunchIcon('dgit') .clickLaunchIcon('dgit')
.pause(3000)
.waitForElementVisible('*[data-id="sourcecontrol-panel"]') .waitForElementVisible('*[data-id="sourcecontrol-panel"]')
.click('*[data-id="sourcecontrol-panel"]') .click('*[data-id="sourcecontrol-panel"]')
.pause(1000) .pause(1000)

@ -3,13 +3,13 @@
set -e set -e
SHA=`git rev-parse --short --verify HEAD` SHA=`git rev-parse --short --verify HEAD`
cd dist/apps/remix-ide
# this gh action is used to deploy the build to the gh pages # this gh action is used to deploy the build to the gh pages
mkdir dist/apps/remix-ide/.github mkdir dist/apps/remix-ide/.github
mkdir dist/apps/remix-ide/.github/workflows mkdir dist/apps/remix-ide/.github/workflows
cp apps/remix-ide/ci/gh-actions-deploy.yml dist/apps/remix-ide/.github/workflows cp apps/remix-ide/ci/gh-actions-deploy.yml dist/apps/remix-ide/.github/workflows
cd dist/apps/remix-ide
git init git init
git checkout -b gh-pages git checkout -b gh-pages
git config user.name "$COMMIT_AUTHOR" git config user.name "$COMMIT_AUTHOR"

@ -3,13 +3,13 @@
set -e set -e
SHA=`git rev-parse --short --verify HEAD` SHA=`git rev-parse --short --verify HEAD`
cd dist/apps/remix-ide
# this gh action is used to deploy the build to the gh pages # this gh action is used to deploy the build to the gh pages
mkdir dist/apps/remix-ide/.github mkdir dist/apps/remix-ide/.github
mkdir dist/apps/remix-ide/.github/workflows mkdir dist/apps/remix-ide/.github/workflows
cp apps/remix-ide/ci/gh-actions-deploy.yml dist/apps/remix-ide/.github/workflows cp apps/remix-ide/ci/gh-actions-deploy.yml dist/apps/remix-ide/.github/workflows
cd dist/apps/remix-ide
git init git init
git checkout -b gh-pages git checkout -b gh-pages
git config user.name "$COMMIT_AUTHOR" git config user.name "$COMMIT_AUTHOR"

@ -3,13 +3,13 @@
set -e set -e
SHA=`git rev-parse --short --verify HEAD` SHA=`git rev-parse --short --verify HEAD`
cd dist/apps/remix-ide
# this gh action is used to deploy the build to the gh pages # this gh action is used to deploy the build to the gh pages
mkdir dist/apps/remix-ide/.github mkdir dist/apps/remix-ide/.github
mkdir dist/apps/remix-ide/.github/workflows mkdir dist/apps/remix-ide/.github/workflows
cp apps/remix-ide/ci/gh-actions-deploy.yml dist/apps/remix-ide/.github/workflows/gh-actions-deploy.yml cp apps/remix-ide/ci/gh-actions-deploy.yml dist/apps/remix-ide/.github/workflows/gh-actions-deploy.yml
cd dist/apps/remix-ide
git init git init
git checkout -b gh-pages git checkout -b gh-pages
git config user.name "$COMMIT_AUTHOR" git config user.name "$COMMIT_AUTHOR"

@ -40,6 +40,7 @@ import {HardhatProvider} from './app/providers/hardhat-provider'
import {GanacheProvider} from './app/providers/ganache-provider' import {GanacheProvider} from './app/providers/ganache-provider'
import {FoundryProvider} from './app/providers/foundry-provider' import {FoundryProvider} from './app/providers/foundry-provider'
import {ExternalHttpProvider} from './app/providers/external-http-provider' import {ExternalHttpProvider} from './app/providers/external-http-provider'
import { EnvironmentExplorer } from './app/providers/environment-explorer'
import { FileDecorator } from './app/plugins/file-decorator' import { FileDecorator } from './app/plugins/file-decorator'
import { CodeFormat } from './app/plugins/code-format' import { CodeFormat } from './app/plugins/code-format'
import { SolidityUmlGen } from './app/plugins/solidity-umlgen' import { SolidityUmlGen } from './app/plugins/solidity-umlgen'
@ -276,6 +277,8 @@ class AppComponent {
const ganacheProvider = new GanacheProvider(blockchain) const ganacheProvider = new GanacheProvider(blockchain)
const foundryProvider = new FoundryProvider(blockchain) const foundryProvider = new FoundryProvider(blockchain)
const externalHttpProvider = new ExternalHttpProvider(blockchain) const externalHttpProvider = new ExternalHttpProvider(blockchain)
const environmentExplorer = new EnvironmentExplorer()
// ----------------- convert offset to line/column service ----------- // ----------------- convert offset to line/column service -----------
const offsetToLineColumnConverter = new OffsetToLineColumnConverter() const offsetToLineColumnConverter = new OffsetToLineColumnConverter()
Registry.getInstance().put({ Registry.getInstance().put({
@ -350,6 +353,7 @@ class AppComponent {
ganacheProvider, ganacheProvider,
foundryProvider, foundryProvider,
externalHttpProvider, externalHttpProvider,
environmentExplorer,
this.walkthroughService, this.walkthroughService,
search, search,
solidityumlgen, solidityumlgen,
@ -505,7 +509,7 @@ class AppComponent {
]) ])
await this.appManager.activatePlugin(['settings']) await this.appManager.activatePlugin(['settings'])
await this.appManager.activatePlugin(['walkthrough', 'storage', 'search', 'compileAndRun', 'recorder', 'dgit']) await this.appManager.activatePlugin(['walkthrough', 'storage', 'search', 'compileAndRun', 'recorder', 'dgitApi', 'dgit'])
await this.appManager.activatePlugin(['solidity-script', 'remix-templates']) await this.appManager.activatePlugin(['solidity-script', 'remix-templates'])
if (isElectron()){ if (isElectron()){

@ -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', name: 'blockchain',
displayName: 'Blockchain', displayName: 'Blockchain',
description: 'Blockchain - Logic', 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 version: packageJson.version
} }
@ -44,6 +44,21 @@ export type Transaction = {
timestamp?: number 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 { export class Blockchain extends Plugin {
active: boolean active: boolean
event: EventManager event: EventManager
@ -62,6 +77,7 @@ export class Blockchain extends Plugin {
providers: {[key: string]: VMProvider | InjectedProvider | NodeProvider} providers: {[key: string]: VMProvider | InjectedProvider | NodeProvider}
transactionContextAPI: TransactionContextAPI transactionContextAPI: TransactionContextAPI
registeredPluginEvents: string[] registeredPluginEvents: string[]
pinnedProviders: string[]
// NOTE: the config object will need to be refactored out in remix-lib // NOTE: the config object will need to be refactored out in remix-lib
constructor(config: Config) { constructor(config: Config) {
@ -93,6 +109,7 @@ export class Blockchain extends Plugin {
this.networkcallid = 0 this.networkcallid = 0
this.networkStatus = { network: { name: ' - ', id: ' - ' } } this.networkStatus = { network: { name: ' - ', id: ' - ' } }
this.registeredPluginEvents = [] 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.setupEvents()
this.setupProviders() 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() { onDeactivation() {
@ -136,12 +161,12 @@ export class Blockchain extends Plugin {
}) })
}) })
this.executionContext.event.register('addProvider', (network) => { this.executionContext.event.register('providerAdded', (network) => {
this._triggerEvent('addProvider', [network]) this._triggerEvent('providerAdded', [network])
}) })
this.executionContext.event.register('removeProvider', (name) => { this.executionContext.event.register('providerRemoved', (name) => {
this._triggerEvent('removeProvider', [name]) this._triggerEvent('providerRemoved', [name])
}) })
setInterval(() => { setInterval(() => {
@ -504,7 +529,11 @@ export class Blockchain extends Plugin {
} }
changeExecutionContext(context, confirmCb, infoCb, cb) { 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) { detectNetwork(cb) {
@ -611,7 +640,8 @@ export class Blockchain extends Plugin {
this.executionContext.listenOnLastBlock() this.executionContext.listenOnLastBlock()
} }
addProvider(provider) { addProvider(provider: Provider) {
if (this.pinnedProviders.includes(provider.name)) this.emit('shouldAddProvidertoUdapp', provider.name, provider)
this.executionContext.addProvider(provider) this.executionContext.addProvider(provider)
} }
@ -619,6 +649,14 @@ export class Blockchain extends Plugin {
this.executionContext.removeProvider(name) this.executionContext.removeProvider(name)
} }
getAllProviders() {
return this.executionContext.getAllProviders()
}
getPinnedProviders() {
return this.pinnedProviders
}
// TODO : event should be triggered by Udapp instead of TxListener // 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) */ /** Listen on New Transaction. (Cannot be done inside constructor because txlistener doesn't exist yet) */
startListening(txlistener) { startListening(txlistener) {

@ -123,10 +123,13 @@ export class ExecutionContext {
addProvider (network) { addProvider (network) {
if (network && network.name && !this.customNetWorks[network.name]) { if (network && network.name && !this.customNetWorks[network.name]) {
this.customNetWorks[network.name] = network this.customNetWorks[network.name] = network
this.event.trigger('addProvider', [network])
} }
} }
getAllProviders () {
return this.customNetWorks
}
internalWeb3 () { internalWeb3 () {
return web3 return web3
} }

@ -82,11 +82,10 @@ let requiredModules = [ // services + layout views + system views
'pinnedPanel', 'pinnedPanel',
'pluginStateLogger', 'pluginStateLogger',
'remixGuide', 'remixGuide',
'matomo' 'matomo',
'walletconnect'
] ]
// dependentModules shouldn't be manually activated (e.g hardhat is activated by remixd) // dependentModules shouldn't be manually activated (e.g hardhat is activated by remixd)
const dependentModules = ['foundry', 'hardhat', 'truffle', 'slither'] const dependentModules = ['foundry', 'hardhat', 'truffle', 'slither']
@ -133,7 +132,7 @@ export function isNative(name) {
'circuit-compiler', 'circuit-compiler',
'compilationDetails', 'compilationDetails',
'vyperCompilationDetails', 'vyperCompilationDetails',
'remixGuide', //'remixGuide',
'walletconnect' 'walletconnect'
] ]
return nativePlugins.includes(name) || requiredModules.includes(name) || isInjectedProvider(name) || isVM(name) return nativePlugins.includes(name) || requiredModules.includes(name) || isInjectedProvider(name) || isVM(name)
@ -389,7 +388,16 @@ class PluginLoader {
constructor() { constructor() {
const queryParams = new QueryParams() 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 = {}
this.loaders.localStorage = { this.loaders.localStorage = {
set: (plugin, actives) => { set: (plugin, actives) => {

@ -0,0 +1,13 @@
import { IFilePanel } from '@remixproject/plugin-api'
import { StatusEvents } from '@remixproject/plugin-utils'
export interface ILayoutApi {
events:{
} & StatusEvents
methods: {
maximisePinnedPanel: () => void
maximiseSidePanel: () => void
resetPinnedPanel: () => void
resetSidePanel: () => void
}
}

@ -0,0 +1,11 @@
import { IFilePanel } from '@remixproject/plugin-api'
import { StatusEvents } from '@remixproject/plugin-utils'
export interface IPinnedPanelApi {
events:{
} & StatusEvents
methods: {
currentFocus(): Promise<string>
}
}

@ -0,0 +1,11 @@
import { IFilePanel } from '@remixproject/plugin-api'
import { StatusEvents } from '@remixproject/plugin-utils'
export interface ISidePanelApi {
events:{
focusChanged: (name: string) => void;
} & StatusEvents
methods: {
}
}

@ -8,6 +8,9 @@ import { INotificationApi } from "./plugins/notification-api"
import { ISettings } from "./plugins/settings-api" import { ISettings } from "./plugins/settings-api"
import { IFilePanelApi } from "./plugins/filePanel-api" import { IFilePanelApi } from "./plugins/filePanel-api"
import { Plugin } from "@remixproject/engine" import { Plugin } from "@remixproject/engine"
import { ISidePanelApi } from "./plugins/sidePanel-api"
import { IPinnedPanelApi } from "./plugins/pinned-panel-api"
import { ILayoutApi } from "./plugins/layout-api"
export interface ICustomRemixApi extends IRemixApi { export interface ICustomRemixApi extends IRemixApi {
dgitApi: IGitApi dgitApi: IGitApi
@ -17,6 +20,9 @@ export interface ICustomRemixApi extends IRemixApi {
fileDecorator: IFileDecoratorApi fileDecorator: IFileDecoratorApi
fileManager: IExtendedFileSystem fileManager: IExtendedFileSystem
filePanel: IFilePanelApi filePanel: IFilePanelApi
sidePanel: ISidePanelApi
pinnedPanel: IPinnedPanelApi
layout: ILayoutApi
} }
export declare type CustomRemixApi = Readonly<ICustomRemixApi> export declare type CustomRemixApi = Readonly<ICustomRemixApi>

@ -699,6 +699,7 @@ export const EditorUI = (props: EditorUIProps) => {
} }
props.plugin.call('notification', 'alert', modalContent) props.plugin.call('notification', 'alert', modalContent)
pasteCodeRef.current = true pasteCodeRef.current = true
_paq.push(['trackEvent', 'editor', 'onDidPaste', 'more_than_10_lines'])
} }
}) })

@ -46,9 +46,9 @@ export const BranchHeader = () => {
const getName = () => { const getName = () => {
const url = context.currentBranch?.remote?.url const url = context.currentBranch?.remote?.url
if (!url) return if (!url) return
const regex = /https:\/\/github\.com\/[^/]+\/([^/]+)\.git/ const regex = /https:\/\/github\.com\/[^/]+\/([^/]+)(?:\.git)?/;
const match = url.match(regex) const match = url.match(regex)
return match ? match[1] : 'Couldn\'t get repo name!' return match ? match[1] : null
} }
const showDetachedWarningText = async () => { const showDetachedWarningText = async () => {
@ -60,52 +60,37 @@ export const BranchHeader = () => {
const Heading = () => { const Heading = () => {
return ( return (
<div className="container-fluid px-3"> <div className="container-fluid px-0">
<div className="d-flex flex-column pt-1 mb-1"> <div className="d-flex flex-column pt-1 mb-1">
<div className="d-flex flex-column justify-content-start align-items-start"> <div className="d-flex flex-column justify-content-start align-items-start w-100">
{getName() !== "Couldn't get repo name!" ? ( {getName() ? (
<div className="pr-1 m-0"> <span className={`text-truncate overflow-hidden whitespace-nowrap w-100`}>
<span className="col-4 px-0">Repository Name:</span> {getName() ?? ''}
<span className="" style={{ width: '15rem' }}> </span>
<span className={`${ changed ? 'text-danger pl-2 text-truncate overflow-hidden whitespace-nowrap ml-4' : "text-secondary pl-2 text-truncate overflow-hidden whitespace-nowrap ml-4" }`}>
{getName() ?? ''}
</span>
</span>
</div>
) : null ) : null
} }
<div className="pr-1 m-0"> {context.currentBranch && context.currentBranch.name ?
<span className="col-4 px-0">Branch Name:</span> <span className="text-secondary text-truncate overflow-hidden whitespace-nowrap w-100">
<span className="pl-2 text-secondary text-truncate overflow-hidden whitespace-nowrap ml-4"> <i className="fa fa-code-branch mr-1"></i>{context.currentBranch && context.currentBranch.name}{changed?'*':''}
<span className={`${changed ? 'text-danger pl-2' : "pl-2"}`}> </span> : null}
<i className="fa fa-code-branch mr-1 pl-2"></i> {(latestCommit && latestCommit.commit && latestCommit.commit.message) ?
{context.currentBranch && context.currentBranch.name} <span className="text-secondary text-truncate overflow-hidden whitespace-nowrap w-100">
</span> {latestCommit ?
latestCommit.commit && latestCommit.commit.message ? `"${latestCommit.commit.message}"` : '' : null}
</span>
: null}
{isDetached ?
<span className="text-secondary text-truncate overflow-hidden whitespace-nowrap w-100">
{isDetached ?
<>You are in a detached state<i onClick={showDetachedWarningText} className="btn fa fa-info-circle mr-1"></i></> : null}
</span> </span>
</div> : null}
{context.storage.enabled ? {context.storage.enabled ?
<div className="d-flex"> <span className="text-secondary text-sm text-truncate overflow-hidden whitespace-nowrap w-100">
<span className="d-flex justify-between align-items-center" style={{ width: '15rem' }}> {context.storage.used} MB used
<span className="col-4 px-0">Storage :</span> ({context.storage.percentUsed} %)
<span className="text-secondary text-sm text-truncate overflow-hidden whitespace-nowrap ml-4">
{context.storage.used} MB used
({context.storage.percentUsed} %)
</span>
</span>
</div> : null}
<div className="d-flex flex-row">
<span className="d-flex justify-between align-items-center" style={{ width: '15rem' }}>
<span className="col-4 px-0">Messages :</span>
<span className="text-truncate overflow-hidden" >
<span className="text-secondary text-truncate overflow-hidden whitespace-nowrap ml-4">
{latestCommit ?
latestCommit.commit && latestCommit.commit.message ? latestCommit.commit.message : '' : null}
{isDetached ?
<>You are in a detached state<i onClick={showDetachedWarningText} className="btn fa fa-info-circle mr-1 pl-2"></i></>: null}
</span>
</span>
</span> </span>
</div> : null}
</div> </div>
</div> </div>
</div> </div>

@ -9,6 +9,7 @@ import { TokenWarning } from '../panels/tokenWarning';
interface RepositorySelectProps { interface RepositorySelectProps {
select: (repo: repository) => void; select: (repo: repository) => void;
title: string;
} }
const RepositorySelect = (props: RepositorySelectProps) => { const RepositorySelect = (props: RepositorySelectProps) => {
@ -65,7 +66,7 @@ const RepositorySelect = (props: RepositorySelectProps) => {
return ( return (
<><button data-id='fetch-repositories' onClick={fetchRepositories} className="w-100 mt-1 btn btn-secondary mb-2"> <><button data-id='fetch-repositories' onClick={fetchRepositories} className="w-100 mt-1 btn btn-secondary mb-2">
<i className="fab fa-github mr-1"></i>Fetch Repositories from GitHub <i className="fab fa-github mr-1"></i>{props.title}
</button> </button>
{ {
show ? show ?

@ -43,7 +43,7 @@ export const SelectAndCloneRepositories = (props: RepositoriesProps) => {
return ( return (
<> <>
<RepositorySelect select={selectRepo} /> <RepositorySelect title="Clone from GitHub" select={selectRepo} />
<TokenWarning /> <TokenWarning />
{ repo && <BranchSelect select={selectRemoteBranch} /> } { repo && <BranchSelect select={selectRemoteBranch} /> }

@ -164,7 +164,7 @@ export const GitUI = (props: IGitUi) => {
return ( return (
<>{(!gitState.canUseApp) ? <Disabled></Disabled> : <>{(!gitState.canUseApp) ? <Disabled></Disabled> :
<div className="m-1"> <div className="px-3">
<gitPluginContext.Provider value={gitState}> <gitPluginContext.Provider value={gitState}>
<loaderContext.Provider value={loaderState}> <loaderContext.Provider value={loaderState}>
<gitActionsContext.Provider value={gitActionsProviderValue}> <gitActionsContext.Provider value={gitActionsProviderValue}>

@ -64,7 +64,7 @@ export const RemotesImport = () => {
return ( return (
<> <>
<RepositorySelect select={selectRepo} /> <RepositorySelect title="Add from GitHub" select={selectRepo} />
<TokenWarning /> <TokenWarning />
{repo ? {repo ?
<input data-id='remote-panel-remotename' placeholder="remote name" name='remotename' onChange={e => onRemoteNameChange(e.target.value)} value={remoteName} className="form-control mb-2" type="text" id="remotename" /> <input data-id='remote-panel-remotename' placeholder="remote name" name='remotename' onChange={e => onRemoteNameChange(e.target.value)} value={remoteName} className="form-control mb-2" type="text" id="remotename" />

@ -164,6 +164,23 @@ export const setCallBacks = (viewPlugin: Plugin, gitDispatcher: React.Dispatch<g
setAtivePanel(panelNumber) setAtivePanel(panelNumber)
}) })
plugin.on('sidePanel', 'focusChanged', async (name: string) => {
const pinnedPlugin = await plugin.call('pinnedPanel', 'currentFocus')
if (name == 'dgit') {
if (pinnedPlugin === 'dgit') {
plugin.call('layout', 'maximisePinnedPanel')
} else {
plugin.call('layout', 'maximiseSidePanel')
}
} else {
if (pinnedPlugin === 'dgit') {
plugin.call('layout', 'resetPinnedPanel')
} else {
plugin.call('layout', 'resetSidePanel')
}
}
})
callBackEnabled = true; callBackEnabled = true;
} }
@ -223,12 +240,6 @@ const calculateLocalStorage = async () => {
const quotaMB = bytesToMB(estimate.quota); const quotaMB = bytesToMB(estimate.quota);
const availableMB = bytesToMB(estimate.quota - estimate.usage); const availableMB = bytesToMB(estimate.quota - estimate.usage);
const percentageUsed = calculatePercentage(estimate.usage, estimate.quota); 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 = { storage = {
used: usedMB, used: usedMB,
total: quotaMB, total: quotaMB,

@ -23,12 +23,13 @@
.remixui_grid_cell_pin:focus { .remixui_grid_cell_pin:focus {
outline: none; outline: none;
} }
.remixui_grid_cell_pin { .remixui_grid_cell_pin {
width: 1rem; width: 1rem;
height: 1rem; height: 1rem;
position: relative; position: relative;
right: 1rem; right: 1rem;
top: -0.5rem; top: -0.8rem;
background: transparent; background: transparent;
} }
@ -41,7 +42,7 @@
.remixui_grid_cell_tags_no_pin { .remixui_grid_cell_tags_no_pin {
position: relative; position: relative;
right: 0rem; right: 0rem;
top: 0.1rem; top: -6.5rem;
} }
.remixui_grid_cell_tag { .remixui_grid_cell_tag {

@ -34,8 +34,8 @@ export const RemixUIGridCell = (props: RemixUIGridCellProps) => {
useEffect(() => { useEffect(() => {
if (props.tagList) setAnyEnabled(props.tagList.some((key) => filterCon.keyValueMap[key]?.enabled)) if (props.tagList) setAnyEnabled(props.tagList.some((key) => filterCon.keyValueMap[key]?.enabled))
else setAnyEnabled(filterCon?.keyValueMap['no tag']?.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)) if (filterCon.filter != '') setAnyEnabled(anyEnabled && props.title.toLowerCase().includes(filterCon.filter))
console.log("pin ", pinned)
}, [filterCon, props.tagList]) }, [filterCon, props.tagList])
@ -54,12 +54,12 @@ export const RemixUIGridCell = (props: RemixUIGridCellProps) => {
*/ */
return ( return (
<div className='mr-2 mt-3' onClick={() => { <div data-values='gridCell' className='' onClick={() => {
if (props.expandViewEl) if (props.expandViewEl)
props.handleExpand(!expand) props.handleExpand(!expand)
else return 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 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 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"> <div className="d-flex remixui_grid_cell flex-column">
@ -77,9 +77,10 @@ export const RemixUIGridCell = (props: RemixUIGridCellProps) => {
</div> </div>
{ filterCon.showPin && <button { filterCon.showPin && <button
className={`${pinned ? 'fa-duotone' : 'fa-light'}` + ` fa-map-pin text-info border-0 mb-0 remixui_grid_cell_pin`} className={`${pinned ? 'fa-duotone' : 'fa-light'}` + ` fa-map-pin text-info border-0 mb-0 remixui_grid_cell_pin`}
onClick={() => { style={{ fontSize: 'large' }}
setPinned(!pinned) onClick={async () => {
props.pinStateCallback() if (!props.pinStateCallback) setPinned(!pinned)
if (await props.pinStateCallback(!pinned)) setPinned(!pinned)
}} }}
></button>} ></button>}
{ props.tagList && <div className={`d-flex flex-column align-items-begin ` +`${filterCon.showPin ? 'remixui_grid_cell_tags' : 'remixui_grid_cell_tags_no_pin'}`}> { 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 React, {useState, useEffect, useContext, useRef, ReactNode} from 'react' // eslint-disable-line
import './remix-ui-grid-section.css' import './remix-ui-grid-section.css'
import FiltersContext from "./filtersContext"
declare global { declare global {
interface Window { interface Window {
@ -19,7 +20,44 @@ interface RemixUIGridSectionProps {
expandedCell?: any expandedCell?: any
} }
const hasChildCell = (children: ReactNode): boolean => {
return true
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) => { export const RemixUIGridSection = (props: RemixUIGridSectionProps) => {
const [children, setChildren] = useState(props.children)
const filterCon = useContext(FiltersContext)
useEffect(() => {
setChildren(props.children)
}, [props.children])
return ( return (
<div <div
className={`d-flex px-4 py-2 flex-column w-100 remixui_grid_section_container ${props.classList}`} className={`d-flex px-4 py-2 flex-column w-100 remixui_grid_section_container ${props.classList}`}
@ -29,6 +67,7 @@ export const RemixUIGridSection = (props: RemixUIGridSectionProps) => {
<div className="d-flex flex-column w-100 remixui_grid_section"> <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> } { 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`}> <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 } { props.children }
</div> </div>
{ props.expandedCell && <div> { props.expandedCell && <div>

@ -51,7 +51,6 @@ export const RemixUIGridView = (props: RemixUIGridViewProps) => {
searchInputRef.current.value = '' searchInputRef.current.value = ''
} else { } else {
setState((prevState) => { setState((prevState) => {
console.log("update filter", searchInputRef.current.value)
return { return {
...prevState, ...prevState,
searchDisable: searchInputRef.current.value === '', searchDisable: searchInputRef.current.value === '',
@ -120,7 +119,7 @@ export const RemixUIGridView = (props: RemixUIGridViewProps) => {
ref={searchInputRef} ref={searchInputRef}
type="text" type="text"
style={{ minWidth: '100px' }} style={{ minWidth: '100px' }}
className="border form-control border-right-0 mr-4" className="border form-control mr-4"
id="GVFilterInput" id="GVFilterInput"
placeholder={"Filter the list"} placeholder={"Filter the list"}
data-id="RemixGVFilterInput" 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)) 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' import { CustomMenu, CustomToggle, CustomTooltip } from '@remix-ui/helper'
export function EnvironmentUI(props: EnvironmentProps) { 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 handleChangeExEnv = (env: string) => {
const provider = props.providers.providerList.find((exEnv) => exEnv.name === env) const provider = props.providers.providerList.find((exEnv) => exEnv.name === env)
const context = provider.name const context = provider.name
@ -23,7 +28,6 @@ export function EnvironmentUI(props: EnvironmentProps) {
<div className="udapp_crow"> <div className="udapp_crow">
<label id="selectExEnv" className="udapp_settingsLabel"> <label id="selectExEnv" className="udapp_settingsLabel">
<FormattedMessage id="udapp.environment" /> <FormattedMessage id="udapp.environment" />
<CustomTooltip placement={'auto-end'} tooltipClasses="text-nowrap" tooltipId="info-recorder" tooltipText={<FormattedMessage id="udapp.tooltipText2" />}> <CustomTooltip placement={'auto-end'} tooltipClasses="text-nowrap" tooltipId="info-recorder" tooltipText={<FormattedMessage id="udapp.tooltipText2" />}>
<a href="https://chainlist.org/" target="_blank"> <a href="https://chainlist.org/" target="_blank">
<i className={'ml-2 fas fa-plug'} aria-hidden="true"></i> <i className={'ml-2 fas fa-plug'} aria-hidden="true"></i>
@ -54,9 +58,42 @@ export function EnvironmentUI(props: EnvironmentProps) {
)} )}
</Dropdown.Toggle> </Dropdown.Toggle>
<Dropdown.Menu as={CustomMenu} className="w-100 custom-dropdown-items" data-id="custom-dropdown-items"> <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 <Dropdown.Item
key={index} key={name}
onClick={() => { onClick={() => {
handleChangeExEnv(name) handleChangeExEnv(name)
}} }}
@ -68,6 +105,18 @@ export function EnvironmentUI(props: EnvironmentProps) {
</span> </span>
</Dropdown.Item> </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.Menu>
</Dropdown> </Dropdown>
</div> </div>

@ -0,0 +1,17 @@
import React, { createContext } from 'react'
import { defaultStatusBarContext, StatusBarContextType, StatusBarInterface } from '../lib/types'
export const StatusBarContext = createContext<StatusBarContextType>(defaultStatusBarContext)
export function StatusBarContextProvider ({ children }) {
const statusBarProviderValues = {
test: true
}
return (
<>
<StatusBarContext.Provider value={statusBarProviderValues}>
{children}
</StatusBarContext.Provider>
</>
)
}

@ -14,6 +14,11 @@
cursor: pointer; cursor: pointer;
} }
.remixui_statusbar_aistatusdisabled {
text-decoration: line-through;
text-decoration-thickness: 3px;
}
/** /**
* approximately same height with vscode statusbar * approximately same height with vscode statusbar
**/ **/
@ -27,3 +32,11 @@
.remixui_statusbar_activelink:active { .remixui_statusbar_activelink:active {
color: var(--danger); color: var(--danger);
} }
.remixui_statusbar_didyouknow {
}
.remixui_statusbar_custom_padding {
padding: 0.421em;
}

@ -33,11 +33,12 @@ export default function AIStatus(props: AIStatusProps) {
}, [props.plugin.isAiActive]) }, [props.plugin.isAiActive])
return ( return (
<CustomTooltip <CustomTooltip
tooltipText={copilotActive ? "Remix Copilot activated" : "Remix Copilot disabled."} tooltipText={copilotActive ? "Remix Copilot activated" : "Remix Copilot disabled. To activate copilot, open a .sol file and toggle the ai switch at the top of the Ide"}
> >
<div className="d-flex flex-row pr-2 text-white justify-content-center align-items-center remixui_statusbar_aistatus"> <div className="d-flex flex-row pr-2 text-white justify-content-center align-items-center">
<span className={copilotActive === false ? "fa-regular fa-microchip-ai ml-1 text-white" : "fa-regular fa-microchip-ai ml-1"}></span> <span className={copilotActive === false ? "small mx-1 text-white semi-bold" : "small mx-1 text-white semi-bold" }>
<span className={copilotActive === false ? "small mx-1 text-white semi-bold" : "small mx-1 semi-bold" }>Remix Copilot</span> {copilotActive === false ? 'Remix Copilot (disabled)' : 'Remix Copilot (enabled)'}
</span>
</div> </div>
</CustomTooltip> </CustomTooltip>
) )

@ -0,0 +1,35 @@
import { CustomTooltip } from '@remix-ui/helper'
import axios from 'axios'
import React, { useEffect, useState } from 'react'
export default function DidYouKnow () {
const [tip, setTip] = useState<string>('')
useEffect(() => {
const abortController = new AbortController()
const signal = abortController.signal
async function showRemixTips() {
const response = await axios.get('https://raw.githubusercontent.com/remix-project-org/remix-dynamics/main/ide/tips.json', { signal })
if (signal.aborted) return
const tips = response.data
const index = Math.floor(Math.random() * (tips.length - 1))
setTip(tips[index])
}
try {
showRemixTips()
} catch (e) {
console.log(e)
}
return () => {
abortController.abort()
}
}, [])
return (
<CustomTooltip tooltipText={'Did you know'}>
<div className="remixui_statusbar_didyouknow text-white small d-flex align-items-center">
<span className="pr-2 text-success fa-solid fa-lightbulb"></span>
<div className="mr-2">Did you know?</div>
{ tip && tip.length > 0 ? <div>{tip}</div> : null }
</div>
</CustomTooltip>
)
}

@ -52,10 +52,11 @@ export default function GitStatus({ plugin, gitBranchName, setGitBranchName }: G
} }
const initializeNewGitRepo = async () => { const initializeNewGitRepo = async () => {
await plugin.call('dgitApi', 'init')
const isActive = await plugin.call('manager', 'isActive', 'dgit')
if (isLocalHost === false) { if (isLocalHost === false) {
const isActive = await plugin.call('manager', 'isActive', 'dgit')
if (!isActive) await plugin.call('manager', 'activatePlugin', 'dgit') if (!isActive) await plugin.call('manager', 'activatePlugin', 'dgit')
await plugin.call('dgitApi', 'init')
;(window as any)._paq.push('trackEvent', 'statusbar', 'initNewRepo')
} }
} }

@ -15,7 +15,7 @@ export default function ScamAlertStatus ({ refs, getReferenceProps }: ScamAlertS
<CustomTooltip <CustomTooltip
tooltipText={"Scam Alerts"} tooltipText={"Scam Alerts"}
> >
<div className="mr-2 d-flex align-items-center justify-content-center remixui_statusbar_scamAlert" id="hTScamAlertSection" ref={refs.setReference} {...getReferenceProps()}> <div className="mr-1 d-flex align-items-center justify-content-center remixui_statusbar_scamAlert" id="hTScamAlertSection" ref={refs.setReference} {...getReferenceProps()}>
<span className="pr-2 far fa-exclamation-triangle text-white"></span> <span className="pr-2 far fa-exclamation-triangle text-white"></span>
<span className="text-white font-semibold small"> <span className="text-white font-semibold small">
<FormattedMessage id="home.scamAlert" /> <FormattedMessage id="home.scamAlert" />

@ -19,12 +19,12 @@ export default function ScamDetails ({ refs, floatStyle, scamAlerts }: ScamDetai
<div <div
ref={refs.setFloating} ref={refs.setFloating}
style={ floatStyle } style={ floatStyle }
className="px-1 ml-1 mb-1 d-flex w-25 alert alert-danger border border-danger" className="px-1 ml-1 mb-1 d-flex w-25 alert alert-warning border border-warning"
> >
<span className="align-self-center pl-4 mt-1"> <span className="align-self-center pl-4 mt-1">
<i style={{ fontSize: 'xxx-large', fontWeight: 'lighter' }} className="pr-2 far text-danger fa-exclamation-triangle"></i> <i style={{ fontSize: 'xxx-large', fontWeight: 'lighter' }} className="pr-2 far text-warning fa-exclamation-triangle"></i>
</span> </span>
<div className="d-flex flex-column text-danger"> <div className="d-flex flex-column text-warning">
{scamAlerts && scamAlerts.map((alert, index) => ( {scamAlerts && scamAlerts.map((alert, index) => (
<span className="pl-4 mt-1" key={`${alert.url}${index}`}> <span className="pl-4 mt-1" key={`${alert.url}${index}`}>
{alert.url.length < 1 ? <FormattedMessage id={`home.scamAlertText${index + 1}`} defaultMessage={alert.message} /> {alert.url.length < 1 ? <FormattedMessage id={`home.scamAlertText${index + 1}`} defaultMessage={alert.message} />

@ -7,6 +7,8 @@ import { FloatingFocusManager, autoUpdate, flip, offset, shift, size, useClick,
import axios from 'axios' import axios from 'axios'
// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries // eslint-disable-next-line @nrwl/nx/enforce-module-boundaries
import { StatusBar } from 'apps/remix-ide/src/app/components/status-bar' import { StatusBar } from 'apps/remix-ide/src/app/components/status-bar'
import { StatusBarContextProvider } from '../contexts/statusbarcontext'
import DidYouKnow from './components/didYouKnow'
export interface RemixUIStatusBarProps { export interface RemixUIStatusBarProps {
statusBarPlugin: StatusBar statusBarPlugin: StatusBar
@ -18,7 +20,7 @@ export type ScamAlert = {
} }
export function RemixUIStatusBar({ statusBarPlugin }: RemixUIStatusBarProps) { export function RemixUIStatusBar({ statusBarPlugin }: RemixUIStatusBarProps) {
const [showScamDetails, setShowScamDetails] = useState(false) const [showScamDetails, setShowScamDetails] = useState(true)
const [scamAlerts, setScamAlerts] = useState<ScamAlert[]>([]) const [scamAlerts, setScamAlerts] = useState<ScamAlert[]>([])
const [gitBranchName, setGitBranchName] = useState('') const [gitBranchName, setGitBranchName] = useState('')
const [isAiActive, setIsAiActive] = useState(false) const [isAiActive, setIsAiActive] = useState(false)
@ -66,21 +68,31 @@ export function RemixUIStatusBar({ statusBarPlugin }: RemixUIStatusBarProps) {
return ( return (
<> <>
{showScamDetails && ( <StatusBarContextProvider>
<FloatingFocusManager context={context} modal={false}> {showScamDetails && (
<ScamDetails refs={refs} floatStyle={{ ...floatingStyles, minHeight: 'auto', alignContent: 'center', paddingRight: '0.5rem' }} getFloatingProps={getFloatingProps} scamAlerts={scamAlerts} /> <FloatingFocusManager context={context} modal={false}>
</FloatingFocusManager> <ScamDetails refs={refs} floatStyle={{ ...floatingStyles, minHeight: 'auto', alignContent: 'center', paddingRight: '0.5rem' }} getFloatingProps={getFloatingProps} scamAlerts={scamAlerts} />
)} </FloatingFocusManager>
<div className="d-flex remixui_statusbar_height flex-row bg-info justify-content-between align-items-center"> )}
<div className="remixui_statusbar remixui_statusbar_gitstatus"> <div className="d-flex remixui_statusbar_height flex-row bg-info justify-content-between align-items-center">
<GitStatus plugin={statusBarPlugin} gitBranchName={gitBranchName} setGitBranchName={setGitBranchName} /> <div className="remixui_statusbar remixui_statusbar_gitstatus">
<GitStatus plugin={statusBarPlugin} gitBranchName={gitBranchName} setGitBranchName={setGitBranchName} />
</div>
<div className="remixui_statusbar"></div>
<div className="remixui_statusbar">
<DidYouKnow />
</div>
<div className="remixui_statusbar"></div>
<div className="remixui_statusbar d-flex align-items-center p-0">
<div className="remixui_statusbar">
<AIStatus plugin={statusBarPlugin} aiActive={lightAiUp} isAiActive={isAiActive} setIsAiActive={setIsAiActive} />
</div>
<div className="remixui_statusbar bg-warning remixui_statusbar_custom_padding d-flex justify-center align-items-center">
<ScamAlertStatus refs={refs} getReferenceProps={getReferenceProps} />
</div>
</div>
</div> </div>
<div className="remixui_statusbar"></div> </StatusBarContextProvider>
<div className="remixui_statusbar d-flex flex-row">
<ScamAlertStatus refs={refs} getReferenceProps={getReferenceProps} />
<AIStatus plugin={statusBarPlugin} aiActive={lightAiUp} isAiActive={isAiActive} setIsAiActive={setIsAiActive} />
</div>
</div>
</> </>
) )
} }

@ -3,6 +3,7 @@ import { Plugin } from '@remixproject/engine'
import { FilePanelType } from '@remix-ui/workspace' import { FilePanelType } from '@remix-ui/workspace'
// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries // eslint-disable-next-line @nrwl/nx/enforce-module-boundaries
import { VerticalIcons } from 'apps/remix-ide/src/app/components/vertical-icons' import { VerticalIcons } from 'apps/remix-ide/src/app/components/vertical-icons'
import { CustomRemixApi } from '@remix-api'
export interface PluginProfile { export interface PluginProfile {
name: string name: string
displayName: string displayName: string
@ -15,7 +16,7 @@ export interface PluginProfile {
version?: string version?: string
} }
export interface StatusBarInterface extends Plugin { export interface StatusBarInterface extends Plugin<any, CustomRemixApi> {
htmlElement: HTMLDivElement htmlElement: HTMLDivElement
events: EventEmitter events: EventEmitter
dispatch: React.Dispatch<any> dispatch: React.Dispatch<any>
@ -25,3 +26,12 @@ export interface StatusBarInterface extends Plugin {
getGitBranchName: () => Promise<any> getGitBranchName: () => Promise<any>
currentWorkspaceName: string currentWorkspaceName: string
} }
export const defaultStatusBarContext: StatusBarContextType = {
test: false
}
export type StatusBarContextType = {
test: boolean
}

@ -1,6 +1,6 @@
import React, { SyntheticEvent, useContext, useEffect, useRef, useState } from 'react' import React, { SyntheticEvent, useContext, useEffect, useRef, useState } from 'react'
import { DragStructure, FileType, FlatTreeDropProps } from '../types' import { DragStructure, FileType, FlatTreeDropProps } from '../types'
import { buildMultiSelectedItemProfiles, getEventTarget } from '../utils/getEventTarget' import { getEventTarget } from '../utils/getEventTarget'
import { extractParentFromKey } from '@remix-ui/helper' import { extractParentFromKey } from '@remix-ui/helper'
import { FileSystemContext } from '../contexts' import { FileSystemContext } from '../contexts'

@ -5,7 +5,7 @@ import { getPathIcon } from '@remix-ui/helper';
import { Virtuoso, VirtuosoHandle } from 'react-virtuoso' import { Virtuoso, VirtuosoHandle } from 'react-virtuoso'
import { FlatTreeItemInput } from './flat-tree-item-input'; import { FlatTreeItemInput } from './flat-tree-item-input';
import { FlatTreeDrop } from './flat-tree-drop'; import { FlatTreeDrop } from './flat-tree-drop';
import { buildMultiSelectedItemProfiles, getEventTarget } from '../utils/getEventTarget'; import { getEventTarget } from '../utils/getEventTarget';
import { fileDecoration, FileDecorationIcons } from '@remix-ui/file-decorators'; import { fileDecoration, FileDecorationIcons } from '@remix-ui/file-decorators';
import { FileHoverIcons } from './file-explorer-hovericons'; import { FileHoverIcons } from './file-explorer-hovericons';
import { deletePath } from '../actions'; import { deletePath } from '../actions';
@ -75,6 +75,37 @@ export const FlatTree = (props: FlatTreeProps) => {
const virtuoso = useRef<VirtuosoHandle>(null) const virtuoso = useRef<VirtuosoHandle>(null)
const [selectedItems, setSelectedItems] = useState<DragStructure[]>([]) const [selectedItems, setSelectedItems] = useState<DragStructure[]>([])
/**
* When multiple files are selected in FileExplorer,
* and these files are dragged to a target folder,
* this function will build the profile of each selected item
* so they can be moved when dropped on target folder
* @param target - Initial target item in FileExplorer
* @returns - {DragStructure} Array of selected items
*/
const buildMultiSelectedItemProfiles = (target: {
path: string
type: string
content: string
position: {
top: number
left: number
}
}) => {
const selectItems = []
selectItems.push(target)
containerRef.current?.querySelectorAll('li.remixui_selected').forEach(item => {
const dragTarget = {
position: { top: target?.position.top || 0, left: target?.position.left || 0 },
path: item.getAttribute('data-path') || item.getAttribute('data-label-path') || '',
type: item.getAttribute('data-type') || item.getAttribute('data-label-type') || '',
content: item.textContent || ''
}
if (dragTarget.path !== target.path) selectItems.push(dragTarget)
})
setSelectedItems(selectItems)
}
const labelClass = (file: FileType) => const labelClass = (file: FileType) =>
props.focusEdit.element === file.path props.focusEdit.element === file.path
? 'bg-light' ? 'bg-light'
@ -116,9 +147,8 @@ export const FlatTree = (props: FlatTreeProps) => {
const target = await getEventTarget(event) const target = await getEventTarget(event)
setDragSource(flatTree.find((item) => item.path === target.path)) setDragSource(flatTree.find((item) => item.path === target.path))
setIsDragging(true) setIsDragging(true)
const items = buildMultiSelectedItemProfiles(target) buildMultiSelectedItemProfiles(target)
setSelectedItems(items) setFilesSelected(selectedItems.map((item) => item.path))
setFilesSelected(items.map((item) => item.path))
} }
useEffect(() => { useEffect(() => {

@ -23,34 +23,3 @@ export const getEventTarget = async (e: any, useLabel: boolean = false) => {
} }
} }
} }
/**
* When multiple files are selected in FileExplorer,
* and these files are dragged to a target folder,
* this function will build the profile of each selected item
* in FileExplorer so they can be moved when dropped
* @param target - Initial target item in FileExplorer
* @returns - {DragStructure} Array of selected items
*/
export const buildMultiSelectedItemProfiles = (target: {
path: string
type: string
content: string
position: {
top: number
left: number
}
}) => {
const selectItems = []
selectItems.push(target)
document.querySelectorAll('li.remixui_selected').forEach(item => {
const dragTarget = {
position: { top: target?.position.top || 0, left: target?.position.left || 0 },
path: item.getAttribute('data-path') || item.getAttribute('data-label-path') || '',
type: item.getAttribute('data-type') || item.getAttribute('data-label-type') || '',
content: item.textContent || ''
}
if (dragTarget.path !== target.path) selectItems.push(dragTarget)
})
return selectItems
}

@ -139,6 +139,7 @@ export class RemixURLResolver {
async handleNpmImport(url: string): Promise<HandlerResponse> { async handleNpmImport(url: string): Promise<HandlerResponse> {
if (!url) throw new Error('url is empty') if (!url) throw new Error('url is empty')
let fetchUrl = url
const isVersionned = semverRegex().exec(url.replace(/@/g, '@ ').replace(/\//g, ' /')) const isVersionned = semverRegex().exec(url.replace(/@/g, '@ ').replace(/\//g, ' /'))
if (this.getDependencies && !isVersionned) { if (this.getDependencies && !isVersionned) {
try { try {
@ -174,7 +175,7 @@ export class RemixURLResolver {
} }
if (version) { if (version) {
const versionSemver = semver.minVersion(version) const versionSemver = semver.minVersion(version)
url = url.replace(pkg, `${pkg}@${versionSemver.version}`) fetchUrl = url.replace(pkg, `${pkg}@${versionSemver.version}`)
} }
} }
} }
@ -189,7 +190,7 @@ export class RemixURLResolver {
// get response from all urls // get response from all urls
for (let i = 0; i < npm_urls.length; i++) { for (let i = 0; i < npm_urls.length; i++) {
try { try {
const req = npm_urls[i] + url const req = npm_urls[i] + fetchUrl
const response: AxiosResponse = await axios.get(req, { transformResponse: []}) const response: AxiosResponse = await axios.get(req, { transformResponse: []})
content = response.data content = response.data
break break

Loading…
Cancel
Save