Merge branch 'master' of https://github.com/ethereum/remix-project into errormarkers

pull/2774/head
bunsenstraat 3 years ago
commit e5ddec6050
  1. 69
      apps/remix-ide-e2e/src/tests/editor_line_text.test.ts
  2. 125
      apps/remix-ide-e2e/src/tests/file_decorator.test.ts
  3. 1
      apps/remix-ide-e2e/src/tests/publishContract.test.ts
  4. 34
      apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts
  5. 7
      apps/remix-ide/src/app.js
  6. 18
      apps/remix-ide/src/app/editor/editor.js
  7. 6
      apps/remix-ide/src/app/panels/tab-proxy.js
  8. 4
      apps/remix-ide/src/app/panels/terminal.js
  9. 86
      apps/remix-ide/src/app/plugins/file-decorator.ts
  10. 5
      apps/remix-ide/src/app/tabs/web3-provider.js
  11. 2
      apps/remix-ide/src/remixAppManager.js
  12. 36
      libs/remix-ui/editor/src/lib/remix-ui-editor.tsx
  13. 4
      libs/remix-ui/file-decorators/.babelrc
  14. 19
      libs/remix-ui/file-decorators/.eslintrc
  15. 3
      libs/remix-ui/file-decorators/src/index.ts
  16. 51
      libs/remix-ui/file-decorators/src/lib/components/file-decoration-icon.tsx
  17. 13
      libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-custom-icon.tsx
  18. 14
      libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-error-icon.tsx
  19. 33
      libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-tooltip.tsx
  20. 11
      libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-warning-icon.tsx
  21. 11
      libs/remix-ui/file-decorators/src/lib/helper/index.tsx
  22. 29
      libs/remix-ui/file-decorators/src/lib/types/index.ts
  23. 16
      libs/remix-ui/file-decorators/tsconfig.json
  24. 13
      libs/remix-ui/file-decorators/tsconfig.lib.json
  25. 9
      libs/remix-ui/publish-to-storage/src/lib/publish-to-storage.tsx
  26. 10
      libs/remix-ui/run-tab/src/lib/actions/deploy.ts
  27. 7
      libs/remix-ui/run-tab/src/lib/actions/index.ts
  28. 10
      libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx
  29. 1
      libs/remix-ui/run-tab/src/lib/reducers/runTab.ts
  30. 2
      libs/remix-ui/run-tab/src/lib/types/index.ts
  31. 77
      libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx
  32. 4
      libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx
  33. 1
      libs/remix-ui/workspace/src/index.ts
  34. 12
      libs/remix-ui/workspace/src/lib/actions/events.ts
  35. 7
      libs/remix-ui/workspace/src/lib/actions/payload.ts
  36. 3
      libs/remix-ui/workspace/src/lib/components/file-explorer.tsx
  37. 19
      libs/remix-ui/workspace/src/lib/components/file-label.tsx
  38. 18
      libs/remix-ui/workspace/src/lib/components/file-render.tsx
  39. 23
      libs/remix-ui/workspace/src/lib/reducers/workspace.ts
  40. 4
      libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx
  41. 2
      libs/remix-ui/workspace/src/lib/types/index.ts
  42. 3
      nx.json
  43. 1
      tsconfig.base.json
  44. 20
      workspace.json

@ -0,0 +1,69 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
module.exports = {
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, 'http://127.0.0.1:8080', true)
},
'Should add line texts': function (browser: NightwatchBrowser) {
browser
.openFile('contracts')
.openFile('contracts/1_Storage.sol')
.addFile('scripts/addlinetext.ts', {content: addLineText})
.pause(4000)
.executeScriptInTerminal('remix.exeCurrent()')
.pause(4000)
.openFile('contracts/1_Storage.sol')
.useXpath()
.waitForElementVisible("//*[@class='view-line' and contains(.,'contract')]//span//span[contains(.,'mylinetext1')]")
.waitForElementVisible("//*[@class='view-line' and contains(.,'function')]//span//span[contains(.,'mylinetext2')]")
}
}
const addLineText = `
(async () => {
await remix.call('editor', 'discardLineTexts' as any)
let linetext = {
content: 'mylinetext1',
position: {
start: {
line: 9,
column: 1,
}
},
hide: false,
className: 'text-muted small',
afterContentClassName: 'text-muted small fas fa-gas-pump pl-4',
hoverMessage: [{
value: 'hovering1',
},
],
}
await remix.call('editor', 'addLineText' as any, linetext, 'contracts/1_Storage.sol')
linetext = {
content: 'mylinetext2',
position: {
start: {
line: 17,
column: 1,
}
},
hide: false,
className: 'text-muted small',
afterContentClassName: 'text-muted small fas fa-gas-pump pl-4',
hoverMessage: [{
value: 'hovering2',
},
],
}
await remix.call('editor', 'addLineText' as any, linetext, 'contracts/1_Storage.sol')
})()`

@ -0,0 +1,125 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
module.exports = {
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done)
},
'Test decorators with script': function (browser: NightwatchBrowser) {
browser
.openFile('contracts')
.openFile('contracts/2_Owner.sol')
.openFile('contracts/1_Storage.sol')
.openFile('contracts/3_Ballot.sol')
.addFile('scripts/decorators.ts', { content: testScriptSet })
.pause(2000)
.executeScriptInTerminal('remix.exeCurrent()')
.pause(4000)
.useXpath()
.waitForElementContainsText('//*[@id="fileExplorerView"]//*[@data-id="file-decoration-error-contracts/2_Owner.sol"]', '2')
.waitForElementContainsText('//*[@class="mainview"]//*[@data-id="file-decoration-error-contracts/2_Owner.sol"]', '2')
.waitForElementContainsText('//*[@id="fileExplorerView"]//*[@data-id="file-decoration-custom-contracts/2_Owner.sol"]', 'U')
.waitForElementContainsText('//*[@class="mainview"]//*[@data-id="file-decoration-custom-contracts/2_Owner.sol"]', 'U')
.waitForElementContainsText('//*[@id="fileExplorerView"]//*[@data-id="file-decoration-warning-contracts/1_Storage.sol"]', '2')
.waitForElementContainsText('//*[@class="mainview"]//*[@data-id="file-decoration-warning-contracts/1_Storage.sol"]', '2')
.waitForElementContainsText('//*[@id="fileExplorerView"]//*[@data-id="file-decoration-custom-contracts/3_Ballot.sol"]', 'customtext')
.waitForElementContainsText('//*[@class="mainview"]//*[@data-id="file-decoration-custom-contracts/3_Ballot.sol"]', 'customtext')
.moveToElement('//*[@id="fileExplorerView"]//*[@data-id="file-decoration-error-contracts/2_Owner.sol"]', 0, 0)
.waitForElementVisible('//*[@id="error-tooltip-contracts/2_Owner.sol"]')
.waitForElementContainsText('//*[@id="error-tooltip-contracts/2_Owner.sol"]', 'error on owner')
},
'clear ballot decorator': function (browser: NightwatchBrowser) {
browser
.useCss()
.addFile('scripts/clearballot.ts', { content: testScriptClearBallot })
.pause(2000)
.executeScriptInTerminal('remix.exeCurrent()')
.pause(4000)
.waitForElementNotPresent('[data-id="file-decoration-custom-contracts/3_Ballot.sol"]', 10000)
},
'clear all decorators': function (browser: NightwatchBrowser) {
browser
.addFile('scripts/clearall.ts', { content: testScriptClear })
.pause(2000)
.executeScriptInTerminal('remix.exeCurrent()')
.pause(4000)
.waitForElementNotPresent('[data-id="file-decoration-error-contracts/2_Owner.sol"]', 10000)
.waitForElementNotPresent('[data-id="file-decoration-warning-contracts/1_Storage.sol"]', 10000)
}
}
const testScriptSet = `
(async () => {
remix.call('fileDecorator' as any, 'clearFileDecorators')
let decorator: any = {
path: 'contracts/2_Owner.sol',
isDirectory: false,
fileStateType: 'ERROR',
fileStateLabelClass: 'text-danger',
fileStateIconClass: '',
fileStateIcon: '',
text: '2',
bubble: true,
comment: 'error on owner',
}
let decorator2: any = {
path: 'contracts/2_Owner.sol',
isDirectory: false,
fileStateType: 'CUSTOM',
fileStateLabelClass: 'text-success',
fileStateIconClass: 'text-success',
fileStateIcon: 'U',
text: '',
bubble: true,
comment: 'modified',
}
await remix.call('fileDecorator' as any, 'setFileDecorators', [decorator, decorator2])
decorator = {
path: 'contracts/1_Storage.sol',
isDirectory: false,
fileStateType: 'WARNING',
fileStateLabelClass: 'text-warning',
fileStateIconClass: '',
fileStateIcon: '',
text: '2',
bubble: true,
comment: 'warning on storage',
}
await remix.call('fileDecorator' as any, 'setFileDecorators', decorator)
decorator = {
path: 'contracts/3_Ballot.sol',
isDirectory: false,
fileStateType: 'CUSTOM',
fileStateLabelClass: '',
fileStateIconClass: '',
fileStateIcon: 'customtext',
text: 'with text',
bubble: true,
comment: 'custom comment',
}
await remix.call('fileDecorator' as any, 'setFileDecorators', decorator)
})()`
const testScriptClearBallot = `
(async () => {
await remix.call('fileDecorator' as any, 'clearFileDecorators', 'contracts/3_Ballot.sol')
})()`
const testScriptClear = `
(async () => {
await remix.call('fileDecorator' as any, 'clearAllFileDecorators')
})()`

@ -3,6 +3,7 @@ import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done)
},

@ -65,6 +65,29 @@ module.exports = {
})
},
'Should show and update balance for deployed contract on JavascriptVM #group3': function (browser: NightwatchBrowser) {
let instanceAddress
browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]')
.clickLaunchIcon('filePanel')
.addFile('checkBalance.sol', sources[0]['checkBalance.sol'])
.clickLaunchIcon('udapp')
.setValue('*[data-id="dandrValue"]', '111')
.waitForElementVisible('*[data-id="Deploy - transact (payable)"]', 45000)
.click('*[data-id="Deploy - transact (payable)"]')
.pause(1000)
.clickInstance(1)
.pause(1000)
.getAddressAtPosition(1, (address) => {
instanceAddress = address
browser
.waitForElementVisible(`#instance${instanceAddress} [data-id="instanceContractBal"]`)
.assert.containsText(`#instance${instanceAddress} [data-id="instanceContractBal"]`, 'Balance: 0.000000000000000111 ETH')
.clickFunction('sendSomeEther - transact (not payable)', { types: 'uint256 num', values: '2' })
.pause(1000)
.assert.containsText(`#instance${instanceAddress} [data-id="instanceContractBal"]`, 'Balance: 0.000000000000000109 ETH')
})
},
'Should run low level interaction (fallback function) #group3': function (browser: NightwatchBrowser) {
browser.waitForElementPresent('*[data-id="remixIdeSidePanel"]')
.clickInstance(0)
@ -224,6 +247,17 @@ const sources = [
message = _message;
}
}`
},
'checkBalance.sol': {
content: `pragma solidity ^0.8.0;
contract checkBalance {
constructor () payable {}
function sendSomeEther(uint256 num) public {
payable(msg.sender).transfer(num);
}
}`
}
}
]

@ -31,6 +31,7 @@ import { FoundryProvider } from './app/tabs/foundry-provider'
import { ExternalHttpProvider } from './app/tabs/external-http-provider'
import { Injected0ptimismProvider } from './app/tabs/injected-optimism-provider'
import { InjectedArbitrumOneProvider } from './app/tabs/injected-arbitrum-one-provider'
import { FileDecorator } from './app/plugins/file-decorator'
const isElectron = require('is-electron')
@ -156,6 +157,9 @@ class AppComponent {
// ----------------- Storage plugin ---------------------------------
const storagePlugin = new StoragePlugin()
// ------- FILE DECORATOR PLUGIN ------------------
const fileDecorator = new FileDecorator()
//----- search
const search = new SearchPlugin()
@ -239,6 +243,7 @@ class AppComponent {
fetchAndCompile,
dGitProvider,
storagePlugin,
fileDecorator,
hardhatProvider,
ganacheProvider,
foundryProvider,
@ -363,7 +368,7 @@ class AppComponent {
await this.appManager.activatePlugin(['sidePanel']) // activating host plugin separately
await this.appManager.activatePlugin(['home'])
await this.appManager.activatePlugin(['settings', 'config'])
await this.appManager.activatePlugin(['hiddenPanel', 'pluginManager', 'contextualListener', 'terminal', 'blockchain', 'fetchAndCompile', 'contentImport', 'gistHandler'])
await this.appManager.activatePlugin(['hiddenPanel', 'pluginManager', 'fileDecorator', 'contextualListener', 'terminal', 'blockchain', 'fetchAndCompile', 'contentImport', 'gistHandler'])
await this.appManager.activatePlugin(['settings'])
await this.appManager.activatePlugin(['walkthrough','storage', 'search','compileAndRun', 'recorder'])

@ -13,7 +13,7 @@ const profile = {
name: 'editor',
description: 'service - editor',
version: packageJson.version,
methods: ['highlight', 'discardHighlight', 'clearAnnotations', 'addAnnotation', 'gotoLine', 'revealRange', 'getCursorPosition','addErrorMarker', 'clearErrorMarkers'],
methods: ['highlight', 'discardHighlight', 'clearAnnotations', 'addLineText', 'discardLineTexts', 'addAnnotation', 'gotoLine', 'revealRange', 'getCursorPosition','addErrorMarker', 'clearErrorMarkers'],
}
class Editor extends Plugin {
@ -26,8 +26,8 @@ class Editor extends Plugin {
remixDark: 'remix-dark'
}
this.registeredDecorations = { sourceAnnotationsPerFile: {}, markerPerFile: {} }
this.currentDecorations = { sourceAnnotationsPerFile: {}, markerPerFile: {} }
this.registeredDecorations = { sourceAnnotationsPerFile: {}, markerPerFile: {}, lineTextPerFile: {} }
this.currentDecorations = { sourceAnnotationsPerFile: {}, markerPerFile: {}, lineTextPerFile: {} }
// Init
this.event = new EventManager()
@ -576,6 +576,18 @@ class Editor extends Plugin {
this.clearDecorationsByPlugin(session, from, 'markerPerFile', this.registeredDecorations, this.currentDecorations)
}
}
async addLineText (lineText, filePath) {
filePath = filePath || this.currentFile
await this.addDecoration(lineText, filePath, 'lineTextPerFile')
}
discardLineTexts() {
const { from } = this.currentRequest
for (const session in this.sessions) {
this.clearDecorationsByPlugin(session, from, 'lineTextPerFile', this.registeredDecorations, this.currentDecorations)
}
}
}
module.exports = Editor

@ -170,6 +170,10 @@ export class TabProxy extends Plugin {
this.removeTab(profile.name)
})
this.on('fileDecorator', 'fileDecoratorsChanged', async (items) => {
this.tabsApi.setFileDecorations(items)
})
try {
this.themeQuality = (await this.call('theme', 'currentTheme') ).quality
} catch (e) {
@ -306,6 +310,7 @@ export class TabProxy extends Plugin {
updateComponent(state) {
return <TabsUI
plugin={state.plugin}
tabs={state.loadedTabs}
onSelect={state.onSelect}
onClose={state.onClose}
@ -339,6 +344,7 @@ export class TabProxy extends Plugin {
const onReady = (api) => { this.tabsApi = api }
this.dispatch({
plugin: this,
loadedTabs: this.loadedTabs,
onSelect,
onClose,

@ -100,8 +100,8 @@ class Terminal extends Plugin {
this.terminalApi.logHtml(html)
}
log (message) {
this.terminalApi.log(message)
log (message, type) {
this.terminalApi.log(message, type)
}
setDispatch(dispatch) {

@ -0,0 +1,86 @@
'use strict'
import { default as deepequal } from 'deep-equal' // eslint-disable-line
import { Plugin } from '@remixproject/engine'
import { fileDecoration } from '@remix-ui/file-decorators'
const profile = {
name: 'fileDecorator',
desciption: 'Keeps decorators of the files',
methods: ['setFileDecorators', 'clearFileDecorators', 'clearAllFileDecorators'],
events: ['fileDecoratorsChanged'],
version: '0.0.1'
}
export class FileDecorator extends Plugin {
private _fileStates: fileDecoration[] = []
constructor() {
super(profile)
}
onActivation(): void {
this.on('filePanel', 'setWorkspace', async () => {
await this.clearAllFileDecorators()
})
}
/**
*
* @param fileStates Array of file states
*/
async setFileDecorators(fileStates: fileDecoration[] | fileDecoration) {
const { from } = this.currentRequest
const workspace = await this.call('filePanel', 'getCurrentWorkspace')
const fileStatesPayload = Array.isArray(fileStates) ? fileStates : [fileStates]
// clear all file states in the previous state of this owner on the files called
fileStatesPayload.forEach((state) => {
state.workspace = workspace
state.owner = from
})
const filteredState = this._fileStates.filter((state) => {
const index = fileStatesPayload.findIndex((payloadFileState: fileDecoration) => {
return from == state.owner && payloadFileState.path == state.path
})
return index == -1
})
const newState = [...filteredState, ...fileStatesPayload].sort(sortByPath)
if (!deepequal(newState, this._fileStates)) {
this._fileStates = newState
this.emit('fileDecoratorsChanged', this._fileStates)
}
}
async clearFileDecorators(path?: string) {
const { from } = this.currentRequest
if (!from) return
const filteredState = this._fileStates.filter((state) => {
if(state.owner != from) return true
if(path && state.path != path) return true
})
const newState = [...filteredState].sort(sortByPath)
if (!deepequal(newState, this._fileStates)) {
this._fileStates = newState
this.emit('fileDecoratorsChanged', this._fileStates)
}
}
async clearAllFileDecorators() {
this._fileStates = []
this.emit('fileDecoratorsChanged', [])
}
}
const sortByPath = (a: fileDecoration, b: fileDecoration) => {
if (a.path < b.path) {
return -1;
}
if (a.path > b.path) {
return 1;
}
return 0;
}

@ -29,7 +29,10 @@ export class Web3ProviderModule extends Plugin {
const provider = this.blockchain.web3().currentProvider
// see https://github.com/ethereum/web3.js/pull/1018/files#diff-d25786686c1053b786cc2626dc6e048675050593c0ebaafbf0814e1996f22022R129
provider[provider.sendAsync ? 'sendAsync' : 'send'](payload, async (error, message) => {
if (error) return reject(error)
if (error) {
this.call('terminal', 'log', error.data ? error.data : error, 'error')
return reject(error.data ? error.data : error)
}
if (payload.method === 'eth_sendTransaction') {
if (payload.params.length && !payload.params[0].to && message.result) {
setTimeout(async () => {

@ -11,7 +11,7 @@ const requiredModules = [ // services + layout views + system views
'filePanel', 'terminal', 'settings', 'pluginManager', 'tabs', 'udapp', 'dGitProvider', 'solidity', 'solidity-logic', 'gistHandler', 'layout',
'notification', 'permissionhandler', 'walkthrough', 'storage', 'restorebackupzip', 'link-libraries', 'deploy-libraries', 'openzeppelin-proxy',
'hardhat-provider', 'ganache-provider', 'foundry-provider', 'basic-http-provider', 'injected-optimism-provider', 'injected-arbitrum-one-provider',
'compileAndRun', 'search', 'recorder']
'compileAndRun', 'search', 'recorder', 'fileDecorator']
// dependentModules shouldn't be manually activated (e.g hardhat is activated by remixd)
const dependentModules = ['hardhat', 'truffle', 'slither']

@ -5,6 +5,7 @@ import { reducerActions, reducerListener, initialState } from './actions/editor'
import { solidityTokensProvider, solidityLanguageConfig } from './syntaxes/solidity'
import { cairoTokensProvider, cairoLanguageConfig } from './syntaxes/cairo'
import { zokratesTokensProvider, zokratesLanguageConfig } from './syntaxes/zokrates'
import { IMarkdownString } from 'monaco-editor'
import './remix-ui-editor.css'
import { loadTypes } from './web-types'
@ -42,6 +43,25 @@ type sourceMarker = {
hide: boolean
}
export type lineText = {
position: {
start: {
line: number
column: number
},
end: {
line: number
column: number
}
},
from?: string // plugin name
content: string
className: string
afterContentClassName: string
hide: boolean,
hoverMessage: IMarkdownString | IMarkdownString[]
}
type errorMarker = {
message: string
severity: MarkerSeverity
@ -57,7 +77,6 @@ type errorMarker = {
},
file: string
}
loader.config({ paths: { vs: 'assets/js/monaco-editor/dev/vs' } })
export type DecorationsReturn = {
@ -281,7 +300,7 @@ export const EditorUI = (props: EditorUIProps) => {
}
}, [props.currentFile])
const convertToMonacoDecoration = (decoration: sourceAnnotation | sourceMarker, typeOfDecoration: string) => {
const convertToMonacoDecoration = (decoration: lineText | sourceAnnotation | sourceMarker, typeOfDecoration: string) => {
if (typeOfDecoration === 'sourceAnnotationsPerFile') {
decoration = decoration as sourceAnnotation
return {
@ -311,6 +330,19 @@ export const EditorUI = (props: EditorUIProps) => {
}
}
}
if (typeOfDecoration === 'lineTextPerFile') {
const lineTextDecoration = decoration as lineText
return {
type: typeOfDecoration,
range: new monacoRef.current.Range(lineTextDecoration.position.start.line + 1, lineTextDecoration.position.start.column + 1, lineTextDecoration.position.start.line + 1, 1024),
options: {
after: { content: ` ${lineTextDecoration.content}`, inlineClassName: `${lineTextDecoration.className}` },
afterContentClassName: `${lineTextDecoration.afterContentClassName}`,
hoverMessage : lineTextDecoration.hoverMessage
},
}
}
}
props.editorAPI.clearDecorationsByPlugin = (filePath: string, plugin: string, typeOfDecoration: string, registeredDecorations: any, currentDecorations: any) => {

@ -0,0 +1,4 @@
{
"presets": ["@nrwl/react/babel"],
"plugins": []
}

@ -0,0 +1,19 @@
{
"env": {
"browser": true,
"es6": true
},
"extends": "../../../.eslintrc.json",
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaVersion": 11,
"sourceType": "module"
},
"rules": {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "error"
}
}

@ -0,0 +1,3 @@
export { fileDecoration, fileDecorationType, FileType } from './lib/types/index'
export { FileDecorationIcons } from './lib/components/file-decoration-icon'

@ -0,0 +1,51 @@
// eslint-disable-next-line no-use-before-define
import React, { useEffect, useState } from 'react'
import { fileDecoration, fileDecorationType, FileType } from '../types'
import FileDecorationCustomIcon from './filedecorationicons/file-decoration-custom-icon'
import FileDecorationErrorIcon from './filedecorationicons/file-decoration-error-icon'
import FileDecorationTooltip from './filedecorationicons/file-decoration-tooltip'
import FileDecorationWarningIcon from './filedecorationicons/file-decoration-warning-icon'
export type fileDecorationProps = {
file: FileType,
fileDecorations: fileDecoration[]
}
export const FileDecorationIcons = (props: fileDecorationProps) => {
const [states, setStates] = useState<fileDecoration[]>([])
useEffect(() => {
//console.log(props.file)
//console.log(props.fileState)
setStates(props.fileDecorations.filter((fileDecoration) => fileDecoration.path === props.file.path || `${fileDecoration.workspace.name}/${fileDecoration.path}` === props.file.path))
}, [props.fileDecorations])
const getTags = function () {
if (states && states.length) {
const elements: JSX.Element[] = []
for (const [index, state] of states.entries()) {
switch (state.fileStateType) {
case fileDecorationType.Error:
elements.push(<FileDecorationTooltip key={index} index={index} fileDecoration={state} icon={<FileDecorationErrorIcon fileDecoration={state} key={index}/>}/>)
break
case fileDecorationType.Warning:
elements.push(<FileDecorationTooltip key={index} index={index} fileDecoration={state} icon={<FileDecorationWarningIcon fileDecoration={state} key={index}/>}/>)
break
case fileDecorationType.Custom:
elements.push(<FileDecorationTooltip key={index} index={index} fileDecoration={state} icon={<FileDecorationCustomIcon fileDecoration={state} key={index}/>}/>)
break
}
}
return elements
}
}
return <>
{getTags()}
</>
}
export default FileDecorationIcons

@ -0,0 +1,13 @@
// eslint-disable-next-line no-use-before-define
import React from 'react'
import { fileDecoration } from '../../types'
const FileDecorationCustomIcon = (props: {
fileDecoration: fileDecoration
}) => {
return <><span data-id={`file-decoration-custom-${props.fileDecoration.path}`} className={`${props.fileDecoration.fileStateIconClass} pr-2`}>
{props.fileDecoration.fileStateIcon}
</span></>
}
export default FileDecorationCustomIcon

@ -0,0 +1,14 @@
// eslint-disable-next-line no-use-before-define
import React from 'react'
import { fileDecoration } from '../../types'
const FileDecorationErrorIcon = (props: {
fileDecoration: fileDecoration
}) => {
return <>
<span data-id={`file-decoration-error-${props.fileDecoration.path}`} className={`${props.fileDecoration.fileStateIconClass} text-danger pr-2`}>{props.fileDecoration.text}</span>
</>
}
export default FileDecorationErrorIcon

@ -0,0 +1,33 @@
import React from "react";
import { OverlayTrigger, Tooltip } from "react-bootstrap";
import { fileDecoration } from "../../types";
const FileDecorationTooltip = (props: {
fileDecoration: fileDecoration,
icon: JSX.Element
index: number
},
) => {
const getComments = function (fileDecoration: fileDecoration) {
if (fileDecoration.comment) {
const comments = Array.isArray(fileDecoration.comment) ? fileDecoration.comment : [fileDecoration.comment]
return comments.map((comment, index) => {
return <div key={index}>{comment}<br></br></div>
})
}
}
return <OverlayTrigger
key={`overlaytrigger-${props.fileDecoration.path}-${props.index}`}
placement='auto'
overlay={
<Tooltip id={`error-tooltip-${props.fileDecoration.path}`}>
<>{getComments(props.fileDecoration)}</>
</Tooltip>
}
><div>{props.icon}</div></OverlayTrigger>
}
export default FileDecorationTooltip;

@ -0,0 +1,11 @@
// eslint-disable-next-line no-use-before-define
import React from 'react'
import { fileDecoration } from '../../types'
const FileDecorationWarningIcon = (props: {
fileDecoration: fileDecoration
}) => {
return <><span data-id={`file-decoration-warning-${props.fileDecoration.path}`} className={`${props.fileDecoration.fileStateIconClass} text-warning pr-2`}>{props.fileDecoration.text}</span></>
}
export default FileDecorationWarningIcon

@ -0,0 +1,11 @@
import React from "react"
import { fileDecoration } from "../types"
export const getComments = function (fileDecoration: fileDecoration) {
if(fileDecoration.comment){
const comments = Array.isArray(fileDecoration.comment) ? fileDecoration.comment : [fileDecoration.comment]
return comments.map((comment, index) => {
return <div key={index}>{comment}<br></br></div>
})
}
}

@ -0,0 +1,29 @@
export enum fileDecorationType {
Error = 'ERROR',
Warning = 'WARNING',
Custom = 'CUSTOM',
None = 'NONE'
}
export type fileDecoration = {
path: string,
isDirectory: boolean,
fileStateType: fileDecorationType,
fileStateLabelClass: string,
fileStateIconClass: string,
fileStateIcon: string | HTMLDivElement | JSX.Element,
bubble: boolean,
text?: string,
owner?: string,
workspace?: any
tooltip?: string
comment?: string[] | string
}
export interface FileType {
path: string,
name?: string,
isDirectory?: boolean,
type?: 'folder' | 'file' | 'gist',
child?: File[]
}

@ -0,0 +1,16 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"jsx": "react",
"allowJs": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
}
]
}

@ -0,0 +1,13 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../dist/out-tsc",
"types": ["node"]
},
"files": [
"../../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
"../../../node_modules/@nrwl/react/typings/image.d.ts"
],
"exclude": ["**/*.spec.ts", "**/*.spec.tsx"],
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
}

@ -31,12 +31,7 @@ export const PublishToStorage = (props: RemixUiPublishToStorageProps) => {
modal(`Published ${contract.name}'s Metadata and Sources`, publishMessage(result.uploaded))
} catch (err) {
let parseError = err
try {
parseError = JSON.stringify(err)
} catch (e) {
}
modal('Swarm Publish Failed', publishMessageFailed(storage, parseError))
modal('Swarm Publish Failed', publishMessageFailed(storage, err.message))
}
} else {
if (!api.config.get('settings/ipfs-url') && !modalShown) {
@ -81,7 +76,7 @@ export const PublishToStorage = (props: RemixUiPublishToStorageProps) => {
const result = await publishToIPFS(contract, api)
modal(`Published ${contract.name}'s Metadata and Sources`, publishMessage(result.uploaded))
} catch (err) {
modal('IPFS Publish Failed', publishMessageFailed(storage, err))
modal('IPFS Publish Failed', publishMessageFailed(storage, err.message))
}
setModalShown(true)
}

@ -301,3 +301,13 @@ export const runTransactions = (
export const getFuncABIInputs = (plugin: RunTab, funcABI: FuncABI) => {
return plugin.blockchain.getInputs(funcABI)
}
export const updateInstanceBalance = (plugin: RunTab) => {
if (plugin.REACT_API?.instances?.instanceList?.length) {
for (const instance of plugin.REACT_API.instances.instanceList) {
plugin.blockchain.getBalanceInEther(instance.address, (err, balInEth) => {
if (!err) instance.balance = balInEth
})
}
}
}

@ -3,8 +3,10 @@ import React from 'react'
import { RunTab } from '../types/run-tab'
import { resetAndInit, setupEvents } from './events'
import { createNewBlockchainAccount, fillAccountsList, setExecutionContext, signMessageWithAddress } from './account'
import { clearInstances, clearPopUp, removeInstance, setAccount, setGasFee, setMatchPassphrasePrompt, setNetworkNameFromProvider, setPassphrasePrompt, setSelectedContract, setSendTransactionValue, setUnit, updateBaseFeePerGas, updateConfirmSettings, updateGasPrice, updateGasPriceStatus, updateMaxFee, updateMaxPriorityFee, updateScenarioPath, updateTxFeeContent } from './actions'
import { createInstance, getContext, getFuncABIInputs, getSelectedContract, loadAddress, runTransactions } from './deploy'
import { clearInstances, clearPopUp, removeInstance, setAccount, setGasFee, setMatchPassphrasePrompt,
setNetworkNameFromProvider, setPassphrasePrompt, setSelectedContract, setSendTransactionValue, setUnit,
updateBaseFeePerGas, updateConfirmSettings, updateGasPrice, updateGasPriceStatus, updateMaxFee, updateMaxPriorityFee, updateScenarioPath, updateTxFeeContent } from './actions'
import { createInstance, getContext, getFuncABIInputs, getSelectedContract, loadAddress, runTransactions, updateInstanceBalance } from './deploy'
import { CompilerAbstract as CompilerAbstractType } from '@remix-project/remix-solidity-ts'
import { ContractData, FuncABI } from "@remix-project/core-plugin"
import { DeployMode, MainnetPrompt } from '../types'
@ -26,6 +28,7 @@ export const initRunTab = (udapp: RunTab) => async (reducerDispatch: React.Dispa
setupEvents(plugin, dispatch)
setInterval(() => {
fillAccountsList(plugin, dispatch)
updateInstanceBalance(plugin)
}, 1000)
}

@ -20,6 +20,7 @@ export function UniversalDappUI (props: UdappProps) {
const [llIError, setLlIError] = useState<string>('')
const [calldataValue, setCalldataValue] = useState<string>('')
const [evmBC, setEvmBC] = useState(null)
const [instanceBalance, setInstanceBalance] = useState(0)
useEffect(() => {
if (!props.instance.abi) {
@ -47,6 +48,12 @@ export function UniversalDappUI (props: UdappProps) {
}
}, [props.instance.contractData])
useEffect(() => {
if (props.instance.balance) {
setInstanceBalance(props.instance.balance)
}
}, [props.instance.balance])
const sendData = () => {
setLlIError('')
const fallback = txHelper.getFallbackInterface(contractABI)
@ -229,6 +236,9 @@ export function UniversalDappUI (props: UdappProps) {
</div>
<div className="udapp_cActionsWrapper" data-id="universalDappUiContractActionWrapper">
<div className="udapp_contractActionsContainer">
<div className="d-flex" data-id="instanceContractBal">
<label>Balance: {instanceBalance} ETH</label>
</div>
{
contractABI && contractABI.map((funcABI, index) => {
if (funcABI.type !== 'function') return null

@ -88,6 +88,7 @@ export interface RunTabState {
instanceList: {
contractData?: ContractData,
address: string,
balance?: number,
name: string,
decodedResponse?: Record<number, any>,
abi?: any

@ -181,6 +181,7 @@ export interface InstanceContainerProps {
instanceList: {
contractData?: ContractData,
address: string,
balance?: number,
name: string,
decodedResponse?: Record<number, any>,
abi?: any
@ -281,6 +282,7 @@ export interface UdappProps {
instance: {
contractData?: ContractData,
address: string,
balance?: number,
name: string,
decodedResponse?: Record<number, any>,
abi?: any

@ -1,3 +1,7 @@
import { fileDecoration, FileDecorationIcons } from '@remix-ui/file-decorators'
import { Plugin } from '@remixproject/engine'
import React, { useState, useRef, useEffect, useReducer } from 'react' // eslint-disable-line
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs'
import './remix-ui-tabs.css'
@ -5,6 +9,7 @@ import './remix-ui-tabs.css'
/* eslint-disable-next-line */
export interface TabsUIProps {
tabs: Array<any>
plugin: Plugin,
onSelect: (index: number) => void
onClose: (index: number) => void
onZoomOut: () => void
@ -18,8 +23,41 @@ export interface TabsUIApi {
active: () => string
}
interface ITabsState {
selectedIndex: number,
fileDecorations: fileDecoration[],
}
interface ITabsAction {
type: string,
payload: any,
}
const initialTabsState: ITabsState = {
selectedIndex: -1,
fileDecorations: [],
}
const tabsReducer = (state: ITabsState, action: ITabsAction) => {
switch (action.type) {
case 'SELECT_INDEX':
return {
...state,
selectedIndex: action.payload,
}
case 'SET_FILE_DECORATIONS':
return {
...state,
fileDecorations: action.payload as fileDecoration[],
}
default:
return state
}
}
export const TabsUI = (props: TabsUIProps) => {
const [selectedIndex, setSelectedIndex] = useState(-1)
const [tabsState, dispatch] = useReducer(tabsReducer, initialTabsState);
const currentIndexRef = useRef(-1)
const tabsRef = useRef({})
const tabsElement = useRef(null)
@ -28,10 +66,24 @@ export const TabsUI = (props: TabsUIProps) => {
tabs.current = props.tabs // we do this to pass the tabs list to the onReady callbacks
useEffect(() => {
if (props.tabs[selectedIndex]) {
tabsRef.current[selectedIndex].scrollIntoView({ behavior: 'smooth', block: 'center' })
if (props.tabs[tabsState.selectedIndex]) {
tabsRef.current[tabsState.selectedIndex].scrollIntoView({ behavior: 'smooth', block: 'center' })
}
}, [tabsState.selectedIndex])
const getFileDecorationClasses = (tab: any) => {
const fileDecoration = tabsState.fileDecorations.find((fileDecoration: fileDecoration) => {
if(`${fileDecoration.workspace.name}/${fileDecoration.path}` === tab.name) return true
})
return fileDecoration && fileDecoration.fileStateLabelClass
}
}, [selectedIndex])
const getFileDecorationIcons = (tab: any) => {
return <FileDecorationIcons file={{path: tab.name}} fileDecorations={tabsState.fileDecorations} />
}
const renderTab = (tab, index) => {
const classNameImg = 'my-1 mr-1 text-dark ' + tab.iconClass
@ -41,7 +93,8 @@ export const TabsUI = (props: TabsUIProps) => {
return (
<div ref={el => { tabsRef.current[index] = el }} className={classNameTab} data-id={index === currentIndexRef.current ? 'tab-active' : ''} title={tab.tooltip}>
{tab.icon ? (<img className="my-1 mr-1 iconImage" style={{ filter: invert }} src={tab.icon} />) : (<i className={classNameImg}></i>)}
<span className="title-tabs">{tab.title}</span>
<span className={`title-tabs ${getFileDecorationClasses(tab)}`}>{tab.title}</span>
{getFileDecorationIcons(tab)}
<span className="close-tabs" onClick={(event) => { props.onClose(index); event.stopPropagation() }}>
<i className="text-dark fas fa-times"></i>
</span>
@ -56,7 +109,11 @@ export const TabsUI = (props: TabsUIProps) => {
const activateTab = (name: string) => {
const index = tabs.current.findIndex((tab) => tab.name === name)
currentIndexRef.current = index
setSelectedIndex(index)
dispatch({ type: 'SELECT_INDEX', payload: index })
}
const setFileDecorations = (fileStates: fileDecoration[]) => {
dispatch({ type: 'SET_FILE_DECORATIONS', payload: fileStates })
}
const transformScroll = (event) => {
@ -71,8 +128,10 @@ export const TabsUI = (props: TabsUIProps) => {
useEffect(() => {
props.onReady({
activateTab,
active
active,
setFileDecorations
})
return () => { tabsElement.current.removeEventListener('wheel', transformScroll) }
}, [])
@ -85,7 +144,7 @@ export const TabsUI = (props: TabsUIProps) => {
</div>
<Tabs
className="tab-scroll"
selectedIndex={selectedIndex}
selectedIndex={tabsState.selectedIndex}
domRef={(domEl) => {
if (tabsElement.current) return
tabsElement.current = domEl
@ -94,7 +153,7 @@ export const TabsUI = (props: TabsUIProps) => {
onSelect={(index) => {
props.onSelect(index)
currentIndexRef.current = index
setSelectedIndex(index)
dispatch({ type: 'SELECT_INDEX', payload: index })
}}
>
<TabList className="d-flex flex-row align-items-center">

@ -86,8 +86,8 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
scriptRunnerDispatch({ type: 'html', payload: { message: [html ? html.innerText ? html.innerText : html : null] } })
},
log: (message) => {
scriptRunnerDispatch({ type: 'log', payload: { message: [message] } })
log: (message, type) => {
scriptRunnerDispatch({ type: type ? type : 'log', payload: { message: [message] } })
}
})
}, [])

@ -1,2 +1,3 @@
export * from './lib/providers/FileSystemProvider'
export * from './lib/contexts'
export { FileType } from './lib/types/index'

@ -1,7 +1,8 @@
import { fileDecoration } from '@remix-ui/file-decorators'
import { extractParentFromKey } from '@remix-ui/helper'
import React from 'react'
import { action, WorkspaceTemplate } from '../types'
import { displayNotification, displayPopUp, fileAddedSuccess, fileRemovedSuccess, fileRenamedSuccess, folderAddedSuccess, loadLocalhostError, loadLocalhostRequest, loadLocalhostSuccess, removeContextMenuItem, removeFocus, rootFolderChangedSuccess, setContextMenuItem, setMode, setReadOnlyMode } from './payload'
import { displayNotification, displayPopUp, fileAddedSuccess, fileRemovedSuccess, fileRenamedSuccess, folderAddedSuccess, loadLocalhostError, loadLocalhostRequest, loadLocalhostSuccess, removeContextMenuItem, removeFocus, rootFolderChangedSuccess, setContextMenuItem, setMode, setReadOnlyMode, setFileDecorationSuccess } from './payload'
import { addInputField, createWorkspace, deleteWorkspace, fetchWorkspaceDirectory, renameWorkspace, switchToWorkspace, uploadFile } from './workspace'
const LOCALHOST = ' - connect to localhost - '
@ -38,6 +39,10 @@ export const listenOnPluginEvents = (filePanelPlugin) => {
uploadFile(target, dir, cb)
})
plugin.on('fileDecorator', 'fileDecoratorsChanged', async (items: fileDecoration[]) => {
setFileDecorators(items)
})
plugin.on('remixd', 'rootFolderChanged', async (path: string) => {
rootFolderChanged(path)
})
@ -202,3 +207,8 @@ const fileRenamed = async (oldPath: string) => {
const rootFolderChanged = async (path) => {
await dispatch(rootFolderChangedSuccess(path))
}
const setFileDecorators = async (items: fileDecoration[], cb?: (err: Error, result?: string | number | boolean | Record<string, any>) => void) => {
await dispatch(setFileDecorationSuccess(items))
cb && cb(null, true)
}

@ -1,3 +1,4 @@
import { fileDecoration } from '@remix-ui/file-decorators'
import { action } from '../types'
export const setCurrentWorkspace = (workspace: { name: string; isGitRepo: boolean; }) => {
@ -240,6 +241,12 @@ export const fsInitializationCompleted = () => {
}
}
export const setFileDecorationSuccess = (items: fileDecoration[]) => {
return {
type: 'SET_FILE_DECORATION_SUCCESS',
payload: items
}
}
export const cloneRepositoryRequest = () => {
return {
type: 'CLONE_REPOSITORY_REQUEST'

@ -12,7 +12,7 @@ import { checkSpecialChars, extractNameFromKey, extractParentFromKey, joinPath }
import { FileRender } from './file-render'
export const FileExplorer = (props: FileExplorerProps) => {
const { name, contextMenuItems, removedContextMenuItems, files } = props
const { name, contextMenuItems, removedContextMenuItems, files, fileState } = props
const [state, setState] = useState<FileExplorerState>({
ctrlKey: false,
newFileName: '',
@ -432,6 +432,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
{
files[props.name] && Object.keys(files[props.name]).map((key, index) => <FileRender
file={files[props.name][key]}
fileDecorations={fileState}
index={index}
focusContext={state.focusContext}
focusEdit={state.focusEdit}

@ -1,4 +1,5 @@
// eslint-disable-next-line no-use-before-define
import { fileDecoration } from '@remix-ui/file-decorators'
import React, { useEffect, useRef, useState } from 'react'
import { FileType } from '../types'
@ -10,12 +11,14 @@ export interface FileLabelProps {
isNew: boolean
lastEdit: string
}
fileDecorations: fileDecoration[],
editModeOff: (content: string) => void
}
export const FileLabel = (props: FileLabelProps) => {
const { file, focusEdit, editModeOff } = props
const { file, focusEdit, editModeOff, fileDecorations } = props
const [isEditable, setIsEditable] = useState<boolean>(false)
const [fileStateClasses, setFileStateClasses] = useState<string>('')
const labelRef = useRef(null)
useEffect(() => {
@ -24,6 +27,18 @@ export const FileLabel = (props: FileLabelProps) => {
}
}, [file.path, focusEdit])
useEffect(() => {
const state = props.fileDecorations.find((state: fileDecoration) => {
if(state.path === props.file.path) return true
if(state.bubble && props.file.isDirectory && state.path.startsWith(props.file.path)) return true
})
if (state && state.fileStateLabelClass) {
setFileStateClasses(state.fileStateLabelClass)
} else{
setFileStateClasses('')
}
}, [fileDecorations])
useEffect(() => {
if (labelRef.current) {
setTimeout(() => {
@ -57,7 +72,7 @@ export const FileLabel = (props: FileLabelProps) => {
>
<span
title={file.path}
className={'text-nowrap remixui_label ' + (file.isDirectory ? 'folder' : 'remixui_leaf')}
className={`text-nowrap remixui_label ${fileStateClasses} ` + (file.isDirectory ? 'folder' : 'remixui_leaf')}
data-path={file.path}
>
{file.name}

@ -6,6 +6,11 @@ import { TreeView, TreeViewItem } from '@remix-ui/tree-view'
import { getPathIcon } from '@remix-ui/helper'
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { FileLabel } from './file-label'
import { fileDecoration, FileDecorationIcons } from '@remix-ui/file-decorators'
export interface RenderFileProps {
file: FileType,
@ -19,6 +24,7 @@ export interface RenderFileProps {
handleClickFolder: (path: string, type: string) => void,
handleClickFile: (path: string, type: string) => void,
handleContextMenu: (pageX: number, pageY: number, path: string, content: string, type: string) => void
fileDecorations: fileDecoration[]
}
export const FileRender = (props: RenderFileProps) => {
@ -76,7 +82,7 @@ export const FileRender = (props: RenderFileProps) => {
iconX='pr-3 fa fa-folder'
iconY='pr-3 fa fa-folder-open'
key={`${file.path + props.index}`}
label={<FileLabel file={file} focusEdit={props.focusEdit} editModeOff={props.editModeOff} />}
label={<FileLabel fileDecorations={props.fileDecorations} file={file} focusEdit={props.focusEdit} editModeOff={props.editModeOff} />}
onClick={handleFolderClick}
onContextMenu={handleContextMenu}
labelClass={labelClass}
@ -89,6 +95,7 @@ export const FileRender = (props: RenderFileProps) => {
file.child ? <TreeView id={`treeView${file.path}`} key={`treeView${file.path}`} {...spreadProps }>{
Object.keys(file.child).map((key, index) => <FileRender
file={file.child[key]}
fileDecorations={props.fileDecorations}
index={index}
focusContext={props.focusContext}
focusEdit={props.focusEdit}
@ -111,7 +118,14 @@ export const FileRender = (props: RenderFileProps) => {
<TreeViewItem
id={`treeViewItem${file.path}`}
key={`treeView${file.path}`}
label={<FileLabel file={file} focusEdit={props.focusEdit} editModeOff={props.editModeOff} />}
label={
<>
<div className="d-flex flex-row">
<FileLabel file={file} fileDecorations={props.fileDecorations} focusEdit={props.focusEdit} editModeOff={props.editModeOff} />
<FileDecorationIcons file={file} fileDecorations={props.fileDecorations}/>
</div>
</>
}
onClick={handleFileClick}
onContextMenu={handleContextMenu}
icon={icon}

@ -1,6 +1,7 @@
import { extractNameFromKey } from '@remix-ui/helper'
import { action, FileType } from '../types'
import * as _ from 'lodash'
import { fileDecoration } from '@remix-ui/file-decorators'
interface Action {
type: string
payload: any
@ -25,7 +26,8 @@ export interface BrowserState {
registeredMenuItems: action[],
removedMenuItems: action[],
error: string
}
},
fileState: fileDecoration[]
},
localhost: {
sharedFolder: string,
@ -40,7 +42,8 @@ export interface BrowserState {
registeredMenuItems: action[],
removedMenuItems: action[],
error: string
}
},
fileState: []
},
mode: 'browser' | 'localhost',
notification: {
@ -75,7 +78,8 @@ export const browserInitialState: BrowserState = {
registeredMenuItems: [],
removedMenuItems: [],
error: null
}
},
fileState: []
},
localhost: {
sharedFolder: '',
@ -90,7 +94,8 @@ export const browserInitialState: BrowserState = {
registeredMenuItems: [],
removedMenuItems: [],
error: null
}
},
fileState: []
},
mode: 'browser',
notification: {
@ -650,6 +655,16 @@ export const browserReducer = (state = browserInitialState, action: Action) => {
}
}
case 'SET_FILE_DECORATION_SUCCESS': {
return {
...state,
browser: {
...state.browser,
fileState: action.payload
}
}
}
default:
throw new Error()
}

@ -227,7 +227,7 @@ export function Workspace () {
e.stopPropagation()
deleteCurrentWorkspace()
}}
className='fas fa-trash remixui_menuicon'
className='far fa-trash remixui_menuicon'
title='Delete'>
</span>
<span
@ -307,6 +307,7 @@ export function Workspace () {
contextMenuItems={global.fs.browser.contextMenu.registeredMenuItems}
removedContextMenuItems={global.fs.browser.contextMenu.removedMenuItems}
files={global.fs.browser.files}
fileState={global.fs.browser.fileState}
expandPath={global.fs.browser.expandPath}
focusEdit={global.fs.focusEdit}
focusElement={global.fs.focusElement}
@ -343,6 +344,7 @@ export function Workspace () {
contextMenuItems={global.fs.localhost.contextMenu.registeredMenuItems}
removedContextMenuItems={global.fs.localhost.contextMenu.removedMenuItems}
files={global.fs.localhost.files}
fileState={[]}
expandPath={global.fs.localhost.expandPath}
focusEdit={global.fs.focusEdit}
focusElement={global.fs.focusElement}

@ -1,5 +1,6 @@
import React from 'react'
import { customAction } from '@remixproject/plugin-api/lib/file-system/file-panel'
import { fileDecoration } from '@remix-ui/file-decorators';
export type action = { name: string, type?: Array<'folder' | 'gist' | 'file'>, path?: string[], extension?: string[], pattern?: string[], id: string, multiselect: boolean, label: string, sticky?: boolean }
export interface JSONStandardInput {
@ -73,6 +74,7 @@ export interface FileExplorerProps {
contextMenuItems: MenuItems,
removedContextMenuItems: MenuItems,
files: { [x: string]: Record<string, FileType> },
fileState: fileDecoration[],
expandPath: string[],
focusEdit: string,
focusElement: { key: string, type: 'file' | 'folder' | 'gist' }[],

@ -193,6 +193,9 @@
"remix-ui-permission-handler": {
"tags": []
},
"remix-ui-file-decorators": {
"tags": []
},
"remix-ui-tooltip-popup": {
"tags": []
},

@ -88,6 +88,7 @@
"@remix-ui/permission-handler": [
"libs/remix-ui/permission-handler/src/index.ts"
],
"@remix-ui/file-decorators": ["libs/remix-ui/file-decorators/src/index.ts"],
"@remix-ui/tooltip-popup": ["libs/remix-ui/tooltip-popup/src/index.ts"]
}
},

@ -1496,6 +1496,26 @@
}
}
},
"remix-ui-file-decorators": {
"root": "libs/remix-ui/file-decorators",
"sourceRoot": "libs/remix-ui/file-decorators/src",
"projectType": "library",
"architect": {
"lint": {
"builder": "@nrwl/linter:lint",
"options": {
"linter": "eslint",
"tsConfig": [
"libs/remix-ui/file-decorators/tsconfig.lib.json"
],
"exclude": [
"**/node_modules/**",
"!libs/remix-ui/file-decorators/**/*"
]
}
}
}
},
"vyper": {
"root": "apps/vyper",
"sourceRoot": "apps/vyper/src",

Loading…
Cancel
Save