Merge branch 'master' into injected-web3

pull/5370/head
David Disu 3 years ago committed by GitHub
commit b8eb704060
  1. 2
      apps/debugger/src/app/debugger-api.ts
  2. 2
      apps/remix-ide-e2e/src/tests/generalSettings.test.ts
  3. 4
      apps/remix-ide/src/app.js
  4. 3
      apps/remix-ide/src/app/components/panel.ts
  5. 37
      apps/remix-ide/src/app/components/side-panel.tsx
  6. 110
      apps/remix-ide/src/app/components/vertical-icons.js
  7. 116
      apps/remix-ide/src/app/components/vertical-icons.tsx
  8. 4
      apps/remix-ide/src/remixAppManager.js
  9. 2
      apps/remix-ide/src/walkthroughService.js
  10. 3
      libs/remix-core-plugin/src/lib/compiler-content-imports.ts
  11. 7
      libs/remix-ui/helper/src/lib/helper-components.tsx
  12. 2
      libs/remix-ui/home-tab/src/lib/remix-ui-home-tab.css
  13. 16
      libs/remix-ui/home-tab/src/lib/remix-ui-home-tab.tsx
  14. 2
      libs/remix-ui/panel/src/lib/plugins/panel-header.tsx
  15. 2
      libs/remix-ui/panel/src/lib/types/index.ts
  16. 20
      libs/remix-ui/vertical-icons-panel/.eslintrc.json
  17. 3
      libs/remix-ui/vertical-icons-panel/src/index.ts
  18. 11
      libs/remix-ui/vertical-icons-panel/src/lib/components/Badge.tsx
  19. 2
      libs/remix-ui/vertical-icons-panel/src/lib/components/BasicLogo.tsx
  20. 13
      libs/remix-ui/vertical-icons-panel/src/lib/components/Chevron.tsx
  21. 41
      libs/remix-ui/vertical-icons-panel/src/lib/components/Debugger.tsx
  22. 59
      libs/remix-ui/vertical-icons-panel/src/lib/components/FilePanel.tsx
  23. 14
      libs/remix-ui/vertical-icons-panel/src/lib/components/Home.tsx
  24. 66
      libs/remix-ui/vertical-icons-panel/src/lib/components/Icon.tsx
  25. 33
      libs/remix-ui/vertical-icons-panel/src/lib/components/IconList.tsx
  26. 43
      libs/remix-ui/vertical-icons-panel/src/lib/components/OtherIcons.tsx
  27. 36
      libs/remix-ui/vertical-icons-panel/src/lib/components/PluginManager.tsx
  28. 72
      libs/remix-ui/vertical-icons-panel/src/lib/components/RequiredSection.tsx
  29. 60
      libs/remix-ui/vertical-icons-panel/src/lib/components/Settings.tsx
  30. 41
      libs/remix-ui/vertical-icons-panel/src/lib/components/Solidity.tsx
  31. 41
      libs/remix-ui/vertical-icons-panel/src/lib/components/SolidityStaticAnalysis.tsx
  32. 42
      libs/remix-ui/vertical-icons-panel/src/lib/components/Udapp.tsx
  33. 29
      libs/remix-ui/vertical-icons-panel/src/lib/reducers/iconBadgeReducer.ts
  34. 3
      libs/remix-ui/vertical-icons-panel/src/lib/reducers/verticalScrollReducer.ts
  35. 25
      libs/remix-ui/vertical-icons-panel/src/lib/remix-ui-vertical-icons-panel.css
  36. 161
      libs/remix-ui/vertical-icons-panel/src/lib/remix-ui-vertical-icons-panel.tsx
  37. 10
      libs/remix-ui/vertical-icons-panel/src/lib/types/index.ts
  38. 46
      libs/remix-ui/vertical-icons-panel/src/lib/vertical-icons-context-menu.tsx
  39. 12
      libs/remix-ui/vertical-icons-panel/tsconfig.json
  40. 3
      libs/remix-ui/vertical-icons-panel/tsconfig.lib.json
  41. 111
      libs/remix-ui/vertical-icons-panel/types/vertical-icons-panel.d.ts
  42. 2
      libs/remix-url-resolver/src/resolve.ts
  43. 2
      libs/remix-url-resolver/tests/test.ts

@ -83,7 +83,7 @@ export const DebuggerApiMixin = (Base) => class extends Base {
const target = (address && remixDebug.traceHelper.isContractCreation(address)) ? receipt.contractAddress : address const target = (address && remixDebug.traceHelper.isContractCreation(address)) ? receipt.contractAddress : address
const targetAddress = target || receipt.contractAddress || receipt.to const targetAddress = target || receipt.contractAddress || receipt.to
const codeAtAddress = await this._web3.eth.getCode(targetAddress) const codeAtAddress = await this._web3.eth.getCode(targetAddress)
const output = await this.call('fetchAndCompile', 'resolve', targetAddress, codeAtAddress, 'browser/.debug') const output = await this.call('fetchAndCompile', 'resolve', targetAddress, codeAtAddress, '.debug')
if (output) { if (output) {
return new CompilerAbstract(output.languageversion, output.data, output.source) return new CompilerAbstract(output.languageversion, output.data, output.source)
} }

@ -19,7 +19,7 @@ module.exports = {
browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]', 5000) browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]', 5000)
.waitForElementVisible('*[data-id="settingsTabGenerateContractMetadataLabel"]', 5000) .waitForElementVisible('*[data-id="settingsTabGenerateContractMetadataLabel"]', 5000)
.verify.elementPresent('[data-id="settingsTabGenerateContractMetadata"]:checked') .verify.elementPresent('[data-id="settingsTabGenerateContractMetadata"]:checked')
.click('*[data-id="verticalIconsFileExplorerIcons"]') .click('*[data-id="verticalIconsKindfilePanel"]')
.click('[data-id="treeViewLitreeViewItemcontracts"]') .click('[data-id="treeViewLitreeViewItemcontracts"]')
.openFile('contracts/3_Ballot.sol') .openFile('contracts/3_Ballot.sol')
.click('*[data-id="verticalIconsKindsolidity"]') .click('*[data-id="verticalIconsKindsolidity"]')

@ -225,8 +225,8 @@ class AppComponent {
self.engine.register([appPanel, tabProxy]) self.engine.register([appPanel, tabProxy])
// those views depend on app_manager // those views depend on app_manager
self.menuicons = new VerticalIcons(appManager) self.menuicons = new VerticalIcons()
self.sidePanel = new SidePanel(appManager, self.menuicons) self.sidePanel = new SidePanel()
self.hiddenPanel = new HiddenPanel() self.hiddenPanel = new HiddenPanel()
const pluginManagerComponent = new PluginManagerComponent( const pluginManagerComponent = new PluginManagerComponent(

@ -49,8 +49,7 @@ export class AbstractPanel extends HostPlugin {
* @param {String} name The name of the plugin to display the content * @param {String} name The name of the plugin to display the content
*/ */
showContent (name) { showContent (name) {
if (!this.plugins[name]) throw new Error(`Plugin ${name} is not yet activated`) if (!this.plugins[name]) throw new Error(`Plugin ${name} is not yet activated`)
Object.values(this.plugins).forEach(plugin => { Object.values(this.plugins).forEach(plugin => {
plugin.active = false plugin.active = false
}) })

@ -4,8 +4,6 @@ import ReactDOM from 'react-dom'
import { AbstractPanel } from './panel' import { AbstractPanel } from './panel'
import { RemixPluginPanel } from '@remix-ui/panel' import { RemixPluginPanel } from '@remix-ui/panel'
import packageJson from '../../../../../package.json' import packageJson from '../../../../../package.json'
import { RemixAppManager } from '../../remixAppManager'
import { VerticalIcons } from 'libs/remix-ui/vertical-icons-panel/types/vertical-icons-panel'
import RemixUIPanelHeader from 'libs/remix-ui/panel/src/lib/plugins/panel-header' import RemixUIPanelHeader from 'libs/remix-ui/panel/src/lib/plugins/panel-header'
// const csjs = require('csjs-inject') // const csjs = require('csjs-inject')
@ -17,20 +15,18 @@ const sidePanel = {
methods: ['addView', 'removeView'] methods: ['addView', 'removeView']
} }
// TODO merge with vertical-icons.js
export class SidePanel extends AbstractPanel { export class SidePanel extends AbstractPanel {
appManager: RemixAppManager
sideelement: any sideelement: any
verticalIcons: VerticalIcons; constructor() {
constructor (appManager: RemixAppManager, verticalIcons: VerticalIcons) {
super(sidePanel) super(sidePanel)
this.appManager = appManager
this.sideelement = document.createElement('section') this.sideelement = document.createElement('section')
this.sideelement.setAttribute('class', 'panel plugin-manager') this.sideelement.setAttribute('class', 'panel plugin-manager')
this.verticalIcons = verticalIcons }
onActivation() {
this.renderComponent()
// Toggle content // Toggle content
verticalIcons.events.on('toggleContent', (name) => { this.on('menuicons', 'toggleContent', (name) => {
if (!this.plugins[name]) return if (!this.plugins[name]) return
if (this.plugins[name].active) { if (this.plugins[name].active) {
// TODO: Only keep `this.emit` (issue#2210) // TODO: Only keep `this.emit` (issue#2210)
@ -44,7 +40,7 @@ export class SidePanel extends AbstractPanel {
this.events.emit('showing', name) this.events.emit('showing', name)
}) })
// Force opening // Force opening
verticalIcons.events.on('showContent', (name) => { this.on('menuicons', 'showContent', (name) => {
if (!this.plugins[name]) return if (!this.plugins[name]) return
this.showContent(name) this.showContent(name)
// TODO: Only keep `this.emit` (issue#2210) // TODO: Only keep `this.emit` (issue#2210)
@ -53,25 +49,22 @@ export class SidePanel extends AbstractPanel {
}) })
} }
onActivation () { focus(name) {
this.renderComponent()
}
focus (name) {
this.emit('focusChanged', name) this.emit('focusChanged', name)
super.focus(name) super.focus(name)
} }
removeView (profile) { removeView(profile) {
if (this.plugins[profile.name].active) this.call('menuicons', 'select', 'filePanel')
super.removeView(profile) super.removeView(profile)
this.emit('pluginDisabled', profile.name) this.emit('pluginDisabled', profile.name)
this.call('menuicons', 'unlinkContent', profile) this.call('menuicons', 'unlinkContent', profile)
this.renderComponent() this.renderComponent()
} }
addView (profile, view) { addView(profile, view) {
super.addView(profile, view) super.addView(profile, view)
this.verticalIcons.linkContent(profile) this.call('menuicons', 'linkContent', profile)
this.renderComponent() this.renderComponent()
} }
@ -79,17 +72,17 @@ export class SidePanel extends AbstractPanel {
* Display content and update the header * Display content and update the header
* @param {String} name The name of the plugin to display * @param {String} name The name of the plugin to display
*/ */
async showContent (name) { async showContent(name) {
super.showContent(name) super.showContent(name)
this.emit('focusChanged', name) this.emit('focusChanged', name)
this.renderComponent() this.renderComponent()
} }
render () { render() {
return this.sideelement return this.sideelement
} }
renderComponent () { renderComponent() {
ReactDOM.render(<RemixPluginPanel header={<RemixUIPanelHeader plugins={this.plugins}></RemixUIPanelHeader>} plugins={this.plugins}/>, this.sideelement) ReactDOM.render(<RemixPluginPanel header={<RemixUIPanelHeader plugins={this.plugins}></RemixUIPanelHeader>} plugins={this.plugins} />, this.sideelement)
} }
} }

@ -1,110 +0,0 @@
import * as packageJson from '../../../../../package.json'
import ReactDOM from 'react-dom'
import React from 'react' // eslint-disable-line
// eslint-disable-next-line no-unused-vars
import { RemixUiVerticalIconsPanel } from '@remix-ui/vertical-icons-panel'
import Registry from '../state/registry'
const { Plugin } = require('@remixproject/engine')
const EventEmitter = require('events')
const profile = {
name: 'menuicons',
displayName: 'Vertical Icons',
description: '',
version: packageJson.version,
methods: ['select', 'unlinkContent']
}
// TODO merge with side-panel.js. VerticalIcons should not be a plugin
export class VerticalIcons extends Plugin {
constructor (appManager) {
super(profile)
this.events = new EventEmitter()
this.appManager = appManager
this.htmlElement = document.createElement('div')
this.htmlElement.setAttribute('id', 'icon-panel')
this.icons = {}
this.iconKind = {}
this.iconStatus = {}
this.defaultProfile = profile
this.targetProfileForChange = {}
this.targetProfileForRemoval = {}
this.registry = Registry.getInstance()
this.keys = ['succeed', 'edited', 'none', 'loading', 'failed']
this.types = ['error', 'warning', 'success', 'info', '']
}
renderComponent () {
ReactDOM.render(
<RemixUiVerticalIconsPanel
verticalIconsPlugin={this}
/>,
this.htmlElement)
}
onActivation () {
this.renderComponent()
}
linkContent (profile) {
if (!profile.icon) return
if (!profile.kind) profile.kind = 'none'
this.targetProfileForChange[profile.name] = profile
this.listenOnStatus(profile)
this.renderComponent()
}
unlinkContent (profile) {
this.targetProfileForRemoval = profile
this.removeIcon(profile)
this.renderComponent()
}
listenOnStatus (profile) {
}
/**
* Remove an icon from the map
* @param {ModuleProfile} profile The profile of the module
*/
removeIcon ({ name }) {
if (this.targetProfileForChange[name]) delete this.targetProfileForChange[name]
setTimeout(() => {
this.renderComponent()
}, 150)
}
/**
* Set an icon as active
* @param {string} name Name of profile of the module to activate
*/
select (name) {
// TODO: Only keep `this.emit` (issue#2210)
this.emit('showContent', name)
this.events.emit('showContent', name)
}
onThemeChanged (themeType) {
const invert = themeType === 'dark' ? 1 : 0
const active = this.view.querySelector('.active')
if (active) {
const image = active.querySelector('.image')
image.style.setProperty('filter', `invert(${invert})`)
}
}
/**
* Toggles the side panel for plugin
* @param {string} name Name of profile of the module to activate
*/
toggle (name) {
// TODO: Only keep `this.emit` (issue#2210)
this.emit('toggleContent', name)
this.events.emit('toggleContent', name)
}
render () {
return this.htmlElement
}
}

@ -0,0 +1,116 @@
// eslint-disable-next-line no-use-before-define
import React from 'react'
import ReactDOM from 'react-dom'
import Registry from '../state/registry'
import packageJson from '../../../../../package.json'
import { Plugin } from '@remixproject/engine'
import { EventEmitter } from 'events'
import { IconRecord, RemixUiVerticalIconsPanel } from '@remix-ui/vertical-icons-panel'
import { Profile } from '@remixproject/plugin-utils'
import { timeStamp } from 'console'
const profile = {
name: 'menuicons',
displayName: 'Vertical Icons',
description: '',
version: packageJson.version,
methods: ['select', 'unlinkContent', 'linkContent'],
events: ['toggleContent', 'showContent']
}
export class VerticalIcons extends Plugin {
events: EventEmitter
htmlElement: HTMLDivElement
icons: Record<string, IconRecord> = {}
constructor () {
super(profile)
this.events = new EventEmitter()
this.htmlElement = document.createElement('div')
this.htmlElement.setAttribute('id', 'icon-panel')
}
renderComponent () {
const fixedOrder = ['filePanel', 'solidity','udapp', 'debugger', 'solidityStaticAnalysis', 'solidityUnitTesting', 'pluginManager']
const divived = Object.values(this.icons).map((value) => { return {
...value,
isRequired: fixedOrder.indexOf(value.profile.name) > -1
}}).sort((a,b) => {
return a.timestamp - b.timestamp
})
const required = divived.filter((value) => value.isRequired).sort((a,b) => {
return fixedOrder.indexOf(a.profile.name) - fixedOrder.indexOf(b.profile.name)
})
const sorted: IconRecord[] = [
...required,
...divived.filter((value) => { return !value.isRequired })
]
ReactDOM.render(
<RemixUiVerticalIconsPanel
verticalIconsPlugin={this}
icons={sorted}
/>,
this.htmlElement)
}
onActivation () {
this.renderComponent()
this.on('sidePanel', 'focusChanged', (name: string) => {
Object.keys(this.icons).map((o) => {
this.icons[o].active = false
})
this.icons[name].active = true
this.renderComponent()
})
}
async linkContent (profile: Profile) {
if (!profile.icon) return
if (!profile.kind) profile.kind = 'none'
this.icons[profile.name] = {
profile: profile,
active: false,
canbeDeactivated: await this.call('manager', 'canDeactivate', this.profile, profile),
timestamp: Date.now()
}
this.renderComponent()
}
unlinkContent (profile: Profile) {
delete this.icons[profile.name]
this.renderComponent()
}
async activateHome() {
await this.call('manager', 'activatePlugin', 'home')
await this.call('tabs', 'focus', 'home')
}
/**
* Set an icon as active
* @param {string} name Name of profile of the module to activate
*/
select (name: string) {
// TODO: Only keep `this.emit` (issue#2210)
console.log(name, this)
this.emit('showContent', name)
this.events.emit('showContent', name)
}
/**
* Toggles the side panel for plugin
* @param {string} name Name of profile of the module to activate
*/
toggle (name: string) {
// TODO: Only keep `this.emit` (issue#2210)
this.emit('toggleContent', name)
this.events.emit('toggleContent', name)
}
render () {
return this.htmlElement
}
}

@ -50,6 +50,10 @@ export class RemixAppManager extends PluginManager {
return isNative(from.name) return isNative(from.name)
} }
async canDeactivate(from,to) {
return this.canDeactivatePlugin(from, to)
}
async deactivatePlugin (name) { async deactivatePlugin (name) {
const [to, from] = [ const [to, from] = [
await this.getProfile(name), await this.getProfile(name),

@ -31,7 +31,7 @@ export class WalkthroughService extends Plugin {
position: 'right' position: 'right'
}, },
{ {
element: document.querySelector('#compileIcons'), element: document.querySelector('#verticalIconsKindsolidity'),
title: 'Solidity Compiler', title: 'Solidity Compiler',
intro: 'Having selected a .sol file in the File Explorers (the icon above), compile it with the Solidity Compiler.', intro: 'Having selected a .sol file in the File Explorers (the icon above), compile it with the Solidity Compiler.',
tooltipClass: 'bg-light text-dark', tooltipClass: 'bg-light text-dark',

@ -35,7 +35,8 @@ export class CompilerImports extends Plugin {
isExternalUrl (url) { isExternalUrl (url) {
const handlers = this.urlResolver.getHandlers() const handlers = this.urlResolver.getHandlers()
return handlers.some(handler => handler.match(url)) // we filter out "npm" because this will be recognized as internal url although it's not the case.
return handlers.filter((handler) => handler.type !== 'npm').some(handler => handler.match(url))
} }
/** /**

@ -3,10 +3,9 @@ import React from 'react'
export const fileChangedToastMsg = (from: string, path: string) => ( export const fileChangedToastMsg = (from: string, path: string) => (
<div><i className="fas fa-exclamation-triangle text-danger mr-1"></i> <div><i className="fas fa-exclamation-triangle text-danger mr-1"></i>
<span> <span>
{from} {from} <span className="font-weight-bold text-warning">
<span className="font-weight-bold text-warning">
is modifying is modifying
</span>{path} </span> {path}
</span> </span>
</div> </div>
) )
@ -52,4 +51,4 @@ export const sourceVerificationNotAvailableToastMsg = () => (
<div> <div>
<b>Source verification plugin not activated or not available.</b> continuing <i>without</i> source code debugging. <b>Source verification plugin not activated or not available.</b> continuing <i>without</i> source code debugging.
</div> </div>
) )

@ -36,7 +36,7 @@
.remixui_home_rightPanel { .remixui_home_rightPanel {
right: 0; right: 0;
position: absolute; position: absolute;
z-index: 3; z-index: 3000;
} }
.remixui_home_remixHomeMedia { .remixui_home_remixHomeMedia {
overflow-y: auto; overflow-y: auto;

@ -80,6 +80,7 @@ export const RemixUiHomeTab = (props: RemixUiHomeTabProps) => {
const remiAudioEl = useRef(null) const remiAudioEl = useRef(null)
const inputValue = useRef(null) const inputValue = useRef(null)
const rightPanel = useRef(null)
useEffect(() => { useEffect(() => {
plugin.call('theme', 'currentTheme').then((theme) => { plugin.call('theme', 'currentTheme').then((theme) => {
@ -97,7 +98,7 @@ export const RemixUiHomeTab = (props: RemixUiHomeTabProps) => {
window.addEventListener('click', (event) => { window.addEventListener('click', (event) => {
const target = event.target as Element const target = event.target as Element
const id = target.id const id = target.id
if (id !== 'remixIDEHomeTwitterbtn' && id !== 'remixIDEHomeMediumbtn') { if (id !== 'remixIDEHomeTwitterbtn' && id !== 'remixIDEHomeMediumbtn' && !rightPanel.current.contains(event.target)) {
// todo check event.target // todo check event.target
setState(prevState => { return { ...prevState, showMediaPanel: 'none' } }) setState(prevState => { return { ...prevState, showMediaPanel: 'none' } })
} }
@ -266,7 +267,7 @@ export const RemixUiHomeTab = (props: RemixUiHomeTabProps) => {
</p> </p>
<p className="mb-1"> <p className="mb-1">
<i className="mr-2 far fa-file-alt"></i> <i className="mr-2 far fa-file-alt"></i>
<label className="ml-1 remixui_home_labelIt remixui_home_bigLabelSize} remixui_home_text" htmlFor="openFileInput"> <label className="ml-1 remixui_home_labelIt remixui_home_bigLabelSize remixui_home_text" htmlFor="openFileInput">
Open Files Open Files
</label> </label>
<input title="open file" type="file" id="openFileInput" onChange={(event) => { <input title="open file" type="file" id="openFileInput" onChange={(event) => {
@ -334,9 +335,14 @@ export const RemixUiHomeTab = (props: RemixUiHomeTabProps) => {
}} }}
></button> ></button>
</div> </div>
<div className="mr-3 d-flex bg-light remixui_home_panels" style={ { visibility: state.showMediaPanel === 'none' ? 'hidden' : 'visible' } } id="remixIDEMediaPanels"> <div
className="mr-3 d-flex bg-light remixui_home_panels"
style={ { visibility: state.showMediaPanel === 'none' ? 'hidden' : 'visible' } }
id="remixIDEMediaPanels"
ref={rightPanel}
>
<div id="remixIDE_MediumBlock" className="p-2 mx-1 mt-3 mb-0 remixui_home_remixHomeMedia" style={ { maxHeight: maxHeight } }> <div id="remixIDE_MediumBlock" className="p-2 mx-1 mt-3 mb-0 remixui_home_remixHomeMedia" style={ { maxHeight: maxHeight } }>
<div id="medium-widget" className="px-3 remixui_home_media" hidden={state.showMediaPanel !== 'medium'} style={ { maxHeight: elHeight } }> <div id="medium-widget" className="px-3 remixui_home_media" hidden={state.showMediaPanel !== 'medium'} style={ { maxHeight: '10000px' } }>
<div <div
id="retainable-rss-embed" id="retainable-rss-embed"
data-rss="https://medium.com/feed/remix-ide" data-rss="https://medium.com/feed/remix-ide"
@ -353,7 +359,7 @@ export const RemixUiHomeTab = (props: RemixUiHomeTabProps) => {
<div id="remixIDE_TwitterBlock" className="p-2 mx-1 mt-3 mb-0 remixui_home_remixHomeMedia" hidden={state.showMediaPanel !== 'twitter'} style={ { maxHeight: maxHeight, marginRight: '28px' } } > <div id="remixIDE_TwitterBlock" className="p-2 mx-1 mt-3 mb-0 remixui_home_remixHomeMedia" hidden={state.showMediaPanel !== 'twitter'} style={ { maxHeight: maxHeight, marginRight: '28px' } } >
<div className="remixui_home_media" style={ { minHeight: elHeight } } > <div className="remixui_home_media" style={ { minHeight: elHeight } } >
<a className="twitter-timeline" <a className="twitter-timeline"
data-width="330" data-width="375"
data-theme={ state.themeQuality.name } data-theme={ state.themeQuality.name }
data-chrome="nofooter noheader transparent" data-chrome="nofooter noheader transparent"
data-tweet-limit="18" data-tweet-limit="18"

@ -20,7 +20,7 @@ const RemixUIPanelHeader = (props: RemixPanelProps) => {
return ( return (
<header className='swapitHeader'><h6 data-id='sidePanelSwapitTitle'>{plugin?.profile.displayName || plugin?.profile.name}</h6> <header className='swapitHeader'><h6 data-id='sidePanelSwapitTitle'>{plugin?.profile.displayName || plugin?.profile.name}</h6>
{plugin?.profile.documentation ? (<a href={plugin.profile.documentation} className="titleInfo" title="link to documentation" target="_blank" rel="noreferrer"><i aria-hidden="true" className="fas fa-book"></i></a>) : ''} {plugin?.profile.documentation ? (<a href={plugin.profile.documentation} className="titleInfo mb-2" title="link to documentation" target="_blank" rel="noreferrer"><i aria-hidden="true" className="fas fa-book"></i></a>) : ''}
</header>) </header>)
} }

@ -6,4 +6,4 @@ export type PluginRecord = {
active: boolean active: boolean
class?: string class?: string
minimized?: boolean minimized?: boolean
} }

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

@ -1 +1,2 @@
export * from './lib/remix-ui-vertical-icons-panel' export { default as RemixUiVerticalIconsPanel } from './lib/remix-ui-vertical-icons-panel'
export { IconRecord } from './lib/types'

@ -1,10 +1,5 @@
/* eslint-disable @typescript-eslint/no-unused-vars */ import React from 'react'
// eslint-disable-next-line no-use-before-define import { BadgeStatus } from './Icon'
import { VerticalIcons } from 'libs/remix-ui/vertical-icons-panel/types/vertical-icons-panel'
// eslint-disable-next-line no-use-before-define
import React, { Fragment, useEffect, useReducer } from 'react'
import { iconBadgeReducer, IconBadgeReducerAction } from '../reducers/iconBadgeReducer'
import { BadgeStatus, IconProfile, IconStatus } from './Icon'
interface BadgeProps { interface BadgeProps {
badgeStatus?: BadgeStatus badgeStatus?: BadgeStatus
@ -65,4 +60,4 @@ function Badge ({ badgeStatus }: BadgeProps) {
) )
} }
export default Badge export default Badge

@ -1,5 +1,3 @@
/* eslint-disable no-use-before-define */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import React from 'react' import React from 'react'
function BasicLogo () { function BasicLogo () {

@ -3,14 +3,23 @@ import React, { MutableRefObject } from 'react'
export interface ChevronProps { export interface ChevronProps {
divElementRef: MutableRefObject<any> divElementRef: MutableRefObject<any>
cssRule: string cssRule: string,
direction: string
} }
function Chevron (props: ChevronProps) { function Chevron (props: ChevronProps) {
const click = () => {
if (props.direction === 'down') {
props.divElementRef.current.scrollBy({ top: 40, behavior: 'smooth' })
} else {
props.divElementRef.current.scrollBy({ top: -40, behavior: 'smooth' })
}
}
return ( return (
<> <>
{ props.divElementRef.current && props.divElementRef.current.scrollHeight > props.divElementRef.current.clientHeight { props.divElementRef.current && props.divElementRef.current.scrollHeight > props.divElementRef.current.clientHeight
? <i className={props.cssRule}></i> : null } ? <i onClick={click} className={props.cssRule}></i> : null }
</> </>
) )
} }

@ -1,41 +0,0 @@
import { VerticalIcons } from 'libs/remix-ui/vertical-icons-panel/types/vertical-icons-panel'
// eslint-disable-next-line no-use-before-define
import React, { Fragment } from 'react'
import Icon from './Icon'
interface DebuggerProps {
verticalIconsPlugin: VerticalIcons
itemContextAction: (e: any, name: string, documentation: string) => Promise<void>
addActive: (name: string) => void
removeActive: () => void
}
function Debugger ({ verticalIconsPlugin, itemContextAction, addActive, removeActive }: DebuggerProps) {
return (
<Fragment>
{verticalIconsPlugin.targetProfileForChange &&
Object.keys(verticalIconsPlugin.targetProfileForChange).length
? Object.keys(verticalIconsPlugin.targetProfileForChange)
.filter(p => p === 'debugger')
.map(p => (
<div id="debuggingIcons" data-id="verticalIconsDebuggingIcons" key={
verticalIconsPlugin.targetProfileForChange[p].displayName
}>
<Icon
profile={verticalIconsPlugin.targetProfileForChange[p]}
verticalIconPlugin={verticalIconsPlugin}
contextMenuAction={itemContextAction}
addActive={addActive}
removeActive={removeActive}
key={
verticalIconsPlugin.targetProfileForChange[p].displayName
}
/>
</div>
))
: null}
</Fragment>
)
}
export default Debugger

@ -1,59 +0,0 @@
import { VerticalIcons } from 'libs/remix-ui/vertical-icons-panel/types/vertical-icons-panel'
// eslint-disable-next-line no-use-before-define
import React, { Fragment, useEffect, useRef } from 'react'
import Icon from './Icon'
interface FilePanelProps {
verticalIconsPlugin: VerticalIcons
itemContextAction: (e: any, name: string, documentation: string) => Promise<void>
addActive: (name: string) => void
removeActive: () => void
}
function FilePanel ({ verticalIconsPlugin, itemContextAction, addActive, removeActive }: FilePanelProps) {
const filePanelRef = useRef(null)
function onThemeChanged (themeType: any) {
const invert = themeType === 'dark' ? 1 : 0
// @ts-ignore
const active = filePanelRef.current && filePanelRef.current.querySelector('.active')
if (active) {
// @ts-ignore
const image = filePanelRef.current.querySelector('.remixui_image')
image.style.setProperty('filter', `invert(${invert})`)
}
}
useEffect(() => {
const themeModule = verticalIconsPlugin.registry.get('themeModule').api
themeModule.events.on('themeChanged', (theme: any) => {
onThemeChanged(theme.quality)
})
}, [])
return (
<Fragment>
{verticalIconsPlugin.targetProfileForChange &&
Object.keys(verticalIconsPlugin.targetProfileForChange).length
? Object.keys(verticalIconsPlugin.targetProfileForChange)
.filter(p => p === 'filePanel')
.map(p => (
<div id="fileExplorerIcons" key={
verticalIconsPlugin.targetProfileForChange[p].displayName
} data-id="verticalIconsFileExplorerIcons"
ref={filePanelRef}
>
<Icon
profile={verticalIconsPlugin.targetProfileForChange[p]}
verticalIconPlugin={verticalIconsPlugin}
contextMenuAction={itemContextAction}
addActive={addActive}
removeActive={removeActive}
/>
</div>
))
: null}
</Fragment>
)
}
export default FilePanel

@ -1,19 +1,15 @@
/* eslint-disable no-use-before-define */ import React from 'react'
/* eslint-disable @typescript-eslint/no-unused-vars */
import { VerticalIcons } from 'libs/remix-ui/vertical-icons-panel/types/vertical-icons-panel'
import React, { ReactNode } from 'react'
import BasicLogo from './BasicLogo' import BasicLogo from './BasicLogo'
interface HomeProps { interface HomeProps {
verticalIconPlugin: VerticalIcons verticalIconPlugin: any
} }
function Home ({ verticalIconPlugin }: HomeProps) { function Home ({ verticalIconPlugin }: HomeProps) {
return ( return (
<div <div
className="pl-1 mt-2 remixui_homeIcon" className="mt-3 my-1 remixui_homeIcon"
onClick={async () => verticalIconPlugin.activateHome()} onClick={async () => await verticalIconPlugin.activateHome()}
// @ts-ignore {...{ plugin: 'home'}}
plugin="home"
title="Home" title="Home"
data-id="verticalIconsHomeIcon" data-id="verticalIconsHomeIcon"
id="verticalIconsHomeIcon" id="verticalIconsHomeIcon"

@ -1,9 +1,10 @@
import VerticalIconsContextMenu from '../vertical-icons-context-menu' import VerticalIconsContextMenu from '../vertical-icons-context-menu'
// eslint-disable-next-line no-use-before-define // eslint-disable-next-line no-use-before-define
import React, { Fragment, SyntheticEvent, useEffect, useReducer, useRef, useState } from 'react' import React, { Fragment, SyntheticEvent, useEffect, useReducer, useRef, useState } from 'react'
import { VerticalIcons } from 'libs/remix-ui/vertical-icons-panel/types/vertical-icons-panel'
import Badge from './Badge' import Badge from './Badge'
import { iconBadgeReducer, IconBadgeReducerAction } from '../reducers/iconBadgeReducer' import { iconBadgeReducer, IconBadgeReducerAction } from '../reducers/iconBadgeReducer'
import { Plugin } from '@remixproject/engine'
import { IconRecord } from '../types'
export interface IconStatus { export interface IconStatus {
key: string key: string
@ -16,26 +17,11 @@ export interface BadgeStatus extends IconStatus {
text: string text: string
} }
export interface IconProfile {
description: string
displayName: string
documentation: string
events: any[]
icon: string
kind: string
location: string
methods: string[]
name: string
version: string
tooltip?: string
}
interface IconProps { interface IconProps {
verticalIconPlugin: VerticalIcons verticalIconPlugin: Plugin
profile: IconProfile iconRecord: IconRecord
contextMenuAction: (evt: any, profileName: string, documentation: string) => void contextMenuAction: (evt: any, profileName: string, documentation: string) => void
addActive: (profileName: string) => void theme: string
removeActive: () => void
} }
const initialState = { const initialState = {
@ -46,18 +32,15 @@ const initialState = {
pluginName: '' pluginName: ''
} }
function Icon ({ const Icon = ({
profile, iconRecord,
verticalIconPlugin, verticalIconPlugin,
contextMenuAction, contextMenuAction,
addActive, theme
removeActive }: IconProps) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars const { displayName, name, icon, documentation } = iconRecord.profile
}: IconProps) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { tooltip, displayName, name, kind, icon, documentation } = profile
const [title] = useState(() => { const [title] = useState(() => {
const temp = tooltip || displayName || name const temp = null || displayName || name
return temp.replace(/^\w/, (word: string) => word.toUpperCase()) return temp.replace(/^\w/, (word: string) => word.toUpperCase())
}) })
const [links, setLinks] = useState<{ Documentation: string, CanDeactivate: boolean }>( const [links, setLinks] = useState<{ Documentation: string, CanDeactivate: boolean }>(
@ -71,11 +54,9 @@ function Icon ({
const [showContext, setShowContext] = useState(false) const [showContext, setShowContext] = useState(false)
const [canDeactivate] = useState(false) const [canDeactivate] = useState(false)
const iconRef = useRef<any>(null) const iconRef = useRef<any>(null)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const handleContextMenu = (e: SyntheticEvent & PointerEvent) => { const handleContextMenu = (e: SyntheticEvent & PointerEvent) => {
const deactivationState = verticalIconPlugin.appManager const deactivationState = iconRecord.canbeDeactivated
.canDeactivatePlugin(verticalIconPlugin.defaultProfile, { name })
if (documentation && documentation.length > 0 && deactivationState) { if (documentation && documentation.length > 0 && deactivationState) {
setLinks({ Documentation: documentation, CanDeactivate: deactivationState }) setLinks({ Documentation: documentation, CanDeactivate: deactivationState })
} else { } else {
@ -102,21 +83,13 @@ function Icon ({
}, []) }, [])
return ( return (
<Fragment> <>
<div <div
className={name === 'pluginManager' ? 'remixui_icon ml-2 mt-2 mr-2 mb-0 pl-1' : 'remixui_icon m-2 pl-1'} className={`remixui_icon m-2 pl-1`}
onLoad={() => {
if (name === 'filePanel') {
addActive(name)
}
}}
onClick={() => { onClick={() => {
removeActive() (verticalIconPlugin as any).toggle(name)
addActive(name)
verticalIconPlugin.toggle(name)
}} }}
// @ts-ignore {...{plugin: name}}
plugin={name}
title={title} title={title}
onContextMenu={(e: any) => { onContextMenu={(e: any) => {
e.preventDefault() e.preventDefault()
@ -127,11 +100,10 @@ function Icon ({
id={`verticalIconsKind${name}`} id={`verticalIconsKind${name}`}
ref={iconRef} ref={iconRef}
> >
<img className="remixui_image" src={icon} alt={name} /> <img className={`${theme === 'dark' ? 'invert' : ''} ${theme} remixui_image ${iconRecord.active ? `selected-${theme}`:''}`} src={icon} alt={name} />
{ badgeStatus && badgeStatus.pluginName === name ? (
<Badge <Badge
badgeStatus={badgeStatus} badgeStatus={badgeStatus}
/>) : null } />
</div> </div>
{showContext ? ( {showContext ? (
<VerticalIconsContextMenu <VerticalIconsContextMenu
@ -145,7 +117,7 @@ function Icon ({
contextMenuAction={contextMenuAction} contextMenuAction={contextMenuAction}
/> />
) : null} ) : null}
</Fragment> </>
) )
} }

@ -0,0 +1,33 @@
/* eslint-disable no-use-before-define */
/* eslint-disable @typescript-eslint/no-unused-vars */
import React, { useEffect } from 'react'
import { IconRecord } from '../types'
import Icon from './Icon'
interface OtherIconsProps {
verticalIconsPlugin: any
itemContextAction: (e: any, name: string, documentation: string) => Promise<void>
icons: IconRecord[]
theme: string
}
function IconList ({ verticalIconsPlugin, itemContextAction, icons, theme }: OtherIconsProps) {
return (
<div id="otherIcons">
{
icons
.map(p => (
<Icon
theme={theme}
iconRecord={p}
verticalIconPlugin={verticalIconsPlugin}
contextMenuAction={itemContextAction}
key={
p.profile.name
}
/>
))}
</div>
)
}
export default IconList

@ -1,43 +0,0 @@
/* eslint-disable no-use-before-define */
/* eslint-disable @typescript-eslint/no-unused-vars */
import { VerticalIcons } from 'libs/remix-ui/vertical-icons-panel/types/vertical-icons-panel'
import React from 'react'
import Icon from './Icon'
function customFilter (p: string) {
if (p !== 'settings' && p !== 'pluginManager' &&
p !== 'filePanel' && p !== 'debugger' &&
p !== 'compiler' && p !== 'solidity' &&
p !== 'udapp' && p !== 'testing' && p !== 'solidityStaticAnalysis') return true
return false
}
interface OtherIconsProps {
verticalIconsPlugin: VerticalIcons
itemContextAction: (e: any, name: string, documentation: string) => Promise<void>
addActive: (name: string) => void
removeActive: () => void
}
function OtherIcons ({ verticalIconsPlugin, itemContextAction, addActive, removeActive }: OtherIconsProps) {
return (
<div id="otherIcons">
{
Object.keys(verticalIconsPlugin.targetProfileForChange)
.filter(customFilter)
.map(p => (
<Icon
profile={verticalIconsPlugin.targetProfileForChange[p]}
verticalIconPlugin={verticalIconsPlugin}
contextMenuAction={itemContextAction}
addActive={addActive}
removeActive={removeActive}
key={
verticalIconsPlugin.targetProfileForChange[p].displayName
}
/>
))}
</div>
)
}
export default OtherIcons

@ -1,36 +0,0 @@
/* eslint-disable no-use-before-define */
/* eslint-disable @typescript-eslint/no-unused-vars */
import { VerticalIcons } from 'libs/remix-ui/vertical-icons-panel/types/vertical-icons-panel'
import React, { Fragment } from 'react'
import Icon from './Icon'
interface PluginManagerProps {
verticalIconsPlugin: VerticalIcons
itemContextAction: (e: any, name: string, documentation: string) => Promise<void>
addActive: (name: string) => void
removeActive: () => void
}
function PluginManager ({ verticalIconsPlugin, itemContextAction, addActive, removeActive }: PluginManagerProps) {
return (
<Fragment>
{Object.keys(verticalIconsPlugin.targetProfileForChange)
.filter(p => p === 'pluginManager')
.map(p => (
<Icon
profile={verticalIconsPlugin.targetProfileForChange[p]}
verticalIconPlugin={verticalIconsPlugin}
contextMenuAction={itemContextAction}
addActive={addActive}
removeActive={removeActive}
key={
verticalIconsPlugin.targetProfileForChange[p]
.displayName
}
/>
))}
</Fragment>
)
}
export default PluginManager

@ -1,72 +0,0 @@
import { VerticalIcons } from 'libs/remix-ui/vertical-icons-panel/types/vertical-icons-panel'
// eslint-disable-next-line no-use-before-define
import React, { Fragment, MutableRefObject } from 'react'
import { Chevron } from './Chevron'
import Debugger from './Debugger'
import FilePanel from './FilePanel'
import PluginManager from './PluginManager'
import Solidity from './Solidity'
import SolidityStaticAnalysis from './SolidityStaticAnalysis'
import Udapp from './Udapp'
interface RequiredSectionProps {
verticalIconsPlugin: VerticalIcons
itemContextAction: (e: any, name: string, documentation: string) => Promise<void>
addActive: (name: string) => void
removeActive: () => void
scrollableRef: MutableRefObject<any>
}
function RequiredSection ({ verticalIconsPlugin, itemContextAction, addActive, removeActive, scrollableRef }: RequiredSectionProps) {
return (
<Fragment>
<FilePanel
verticalIconsPlugin={verticalIconsPlugin}
addActive={addActive}
removeActive={removeActive}
itemContextAction={itemContextAction}
/>
<PluginManager
verticalIconsPlugin={verticalIconsPlugin}
addActive={addActive}
removeActive={removeActive}
itemContextAction={itemContextAction}
/>
<Solidity
verticalIconsPlugin={verticalIconsPlugin}
addActive={addActive}
removeActive={removeActive}
itemContextAction={itemContextAction}
/>
<Udapp
verticalIconsPlugin={verticalIconsPlugin}
addActive={addActive}
removeActive={removeActive}
itemContextAction={itemContextAction}
/>
<SolidityStaticAnalysis
verticalIconsPlugin={verticalIconsPlugin}
addActive={addActive}
removeActive={removeActive}
itemContextAction={itemContextAction}
/>
<Debugger
verticalIconsPlugin={verticalIconsPlugin}
addActive={addActive}
removeActive={removeActive}
itemContextAction={itemContextAction}
/>
{
scrollableRef.current && scrollableRef.current.scrollHeight > scrollableRef.current.clientHeight
? (
<Chevron
divElementRef={scrollableRef}
cssRule={'fa fa-chevron-up remixui_icon-chevron mt-0 mb-0 ml-1 pl-3'}
/>
) : null
}
</Fragment>
)
}
export { RequiredSection }

@ -1,60 +0,0 @@
/* eslint-disable no-use-before-define */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { VerticalIcons } from 'libs/remix-ui/vertical-icons-panel/types/vertical-icons-panel'
import React, { useEffect, useRef } from 'react'
import { Chevron } from './Chevron'
import Icon from './Icon'
interface SettingsProps {
verticalIconsPlugin: VerticalIcons
itemContextAction: (e: any, name: string, documentation: string) => Promise<void>
addActive: (name: string) => void
removeActive: () => void
scrollableRef: React.MutableRefObject<any>
}
function Settings ({ scrollableRef, verticalIconsPlugin, itemContextAction, addActive, removeActive }: SettingsProps) {
const settingsRef = useRef(null)
function onThemeChanged (themeType: any) {
const invert = themeType === 'dark' ? 1 : 0
// @ts-ignore
const active = settingsRef.current.querySelector('.active')
if (active) {
// @ts-ignore
const image = settingsRef.current.querySelector('.remixui_image')
image.style.setProperty('filter', `invert(${invert})`)
}
}
useEffect(() => {
const themeModule = verticalIconsPlugin.registry.get('themeModule').api
themeModule.events.on('themeChanged', (theme: any) => {
onThemeChanged(theme.quality)
})
}, [])
return (
<div id="settingsIcons" className="remixui_settings mb-0 flex-grow-0" data-id="vertialIconsSettingsIcons" ref={settingsRef}>
{ scrollableRef.current && scrollableRef.current.scrollHeight > scrollableRef.current.clientHeight ? (<Chevron
divElementRef={scrollableRef}
cssRule={'fa fa-chevron-down remixui_icon-chevron mt-0 mb-0 ml-1 pl-3'}
/>) : null }
{Object.keys(verticalIconsPlugin.targetProfileForChange)
.filter(p => p === 'settings')
.map(p => (
<Icon
profile={verticalIconsPlugin.targetProfileForChange[p]}
verticalIconPlugin={verticalIconsPlugin}
contextMenuAction={itemContextAction}
addActive={addActive}
removeActive={removeActive}
key={
verticalIconsPlugin.targetProfileForChange[p]
.displayName
}
/>
))}
</div>
)
}
export default Settings

@ -1,41 +0,0 @@
import { VerticalIcons } from 'libs/remix-ui/vertical-icons-panel/types/vertical-icons-panel'
// eslint-disable-next-line no-use-before-define
import React, { Fragment } from 'react'
import Icon from './Icon'
interface SolidityProps {
verticalIconsPlugin: VerticalIcons
itemContextAction: (e: any, name: string, documentation: string) => Promise<void>
addActive: (name: string) => void
removeActive: () => void
}
function Solidity ({ verticalIconsPlugin, itemContextAction, addActive, removeActive }: SolidityProps) {
return (
<Fragment>
{verticalIconsPlugin.targetProfileForChange &&
Object.keys(verticalIconsPlugin.targetProfileForChange).length
? Object.keys(verticalIconsPlugin.targetProfileForChange)
.filter(p => p === 'solidity')
.map(p => (
<div id="compileIcons" key={
verticalIconsPlugin.targetProfileForChange[p].displayName
}>
<Icon
profile={verticalIconsPlugin.targetProfileForChange[p]}
verticalIconPlugin={verticalIconsPlugin}
contextMenuAction={itemContextAction}
addActive={addActive}
removeActive={removeActive}
key={
verticalIconsPlugin.targetProfileForChange[p].displayName
}
/>
</div>
))
: null}
</Fragment>
)
}
export default Solidity

@ -1,41 +0,0 @@
import { VerticalIcons } from 'libs/remix-ui/vertical-icons-panel/types/vertical-icons-panel'
// eslint-disable-next-line no-use-before-define
import React, { Fragment } from 'react'
import Icon from './Icon'
interface SolidityStaticAnalysisProps {
verticalIconsPlugin: VerticalIcons
itemContextAction: (e: any, name: string, documentation: string) => Promise<void>
addActive: (name: string) => void
removeActive: () => void
}
function SolidityStaticAnalysis ({ verticalIconsPlugin, itemContextAction, addActive, removeActive }: SolidityStaticAnalysisProps) {
return (
<Fragment>
{verticalIconsPlugin.targetProfileForChange &&
Object.keys(verticalIconsPlugin.targetProfileForChange).length
? Object.keys(verticalIconsPlugin.targetProfileForChange)
.filter(p => p === 'solidityStaticAnalysis')
.map(p => (
<div id="analysisIcons" className="remixui_iconContainer" key={
verticalIconsPlugin.targetProfileForChange[p].displayName
}>
<Icon
profile={verticalIconsPlugin.targetProfileForChange[p]}
verticalIconPlugin={verticalIconsPlugin}
contextMenuAction={itemContextAction}
addActive={addActive}
removeActive={removeActive}
key={
verticalIconsPlugin.targetProfileForChange[p].displayName
}
/>
</div>
))
: null}
</Fragment>
)
}
export default SolidityStaticAnalysis

@ -1,42 +0,0 @@
import { VerticalIcons } from 'libs/remix-ui/vertical-icons-panel/types/vertical-icons-panel'
// eslint-disable-next-line no-use-before-define
import React, { Fragment } from 'react'
import Icon from './Icon'
interface UdappProps {
verticalIconsPlugin: VerticalIcons
itemContextAction: (e: any, name: string, documentation: string) => Promise<void>
addActive: (name: string) => void
removeActive: () => void
}
function Udapp ({ verticalIconsPlugin, itemContextAction, addActive, removeActive }: UdappProps) {
return (
<Fragment>
{verticalIconsPlugin.targetProfileForChange &&
Object.keys(verticalIconsPlugin.targetProfileForChange).length
? Object.keys(verticalIconsPlugin.targetProfileForChange)
.filter(p => p === 'udapp')
.map(p => (
<div id="runIcons" data-id="verticalIconsKindUdapp" key={
verticalIconsPlugin.targetProfileForChange[p].displayName
}
>
<Icon
profile={verticalIconsPlugin.targetProfileForChange[p]}
verticalIconPlugin={verticalIconsPlugin}
contextMenuAction={itemContextAction}
addActive={addActive}
removeActive={removeActive}
key={
verticalIconsPlugin.targetProfileForChange[p].displayName
}
/>
</div>
))
: null}
</Fragment>
)
}
export default Udapp

@ -1,8 +1,5 @@
/* eslint-disable @typescript-eslint/no-unused-vars */ import { checkSpecialChars } from '@remix-ui/helper'
import helper from 'apps/remix-ide/src/lib/helper'
import { BadgeStatus, IconStatus } from '../components/Icon' import { BadgeStatus, IconStatus } from '../components/Icon'
import React, { MutableRefObject } from 'react'
import { VerticalIcons } from 'libs/remix-ui/vertical-icons-panel/types/vertical-icons-panel'
export type IconBadgeReducerAction = { export type IconBadgeReducerAction = {
readonly type: string readonly type: string
@ -14,32 +11,30 @@ export type IconBadgeReducerAction = {
* @param {String} name * @param {String} name
* @param {Object} status * @param {Object} status
*/ */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function setIconStatus (name: string, status: IconStatus) { function setIconStatus(name: string, status: IconStatus) {
if (status.key === 'none') return { ...status, text: '' } // remove status if (status.key === 'none') return { ...status, text: '' } // remove status
let text = '' let text = ''
let key = '' let key = ''
if (typeof status.key === 'number') { if (typeof status.key === 'number') {
key = status.key key = status.key
// eslint-disable-next-line @typescript-eslint/no-unused-vars
text = key text = key
} else key = helper.checkSpecialChars(status.key) ? '' : status.key } else key = checkSpecialChars(status.key) ? '' : status.key
let thisType = '' let thisType = ''
if (status.type === 'error') { if (status.type === 'error') {
thisType = 'danger' // to use with bootstrap thisType = 'danger' // to use with bootstrap
} else thisType = helper.checkSpecialChars(status.type) ? '' : status.type! } else thisType = checkSpecialChars(status.type) ? '' : status.type!
const title = helper.checkSpecialChars(status.title) ? '' : status.title const title = checkSpecialChars(status.title) ? '' : status.title
const pluginName = status.pluginName const pluginName = status.pluginName
return { title, type: thisType, key, text, pluginName } return { title, type: thisType, key, text, pluginName }
} }
export function iconBadgeReducer (state: BadgeStatus, action: IconBadgeReducerAction) { export function iconBadgeReducer(state: BadgeStatus, action: IconBadgeReducerAction) {
const { status, ref, verticalIconPlugin } = action.payload const { status } = action.payload
if (Object.keys(verticalIconPlugin.targetProfileForChange).includes(action.type)) {
const setStatus = setIconStatus(action.type, status) const setStatus = setIconStatus(action.type, status)
return setStatus return setStatus
}
return state
} }

@ -6,7 +6,8 @@ export type actionType = {
export function verticalScrollReducer (prevState: any, actionPayload: actionType) { export function verticalScrollReducer (prevState: any, actionPayload: actionType) {
if (actionPayload.type === 'resize') { if (actionPayload.type === 'resize') {
let { scrollHeight, clientHeight, scrollState } = actionPayload.payload const { scrollHeight, clientHeight } = actionPayload.payload
let { scrollState } = actionPayload.payload
if (scrollHeight > clientHeight) scrollState = true if (scrollHeight > clientHeight) scrollState = true
return { scrollHeight, clientHeight, scrollState } return { scrollHeight, clientHeight, scrollState }
} }

@ -4,6 +4,10 @@
height: 42px; height: 42px;
cursor: pointer; cursor: pointer;
} }
.remixui_homeIcon:hover {
box-shadow: 0px 0px 14px -7px;
}
.remixui_homeIcon svg path { .remixui_homeIcon svg path {
fill: var(--primary); fill: var(--primary);
} }
@ -16,13 +20,14 @@
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
} }
.remixui_icon:hover {
box-shadow: 0px 0px 14px -7px;
}
.remixui_icon { .remixui_icon {
cursor: pointer; cursor: pointer;
margin-bottom: 3px;
position: 12px;
width: 36px; width: 36px;
height: 36px; height: 36px;
padding: relative;
border-radius: 8px; border-radius: 8px;
} }
.remixui_icon img { .remixui_icon img {
@ -31,6 +36,16 @@
padding: 4px; padding: 4px;
filter: invert(0.5); filter: invert(0.5);
} }
.remixui_icon .selected-dark {
filter: invert(1) grayscale(1);
}
.remixui_icon .selected-light {
filter: invert(0) grayscale(1);
}
.remixui_image { .remixui_image {
} }
.remixui_icon svg { .remixui_icon svg {
@ -116,4 +131,8 @@
#menuitems { #menuitems {
list-style: none; list-style: none;
margin: 0px; margin: 0px;
}
.remixui_icon-chevron {
cursor: pointer;
} }

@ -1,5 +1,3 @@
/* eslint-disable no-use-before-define */
/* eslint-disable @typescript-eslint/no-unused-vars */
import React, { import React, {
Fragment, Fragment,
useEffect, useEffect,
@ -7,16 +5,16 @@ import React, {
useRef, useRef,
useState useState
} from 'react' } from 'react'
import { Plugin } from '@remixproject/engine'
import './remix-ui-vertical-icons-panel.css' import './remix-ui-vertical-icons-panel.css'
import OtherIcons from './components/OtherIcons' import IconList from './components/IconList'
import { VerticalIcons } from '../../types/vertical-icons-panel'
import Home from './components/Home' import Home from './components/Home'
import Settings from './components/Settings'
import { RequiredSection } from './components/RequiredSection'
import { verticalScrollReducer } from './reducers/verticalScrollReducer' import { verticalScrollReducer } from './reducers/verticalScrollReducer'
import { Chevron } from './components/Chevron'
import { IconRecord } from './types'
export interface RemixUiVerticalIconsPanelProps { export interface RemixUiVerticalIconsPanelProps {
verticalIconsPlugin: VerticalIcons verticalIconsPlugin: Plugin
icons: IconRecord[]
} }
const initialState = { const initialState = {
@ -25,95 +23,54 @@ const initialState = {
scrollState: false scrollState: false
} }
export function RemixUiVerticalIconsPanel ({ const RemixUiVerticalIconsPanel = ({
verticalIconsPlugin verticalIconsPlugin, icons
}: RemixUiVerticalIconsPanelProps) { }: RemixUiVerticalIconsPanelProps) => {
const scrollableRef = useRef<any>() const scrollableRef = useRef<any>()
const iconPanelRef = useRef<any>() const iconPanelRef = useRef<any>()
const [activateScroll, dispatchScrollAction] = useReducer(verticalScrollReducer, initialState) const [activateScroll, dispatchScrollAction] = useReducer(verticalScrollReducer, initialState)
const [theme, setTheme] = useState<string>('dark')
useEffect(() => {
const evaluateScrollability = (evt: any) => {
dispatchScrollAction({
type: 'resize',
payload: {
scrollHeight: document.querySelector('#remixuiScrollable')?.scrollHeight,
clientHeight: document.querySelector('#remixuiScrollable')?.clientHeight,
scrollState: false
}
})
}
addEventListener('resize', evaluateScrollability)
return () => {
removeEventListener('resize', evaluateScrollability)
}
})
function onThemeChanged (themeType: any) { const evaluateScrollability = () => {
const invert = themeType === 'dark' ? 1 : 0 dispatchScrollAction({
// @ts-ignore type: 'resize',
const active = iconPanelRef.current.querySelector('.active') payload: {
if (active) { scrollHeight: scrollableRef.current?.scrollHeight,
// @ts-ignore clientHeight: scrollableRef.current?.clientHeight,
const image = iconPanelRef.current.querySelector('.remixui_image') scrollState: false
image.style.setProperty('filter', `invert(${invert})`) }
}
}
function removeActive () {
// @ts-ignore
const images = iconPanelRef.current.querySelectorAll('.remixui_image')
images.forEach(function (im: any) {
im.style.setProperty('filter', 'invert(0.5)')
}) })
// remove active
// @ts-ignore
const currentActive = iconPanelRef.current.querySelector('.active')
if (currentActive) {
currentActive.classList.remove('active')
}
} }
function addActive (name: string) { useEffect(() => {
if (name === 'home') return window.addEventListener('resize', evaluateScrollability)
const themeType = verticalIconsPlugin.registry.get('themeModule').api.currentTheme().quality evaluateScrollability()
const invert = themeType === 'dark' ? 1 : 0 return () => {
const brightness = themeType === 'dark' ? '150' : '0' // should be >100 for icons with color window.removeEventListener('resize', evaluateScrollability)
// @ts-ignore
const nextActive = iconPanelRef.current.querySelector(`[plugin="${name}"]`)
if (nextActive) {
const image = nextActive.querySelector('.remixui_image')
nextActive.classList.add('active')
image.style.setProperty('filter', `invert(${invert}) grayscale(1) brightness(${brightness}%)`)
} }
} }, [])
async function itemContextAction (e: any, name: string, documentation: string) { useEffect(() => {
verticalIconsPlugin.appManager.deactivatePlugin(name) evaluateScrollability()
if (e.target.parentElement.classList.contains('active')) { },[icons, theme])
verticalIconsPlugin.select('filePanel')
}
verticalIconsPlugin.renderComponent()
}
useEffect(() => { useEffect(() => {
const themeModule = verticalIconsPlugin.registry.get('themeModule').api verticalIconsPlugin.call('theme', 'currentTheme').then((th: any) => {
themeModule.events.on('themeChanged', (theme: any) => { setTheme(th.quality)
onThemeChanged(theme.quality) })
verticalIconsPlugin.on('theme', 'themeChanged', (th: any) => {
setTheme(th.quality)
}) })
return () => { return () => {
themeModule.events.off('themeChanged') verticalIconsPlugin.off('theme', 'themeChanged')
} }
}, []) }, [])
useEffect(() => { async function itemContextAction (e: any, name: string, documentation: string) {
if (verticalIconsPlugin.targetProfileForChange && verticalIconsPlugin.targetProfileForChange.udapp) { verticalIconsPlugin.call('manager', 'deactivatePlugin', name)
const doWalkThroughEvent = new Event('doWalkThrough') }
document.dispatchEvent(doWalkThroughEvent)
}
}, [Object.keys(verticalIconsPlugin.targetProfileForChange).length])
return ( return (
<div id="iconsP" className="h-100"> <div id="iconsP" className="h-100">
@ -121,13 +78,22 @@ export function RemixUiVerticalIconsPanel ({
<Home verticalIconPlugin={verticalIconsPlugin} /> <Home verticalIconPlugin={verticalIconsPlugin} />
<div className={scrollableRef.current && scrollableRef.current.scrollHeight > scrollableRef.current.clientHeight <div className={scrollableRef.current && scrollableRef.current.scrollHeight > scrollableRef.current.clientHeight
? 'remixui_default-icons-container remixui_requiredSection' : activateScroll && activateScroll.scrollState ? 'remixui_default-icons-container remixui_requiredSection' : 'remixui_requiredSection'}> ? 'remixui_default-icons-container remixui_requiredSection' : activateScroll && activateScroll.scrollState ? 'remixui_default-icons-container remixui_requiredSection' : 'remixui_requiredSection'}>
<RequiredSection <IconList
theme={theme}
icons={icons.filter((p) => p.isRequired && p.profile.name !== 'pluginManager')}
verticalIconsPlugin={verticalIconsPlugin} verticalIconsPlugin={verticalIconsPlugin}
addActive={addActive}
removeActive={removeActive}
itemContextAction={itemContextAction} itemContextAction={itemContextAction}
scrollableRef={scrollableRef}
/> />
{
scrollableRef.current && scrollableRef.current.scrollHeight > scrollableRef.current.clientHeight
? (
<Chevron
direction='up'
divElementRef={scrollableRef}
cssRule={'fa fa-chevron-up remixui_icon-chevron mt-0 mb-0 ml-1 pl-3'}
/>
) : null
}
</div> </div>
<div <div
id="remixuiScrollable" id="remixuiScrollable"
@ -136,26 +102,29 @@ export function RemixUiVerticalIconsPanel ({
: activateScroll && activateScroll.scrollState ? 'remixui_default-icons-container remixui_scrollable-container remixui_scrollbar remixui_hide-scroll' : 'remixui_scrollable-container remixui_scrollbar remixui_hide-scroll'} : activateScroll && activateScroll.scrollState ? 'remixui_default-icons-container remixui_scrollable-container remixui_scrollbar remixui_hide-scroll' : 'remixui_scrollable-container remixui_scrollbar remixui_hide-scroll'}
ref={scrollableRef} ref={scrollableRef}
> >
<OtherIcons <IconList
theme={theme}
icons={icons.filter((p) => {return !p.isRequired && p.profile.name !== 'settings'})}
verticalIconsPlugin={verticalIconsPlugin} verticalIconsPlugin={verticalIconsPlugin}
addActive={addActive}
removeActive={removeActive}
itemContextAction={itemContextAction} itemContextAction={itemContextAction}
/> />
</div> </div>
{verticalIconsPlugin.targetProfileForChange && <div>
Object.keys(verticalIconsPlugin.targetProfileForChange).length ? ( { scrollableRef.current && scrollableRef.current.scrollHeight > scrollableRef.current.clientHeight ? (<Chevron
<Fragment> divElementRef={scrollableRef}
<Settings direction='down'
cssRule={'fa fa-chevron-down remixui_icon-chevron mt-0 mb-0 ml-1 pl-3'}
/>) : null }
<IconList
theme={theme}
icons={icons.filter((p) => p.profile.name === 'settings' || p.profile.name === 'pluginManager')}
verticalIconsPlugin={verticalIconsPlugin} verticalIconsPlugin={verticalIconsPlugin}
addActive={addActive}
removeActive={removeActive}
itemContextAction={itemContextAction} itemContextAction={itemContextAction}
scrollableRef={scrollableRef}
/> />
</Fragment> </div>
) : null}
</div> </div>
</div> </div>
) )
} }
export default RemixUiVerticalIconsPanel

@ -0,0 +1,10 @@
import { Profile } from '@remixproject/plugin-utils'
export type IconRecord = {
profile: Profile
active: boolean
class?: string
canbeDeactivated?: boolean
isRequired?: boolean
timestamp: number
}

@ -1,9 +1,5 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */ import { Plugin } from '@remixproject/engine'
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable no-use-before-define */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import React, { Fragment, useEffect, useRef } from 'react' import React, { Fragment, useEffect, useRef } from 'react'
import { VerticalIcons } from '../../types/vertical-icons-panel'
export interface VerticalIconsContextMenuProps extends React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement> { export interface VerticalIconsContextMenuProps extends React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
pageX: number pageX: number
@ -11,7 +7,7 @@ export interface VerticalIconsContextMenuProps extends React.DetailedHTMLProps<R
profileName: string profileName: string
links: { Documentation: string, CanDeactivate: boolean } links: { Documentation: string, CanDeactivate: boolean }
canBeDeactivated: boolean canBeDeactivated: boolean
verticalIconPlugin: VerticalIcons verticalIconPlugin: any
hideContextMenu: () => void hideContextMenu: () => void
contextMenuAction: (evt: any, profileName: string, documentation: string) => void contextMenuAction: (evt: any, profileName: string, documentation: string) => void
} }
@ -21,31 +17,22 @@ interface MenuLinksProps {
hide: () => void hide: () => void
profileName: string profileName: string
canBeDeactivated: boolean canBeDeactivated: boolean
verticalIconPlugin: VerticalIcons verticalIconPlugin: any
ref?: React.MutableRefObject<any> ref?: React.MutableRefObject<any>
toggle: (name: string) => void toggle: (name: string) => void
contextMenuAction: (evt: any, profileName: string, documentation: string) => void contextMenuAction: (evt: any, profileName: string, documentation: string) => void
} }
interface MenuProps { interface MenuProps {
verticalIconsPlugin: VerticalIcons verticalIconsPlugin: Plugin
profileName: string profileName: string
listItems: { Documentation: string, CanDeactivate: boolean } listItems: { Documentation: string, CanDeactivate: boolean }
hide: () => void hide: () => void
} }
const requiredModules = [ const VerticalIconsContextMenu = (props: VerticalIconsContextMenuProps) =>{
'manager', 'compilerArtefacts', 'compilerMetadata', 'contextualListener', 'editor', 'offsetToLineColumnConverter', 'network', 'theme',
'fileManager', 'contentImport', 'blockchain', 'web3Provider', 'scriptRunner', 'fetchAndCompile', 'mainPanel', 'hiddenPanel', 'sidePanel', 'menuicons',
'filePanel', 'terminal', 'settings', 'pluginManager', 'tabs', 'udapp', 'dGitProvider', 'solidity-logic']
const nativePlugins = ['vyper', 'workshops', 'debugger', 'remixd', 'menuicons', 'solidity', 'hardhat-provider']
function VerticalIconsContextMenu (props: VerticalIconsContextMenuProps) {
const menuRef = useRef(null) const menuRef = useRef(null)
useEffect(() => { ClickOutside(menuRef, props.hideContextMenu)
document.addEventListener('click', props.hideContextMenu)
return () => document.removeEventListener('click', props.hideContextMenu)
}, [])
useEffect(() => { useEffect(() => {
// @ts-ignore // @ts-ignore
menuRef.current.focus() menuRef.current.focus()
@ -54,7 +41,6 @@ function VerticalIconsContextMenu (props: VerticalIconsContextMenuProps) {
<div <div
id="menuItemsContainer" id="menuItemsContainer"
className="p-1 remixui_verticalIconContextcontainer bg-light shadow border" className="p-1 remixui_verticalIconContextcontainer bg-light shadow border"
onBlur={props.hideContextMenu}
style={{ style={{
left: props.pageX, left: props.pageX,
top: props.pageY, top: props.pageY,
@ -80,15 +66,15 @@ function VerticalIconsContextMenu (props: VerticalIconsContextMenuProps) {
} }
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
function MenuForLinks ({ const MenuForLinks = ({
listItems, listItems,
hide, hide,
profileName, profileName,
contextMenuAction contextMenuAction
}: MenuLinksProps) { }: MenuLinksProps) => {
return ( return (
<Fragment> <Fragment>
{listItems.CanDeactivate && !requiredModules.includes(profileName) {listItems.CanDeactivate
? <li ? <li
id="menuitemdeactivate" id="menuitemdeactivate"
onClick={(evt) => { onClick={(evt) => {
@ -118,4 +104,18 @@ function MenuForLinks ({
) )
} }
function ClickOutside(ref: React.MutableRefObject<HTMLElement>, hideFn: () => void) {
useEffect(() => {
function handleClickOutside(event: any) {
if (ref.current && !ref.current.contains(event.target)) {
hideFn()
}
}
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [ref]);
}
export default VerticalIconsContextMenu export default VerticalIconsContextMenu

@ -1,21 +1,19 @@
{ {
"extends": "../../../tsconfig.base.json", "extends": "../../../tsconfig.base.json",
"compilerOptions": { "compilerOptions": {
"jsx": "react-jsx", "jsx": "react",
"allowJs": true, "allowJs": true,
"esModuleInterop": true, "esModuleInterop": true,
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"resolveJsonModule": true
}, },
"files": [], "files": [],
"include": [], "include": [],
"references": [ "references": [
{ {
"path": "./tsconfig.lib.json" "path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
} }
] ]
} }

@ -2,8 +2,7 @@
"extends": "./tsconfig.json", "extends": "./tsconfig.json",
"compilerOptions": { "compilerOptions": {
"outDir": "../../../dist/out-tsc", "outDir": "../../../dist/out-tsc",
"types": ["node"], "types": ["node"]
"resolveJsonModule": true
}, },
"files": [ "files": [
"../../../node_modules/@nrwl/react/typings/cssmodule.d.ts", "../../../node_modules/@nrwl/react/typings/cssmodule.d.ts",

@ -1,111 +0,0 @@
/* eslint-disable @typescript-eslint/ban-types */
/* eslint-disable no-use-before-define */
import { Plugin } from '@remixproject/engine/lib/abstract'
import * as packageJson from '../../../../package.json'
import Registry from 'apps/remix-ide/src/app/state/registry'
import { RemixAppManager } from '@remix-ui/plugin-manager'
export type Kind =
| 'fileexplorer'
| 'compiler'
| 'udapp'
| 'testing'
| 'analysis'
| 'debugging'
| 'settings'
| 'none'
type IconKindType = {
kind: {}
}
interface defaultModuleProfile {
name: string
displayName: string
description: string
version: packageJson.version
methods: string[]
}
interface PassedProfile {
name: string
displayName: string
description: string
version: packageJson.version
methods: string[]
icon?: string
tooltip?: string
kind?: string
documentation?: string
}
interface targetProfileIcons {
profile: PassedProfile
}
export class VerticalIcons extends Plugin<any, any> {
events: EventEmitter
appManager: RemixAppManager
htmlElement: HTMLDivElement
icons: any
iconKind: {}
iconStatus: {}
defaultProfile: defaultModuleProfile
targetProfileForChange: any
targetProfileForRemoval: any
registry: Registry
keys: string[]
types: string[]
renderComponent(): void
linkContent(profile: any): void
unlinkContent(profile: any): void
listenOnStatus(profile: any): void
activateHome(): void
/**
* Add an icon to the map
* @param {ModuleProfile} profile The profile of the module
*/
addIcon({ kind, name, icon, displayName, tooltip, documentation }: any): void
/**
* resolve a classes list for @arg key
* @param {Object} key
* @param {Object} type
*/
resolveClasses(key: any, type: any): any
/**
* Set a new status for the @arg name
* @param {String} name
* @param {Object} status
*/
setIconStatus(name: string, status: any): void
/**
* Remove an icon from the map
* @param {ModuleProfile} profile The profile of the module
*/
removeIcon({ name }: any): void
/**
* Remove active for the current activated icons
*/
removeActive(): void
/**
* Add active for the new activated icon
* @param {string} name Name of profile of the module to activate
*/
addActive(name: string): void
/**
* Set an icon as active
* @param {string} name Name of profile of the module to activate
*/
select(name: string): void
/**
* Toggles the side panel for plugin
* @param {string} name Name of profile of the module to activate
*/
toggle(name: string): void
updateActivations(name: any): void
onThemeChanged(themeType: any): void
itemContextMenu(e: any, name: any, documentation: any): Promise<void>
render(): any
view: any
}
import EventEmitter = require('events')

@ -117,7 +117,7 @@ export class RemixURLResolver {
// If you don't find greeter.sol on ipfs gateway use local // If you don't find greeter.sol on ipfs gateway use local
// const req = 'http://localhost:8080/' + url // const req = 'http://localhost:8080/' + url
const response: AxiosResponse = await axios.get(req) const response: AxiosResponse = await axios.get(req)
return { content: response.data, cleanUrl: url } return { content: response.data, cleanUrl: url.replace('ipfs/', '') }
} catch (e) { } catch (e) {
throw e throw e
} }

@ -260,7 +260,7 @@ describe('testRunner', () => {
const content = fs.readFileSync(__dirname + '/example_1/greeter.sol', { encoding: 'utf8'}) const content = fs.readFileSync(__dirname + '/example_1/greeter.sol', { encoding: 'utf8'})
const expt: object = { const expt: object = {
content: content, content: content,
cleanUrl: 'ipfs/QmcuCKyokk9Z6f65ADAADNiS2R2xCjfRkv7mYBSWDwtA7M', cleanUrl: 'QmcuCKyokk9Z6f65ADAADNiS2R2xCjfRkv7mYBSWDwtA7M',
type: 'ipfs' type: 'ipfs'
} }
assert.deepEqual(results, expt) assert.deepEqual(results, expt)

Loading…
Cancel
Save