Merge pull request #1671 from ethereum/vertical-icons-panel-react

create vertical icons panel react lib
pull/1849/head
bunsenstraat 3 years ago committed by GitHub
commit 32a322bad7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      apps/remix-ide-e2e/src/helpers/init.ts
  2. 2
      apps/remix-ide-e2e/src/tests/debugger.test.ts
  3. 5
      apps/remix-ide-e2e/src/tests/gist.test.ts
  4. 1
      apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts
  5. 329
      apps/remix-ide/src/app/components/vertical-icons.js
  6. 4
      apps/remix-ide/src/walkthroughService.js
  7. 2
      libs/remix-ui/solidity-compiler/src/lib/compiler-container.tsx
  8. 2
      libs/remix-ui/solidity-compiler/src/lib/css/style.css
  9. 4
      libs/remix-ui/vertical-icons-panel/.babelrc
  10. 19
      libs/remix-ui/vertical-icons-panel/.eslintrc.json
  11. 5
      libs/remix-ui/vertical-icons-panel/.prettierrc
  12. 7
      libs/remix-ui/vertical-icons-panel/README.md
  13. 1
      libs/remix-ui/vertical-icons-panel/src/index.ts
  14. 68
      libs/remix-ui/vertical-icons-panel/src/lib/components/Badge.tsx
  15. 14
      libs/remix-ui/vertical-icons-panel/src/lib/components/BasicLogo.tsx
  16. 18
      libs/remix-ui/vertical-icons-panel/src/lib/components/Chevron.tsx
  17. 62
      libs/remix-ui/vertical-icons-panel/src/lib/components/Debugger.tsx
  18. 59
      libs/remix-ui/vertical-icons-panel/src/lib/components/FilePanel.tsx
  19. 26
      libs/remix-ui/vertical-icons-panel/src/lib/components/Home.tsx
  20. 134
      libs/remix-ui/vertical-icons-panel/src/lib/components/Icon.tsx
  21. 66
      libs/remix-ui/vertical-icons-panel/src/lib/components/OtherIcons.tsx
  22. 36
      libs/remix-ui/vertical-icons-panel/src/lib/components/PluginManager.tsx
  23. 67
      libs/remix-ui/vertical-icons-panel/src/lib/components/RequiredSection.tsx
  24. 60
      libs/remix-ui/vertical-icons-panel/src/lib/components/Settings.tsx
  25. 58
      libs/remix-ui/vertical-icons-panel/src/lib/components/Solidity.tsx
  26. 58
      libs/remix-ui/vertical-icons-panel/src/lib/components/SolidityStaticAnalysis.tsx
  27. 60
      libs/remix-ui/vertical-icons-panel/src/lib/components/Udapp.tsx
  28. 44
      libs/remix-ui/vertical-icons-panel/src/lib/reducers/iconBadgeReducer.ts
  29. 124
      libs/remix-ui/vertical-icons-panel/src/lib/remix-ui-vertical-icons-panel.css
  30. 139
      libs/remix-ui/vertical-icons-panel/src/lib/remix-ui-vertical-icons-panel.tsx
  31. 121
      libs/remix-ui/vertical-icons-panel/src/lib/vertical-icons-context-menu.tsx
  32. 21
      libs/remix-ui/vertical-icons-panel/tsconfig.json
  33. 14
      libs/remix-ui/vertical-icons-panel/tsconfig.lib.json
  34. 110
      libs/remix-ui/vertical-icons-panel/types/vertical-icons-panel.d.ts
  35. 5
      nx.json
  36. 66
      package-lock.json
  37. 3
      package.json
  38. 5
      tsconfig.base.json
  39. 1
      tsconfig.json
  40. 74
      workspace.json

@ -5,7 +5,7 @@ require('dotenv').config()
export default function (browser: NightwatchBrowser, callback: VoidFunction, url?: string, preloadPlugins = true): void {
browser
.url(url || 'http://127.0.0.1:8080')
.pause(5000)
.pause(6000)
.switchBrowserTab(0)
.waitForElementVisible('[id="remixTourSkipbtn"]')
.click('[id="remixTourSkipbtn"]')

@ -200,7 +200,7 @@ module.exports = {
browser
.addFile('test_jsGetTrace.js', { content: jsGetTrace })
.executeScript('remix.exeCurrent()')
.pause(1000)
.pause(3000)
.waitForElementContainsText('*[data-id="terminalJournal"]', '{"gas":"0x575f","return":"0x0000000000000000000000000000000000000000000000000000000000000000","structLogs":', 60000)
},
// depends on Should debug using generated sources

@ -72,7 +72,10 @@ module.exports = {
browser.clickLaunchIcon('home')
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('filePanel')
.scrollAndClick('*[data-id="landingPageImportFromGistButton"]')
.click('div[title="home"]')
.waitForElementVisible('button[data-id="landingPageImportFromGistButton"]')
.pause(1000)
.scrollAndClick('button[data-id="landingPageImportFromGistButton"]')
.waitForElementVisible('*[data-id="modalDialogModalTitle"]')
.assert.containsText('*[data-id="modalDialogModalTitle"]', 'Load a Gist')
.waitForElementVisible('*[data-id="modalDialogModalBody"]')

@ -40,6 +40,7 @@ module.exports = {
.setValue('*[data-id="modalDialogCustomPromptText"]', 'Remix is cool!')
.assert.elementNotPresent('*[data-id="settingsRemixRunSignMsgHash"]')
.assert.elementNotPresent('*[data-id="settingsRemixRunSignMsgSignature"]')
.pause(2000)
.modalFooterOKClick()
.waitForElementVisible('*[data-id="modalDialogContainer"]', 12000)
.assert.elementPresent('*[data-id="settingsRemixRunSignMsgHash"]')

@ -1,13 +1,14 @@
import * as packageJson from '../../../../../package.json'
// eslint-disable-next-line no-unused-vars
import { basicLogo } from '../ui/svgLogo'
var yo = require('yo-yo')
var csjs = require('csjs-inject')
var helper = require('../../lib/helper')
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'
// var helper = require('../../lib/helper')
const globalRegistry = require('../../global/registry')
const contextMenu = require('../ui/contextMenu')
const { Plugin } = require('@remixproject/engine')
const EventEmitter = require('events')
let VERTICALMENU_HANDLE
const profile = {
name: 'menuicons',
@ -23,170 +24,57 @@ export class VerticalIcons extends Plugin {
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 = globalRegistry
this.keys = ['succeed', 'edited', 'none', 'loading', 'failed']
this.types = ['error', 'warning', 'success', 'info', '']
}
renderComponent () {
ReactDOM.render(
<RemixUiVerticalIconsPanel
verticalIconsPlugin={this}
/>,
this.htmlElement)
}
const themeModule = globalRegistry.get('themeModule').api
themeModule.events.on('themeChanged', (theme) => {
this.onThemeChanged(theme.quality)
})
onActivation () {
this.renderComponent()
}
linkContent (profile) {
if (!profile.icon) return
this.addIcon(profile)
if (!profile.kind) profile.kind = 'none'
this.targetProfileForChange[profile.name] = profile
this.listenOnStatus(profile)
this.renderComponent()
}
unlinkContent (profile) {
this.targetProfileForRemoval = profile
this.removeIcon(profile)
}
listenOnStatus (profile) {
// the list of supported keys. 'none' will remove the status
const keys = ['edited', 'succeed', 'none', 'loading', 'failed']
const types = ['error', 'warning', 'success', 'info', '']
const fn = (status) => {
if (!types.includes(status.type) && status.type) throw new Error(`type should be ${keys.join()}`)
if (status.key === undefined) throw new Error('status key should be defined')
if (typeof status.key === 'string' && (!keys.includes(status.key))) {
throw new Error('key should contain either number or ' + keys.join())
}
this.setIconStatus(profile.name, status)
}
this.iconStatus[profile.name] = fn
this.on(profile.name, 'statusChanged', this.iconStatus[profile.name])
}
/**
* Add an icon to the map
* @param {ModuleProfile} profile The profile of the module
*/
addIcon ({ kind, name, icon, displayName, tooltip, documentation }) {
let title = (tooltip || displayName || name)
title = title.replace(/^\w/, c => c.toUpperCase())
this.icons[name] = yo`
<div
class="${css.icon} m-2"
onclick="${() => { this.toggle(name) }}"
plugin="${name}"
title="${title}"
oncontextmenu="${(e) => this.itemContextMenu(e, name, documentation)}"
data-id="verticalIconsKind${name}"
id="verticalIconsKind${name}"
>
<img class="image" src="${icon}" alt="${name}" />
</div>`
this.iconKind[kind || 'none'].appendChild(this.icons[name])
}
/**
* resolve a classes list for @arg key
* @param {Object} key
* @param {Object} type
*/
resolveClasses (key, type) {
let classes = css.status
switch (key) {
case 'succeed':
classes += ' fas fa-check-circle text-' + type + ' ' + css.statusCheck
break
case 'edited':
classes += ' fas fa-sync text-' + type + ' ' + css.statusCheck
break
case 'loading':
classes += ' fas fa-spinner text-' + type + ' ' + css.statusCheck
break
case 'failed':
classes += ' fas fa-exclamation-triangle text-' + type + ' ' + css.statusCheck
break
default: {
classes += ' badge badge-pill badge-' + type
}
}
return classes
}
/**
* Set a new status for the @arg name
* @param {String} name
* @param {Object} status
*/
setIconStatus (name, status) {
const el = this.icons[name]
if (!el) return
const statusEl = el.querySelector('i')
if (statusEl) {
el.removeChild(statusEl)
}
if (status.key === 'none') return // remove status
let text = ''
let key = ''
if (typeof status.key === 'number') {
key = status.key.toString()
text = key
} else key = helper.checkSpecialChars(status.key) ? '' : status.key
let type = ''
if (status.type === 'error') {
type = 'danger' // to use with bootstrap
} else type = helper.checkSpecialChars(status.type) ? '' : status.type
const title = helper.checkSpecialChars(status.title) ? '' : status.title
el.appendChild(yo`<i
title="${title}"
class="${this.resolveClasses(key, type)}"
aria-hidden="true"
>
${text}
</i>`)
el.classList.add(`${css.icon}`)
}
/**
* Remove an icon from the map
* @param {ModuleProfile} profile The profile of the module
*/
removeIcon ({ kind, name }) {
if (this.icons[name]) this.iconKind[kind || 'none'].removeChild(this.icons[name])
}
/**
* Remove active for the current activated icons
*/
removeActive () {
// reset filters
const images = this.view.querySelectorAll('.image')
images.forEach(function (im) {
im.style.setProperty('filter', 'invert(0.5)')
})
// remove active
const currentActive = this.view.querySelector('.active')
if (currentActive) {
currentActive.classList.remove('active')
}
}
/**
* Add active for the new activated icon
* @param {string} name Name of profile of the module to activate
*/
addActive (name) {
if (name === 'home') return
const themeType = globalRegistry.get('themeModule').api.currentTheme().quality
const invert = themeType === 'dark' ? 1 : 0
const brightness = themeType === 'dark' ? '150' : '0' // should be >100 for icons with color
const nextActive = this.view.querySelector(`[plugin="${name}"]`)
if (nextActive) {
const image = nextActive.querySelector('.image')
nextActive.classList.add('active')
image.style.setProperty('filter', `invert(${invert}) grayscale(1) brightness(${brightness}%)`)
}
removeIcon ({ name }) {
if (this.targetProfileForChange[name]) delete this.targetProfileForChange[name]
setTimeout(() => {
this.renderComponent()
}, 150)
}
/**
@ -194,28 +82,11 @@ export class VerticalIcons extends Plugin {
* @param {string} name Name of profile of the module to activate
*/
select (name) {
this.updateActivations(name)
// TODO: Only keep `this.emit` (issue#2210)
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) {
this.updateActivations(name)
// TODO: Only keep `this.emit` (issue#2210)
this.emit('toggleContent', name)
this.events.emit('toggleContent', name)
}
updateActivations (name) {
this.removeActive()
this.addActive(name)
}
onThemeChanged (themeType) {
const invert = themeType === 'dark' ? 1 : 0
const active = this.view.querySelector('.active')
@ -225,131 +96,17 @@ export class VerticalIcons extends Plugin {
}
}
async itemContextMenu (e, name, documentation) {
const actions = {}
if (await this.appManager.canDeactivatePlugin(profile, { name })) {
actions.Deactivate = () => {
// this.call('manager', 'deactivatePlugin', name)
this.appManager.deactivatePlugin(name)
if (e.target.parentElement.classList.contains('active')) {
this.select('filePanel')
}
}
}
const links = {}
if (documentation) {
links.Documentation = documentation
}
if (Object.keys(actions).length || Object.keys(links).length) {
VERTICALMENU_HANDLE && VERTICALMENU_HANDLE.hide(null, true)
VERTICALMENU_HANDLE = contextMenu(e, actions, links)
}
e.preventDefault()
e.stopPropagation()
/**
* 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 () {
const home = yo`
<div
class="m-1 mt-2 ${css.homeIcon}"
onclick="${async () => {
await this.appManager.activatePlugin('home')
this.call('tabs', 'focus', 'home')
}}"
plugin="home" title="Home"
data-id="verticalIconsHomeIcon"
id="verticalIconsHomeIcon"
>
${basicLogo()}
</div>
`
this.iconKind.fileexplorer = yo`<div id='fileExplorerIcons' data-id="verticalIconsFileExplorerIcons"></div>`
this.iconKind.compiler = yo`<div id='compileIcons'></div>`
this.iconKind.udapp = yo`<div id='runIcons'></div>`
this.iconKind.testing = yo`<div id='testingIcons'></div>`
this.iconKind.analysis = yo`<div id='analysisIcons'></div>`
this.iconKind.debugging = yo`<div id='debuggingIcons' data-id="verticalIconsDebuggingIcons"></div>`
this.iconKind.none = yo`<div id='otherIcons'></div>`
this.iconKind.settings = yo`<div id='settingsIcons' data-id="verticalIconsSettingsIcons"></div>`
this.view = yo`
<div class="h-100">
<div class=${css.icons}>
${home}
${this.iconKind.fileexplorer}
${this.iconKind.compiler}
${this.iconKind.udapp}
${this.iconKind.testing}
${this.iconKind.analysis}
${this.iconKind.debugging}
${this.iconKind.none}
${this.iconKind.settings}
</div>
</div>
`
return this.view
}
}
const css = csjs`
.homeIcon {
display: block;
width: 42px;
height: 42px;
margin-bottom: 20px;
cursor: pointer;
}
.homeIcon svg path {
fill: var(--primary);
}
.homeIcon svg polygon {
fill: var(--primary);
}
.icons {
}
.icon {
cursor: pointer;
margin-bottom: 12px;
width: 36px;
height: 36px;
padding: 3px;
position: relative;
border-radius: 8px;
}
.icon img {
width: 28px;
height: 28px;
padding: 4px;
filter: invert(0.5);
}
.image {
}
.icon svg {
width: 28px;
height: 28px;
padding: 4px;
}
.icon[title='Settings'] {
position: absolute;
bottom: 0;
}
.status {
position: absolute;
bottom: 0;
right: 0;
}
.statusCheck {
font-size: 1.2em;
return this.htmlElement
}
.statusWithBG
border-radius: 8px;
background-color: var(--danger);
color: var(--light);
font-size: 12px;
height: 15px;
text-align: center;
font-weight: bold;
padding-left: 5px;
padding-right: 5px;
}
`

@ -6,6 +6,7 @@ export class WalkthroughService {
}
start (params) {
document.addEventListener('doWalkThrough', (e) => {
if (!localStorage.getItem('hadTour_initial')) {
introJs().setOptions({
steps: [{
@ -24,7 +25,7 @@ export class WalkthroughService {
},
{
title: 'Deploy your contract',
element: document.querySelector('#runIcons'),
element: document.querySelector('#verticalIconsKindudapp'),
intro: 'Choose a chain, deploy a contract and play with your functions.',
tooltipClass: 'bg-light text-dark',
position: 'right'
@ -47,6 +48,7 @@ export class WalkthroughService {
}).start()
localStorage.setItem('hadTour_initial', true)
}
})
}
startFeatureTour () {

@ -596,7 +596,7 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
}
<button id="compileBtn" data-id="compilerContainerCompileBtn" className="btn btn-primary btn-block remixui_disabled mt-3" title="Compile" onClick={compile} disabled={disableCompileButton}>
<span>
{ <i ref={compileIcon} className="fas fa-sync remixui_icon" aria-hidden="true"></i> }
{ <i ref={compileIcon} className="fas fa-sync remixui_iconbtn" aria-hidden="true"></i> }
Compile { typeof state.compiledFileName === 'string' ? helper.extractNameFromKey(state.compiledFileName) || '<no file selected>' : '<no file selected>' }
</span>
</button>

@ -151,7 +151,7 @@
padding: 8px 0;
border: none;
}
.remixui_icon {
.remixui_iconbtn {
margin-right: 0.3em;
}
.remixui_errorBlobs {

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

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

@ -0,0 +1,5 @@
{
"tabWidth": 2,
"singleQuote": true,
"semi": false
}

@ -0,0 +1,7 @@
# remix-ui-vertical-icons-panel
React library for RemixIde vertical icons Panel
## Running unit tests
Run `nx test remix-ui-vertical-icons-panel` to execute the unit tests via [Jest](https://jestjs.io).

@ -0,0 +1 @@
export * from './lib/remix-ui-vertical-icons-panel'

@ -0,0 +1,68 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
// eslint-disable-next-line no-use-before-define
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 {
badgeStatus: BadgeStatus
}
// eslint-disable-next-line no-undef
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function Badge ({ badgeStatus }: BadgeProps) {
/**
* resolve a classes list for @arg key
* @param {Object} key
* @param {Object} type
*/
function resolveClasses (key: string, type: string) {
let classes = 'remixui_status'
switch (key) {
case 'succeed':
classes += ' fas fa-check-circle text-' + type + ' ' + 'remixui_statusCheck'
break
case 'edited':
classes += ' fas fa-sync text-' + type + ' ' + 'remixui_statusCheck'
break
case 'loading':
classes += ' fas fa-spinner text-' + type + ' ' + 'remixui_statusCheck'
break
case 'failed':
classes += ' fas fa-exclamation-triangle text-' + type + ' ' + 'remixui_statusCheck'
break
default: {
classes += ' badge badge-pill badge-' + type
}
}
return classes
}
function checkStatusKeyValue (value: any, type: string) {
if (value === 'succeed' || value === 'edited' || value === 'loading' || value === 'failed' ||
typeof value === 'number' || type === 'warning' || type === 'error' || type === 'success' || type === 'info' || type === 'danger') {
return true
}
return false
}
return (
<>
{
badgeStatus && checkStatusKeyValue(badgeStatus.key, badgeStatus.type) ? (
<i
title={badgeStatus.title}
className={resolveClasses(badgeStatus.key, badgeStatus.type!)}
aria-hidden="true"
>
{badgeStatus.text}
</i>
) : null
}
</>
)
}
export default Badge

@ -0,0 +1,14 @@
/* eslint-disable no-use-before-define */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import React from 'react'
function BasicLogo () {
return (<svg id="Ebene_2" data-name="Ebene 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 105 100">
<path d="M91.84,35a.09.09,0,0,1-.1-.07,41,41,0,0,0-79.48,0,.09.09,0,0,1-.1.07C9.45,35,1,35.35,1,42.53c0,8.56,1,16,6,20.32,2.16,1.85,5.81,2.3,9.27,2.22a44.4,44.4,0,0,0,6.45-.68.09.09,0,0,0,.06-.15A34.81,34.81,0,0,1,17,45c0-.1,0-.21,0-.31a35,35,0,0,1,70,0c0,.1,0,.21,0,.31a34.81,34.81,0,0,1-5.78,19.24.09.09,0,0,0,.06.15,44.4,44.4,0,0,0,6.45.68c3.46.08,7.11-.37,9.27-2.22,5-4.27,6-11.76,6-20.32C103,35.35,94.55,35,91.84,35Z"/>
<path d="M52,74,25.4,65.13a.1.1,0,0,0-.1.17L51.93,91.93a.1.1,0,0,0,.14,0L78.7,65.3a.1.1,0,0,0-.1-.17L52,74A.06.06,0,0,1,52,74Z"/>
<path d="M75.68,46.9,82,45a.09.09,0,0,0,.08-.09,29.91,29.91,0,0,0-.87-6.94.11.11,0,0,0-.09-.08l-6.43-.58a.1.1,0,0,1-.06-.18l4.78-4.18a.13.13,0,0,0,0-.12,30.19,30.19,0,0,0-3.65-6.07.09.09,0,0,0-.11,0l-5.91,2a.1.1,0,0,1-.12-.14L72.19,23a.11.11,0,0,0,0-.12,29.86,29.86,0,0,0-5.84-4.13.09.09,0,0,0-.11,0l-4.47,4.13a.1.1,0,0,1-.17-.07l.09-6a.1.1,0,0,0-.07-.1,30.54,30.54,0,0,0-7-1.47.1.1,0,0,0-.1.07l-2.38,5.54a.1.1,0,0,1-.18,0l-2.37-5.54a.11.11,0,0,0-.11-.06,30,30,0,0,0-7,1.48.12.12,0,0,0-.07.1l.08,6.05a.09.09,0,0,1-.16.07L37.8,18.76a.11.11,0,0,0-.12,0,29.75,29.75,0,0,0-5.83,4.13.11.11,0,0,0,0,.12l2.59,5.6a.11.11,0,0,1-.13.14l-5.9-2a.11.11,0,0,0-.12,0,30.23,30.23,0,0,0-3.62,6.08.11.11,0,0,0,0,.12l4.79,4.19a.1.1,0,0,1-.06.17L23,37.91a.1.1,0,0,0-.09.07A29.9,29.9,0,0,0,22,44.92a.1.1,0,0,0,.07.1L28.4,47a.1.1,0,0,1,0,.18l-5.84,3.26a.16.16,0,0,0,0,.11,30.17,30.17,0,0,0,2.1,6.76c.32.71.67,1.4,1,2.08a.1.1,0,0,0,.06,0L52,68.16H52l26.34-8.78a.1.1,0,0,0,.06-.05,30.48,30.48,0,0,0,3.11-8.88.1.1,0,0,0-.05-.11l-5.83-3.26A.1.1,0,0,1,75.68,46.9Z"/>
</svg>
)
}
export default BasicLogo

@ -0,0 +1,18 @@
/* eslint-disable no-use-before-define */
import React, { MutableRefObject } from 'react'
export interface ChevronProps {
divElementRef: MutableRefObject<any>
cssRule: string
}
function Chevron (props: ChevronProps) {
return (
<>
{ props.divElementRef.current && props.divElementRef.current.scrollHeight > props.divElementRef.current.clientHeight
? <i className={props.cssRule}></i> : null }
</>
)
}
export { Chevron }

@ -0,0 +1,62 @@
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 Badge from './Badge'
import Icon, { IconStatus } from './Icon'
interface DebuggerProps {
verticalIconsPlugin: VerticalIcons
itemContextAction: (e: any, name: string, documentation: string) => Promise<void>
addActive: (name: string) => void
removeActive: () => void
}
const initialState = {
text: '',
key: '',
title: '',
type: ''
}
function Debugger ({ verticalIconsPlugin, itemContextAction, addActive, removeActive }: DebuggerProps) {
const [badgeStatus, dispatchStatusUpdate] = useReducer(iconBadgeReducer, initialState)
useEffect(() => {
verticalIconsPlugin.on('debugger', 'statusChanged', (iconStatus: IconStatus) => {
const action: IconBadgeReducerAction = { type: 'debugger', payload: { status: iconStatus, verticalIconPlugin: verticalIconsPlugin } }
dispatchStatusUpdate(action)
})
}, [])
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
}
/>
<Badge
badgeStatus={badgeStatus}
/>
</div>
))
: null}
</Fragment>
)
}
export default Debugger

@ -0,0 +1,59 @@
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

@ -0,0 +1,26 @@
/* 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, { ReactNode } from 'react'
import BasicLogo from './BasicLogo'
interface HomeProps {
verticalIconPlugin: VerticalIcons
}
function Home ({ verticalIconPlugin }: HomeProps) {
return (
<div
className="pl-1 mt-2 remixui_homeIcon"
onClick={async () => verticalIconPlugin.activateHome()}
// @ts-ignore
plugin="home"
title="Home"
data-id="verticalIconsHomeIcon"
id="verticalIconsHomeIcon"
>
<BasicLogo />
</div>
)
}
export default Home

@ -0,0 +1,134 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
/* eslint-disable no-use-before-define */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import VerticalIconsContextMenu from '../vertical-icons-context-menu'
// eslint-disable-next-line @typescript-eslint/no-unused-vars
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'
interface IconProps {
verticalIconPlugin: VerticalIcons
profile: IconProfile
contextMenuAction: (evt: any, profileName: string, documentation: string) => void
addActive: (profileName: string) => void
removeActive: () => void
badgeStatus?: BadgeStatus
}
export interface IconStatus {
key: string
title: string
type: string
}
export interface BadgeStatus extends IconStatus {
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
}
function Icon ({
profile,
verticalIconPlugin,
contextMenuAction,
addActive,
removeActive,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
badgeStatus
}: IconProps) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { tooltip, displayName, name, kind, icon, documentation } = profile
const [title] = useState(() => {
const temp = tooltip || displayName || name
return temp.replace(/^\w/, (word: string) => word.toUpperCase())
})
const [links, setLinks] = useState<{ Documentation: string, CanDeactivate: boolean }>(
{} as { Documentation: string, CanDeactivate: boolean }
)
// @ts-ignore
const [pageX, setPageX] = useState<number>(null)
// @ts-ignore
const [pageY, setPageY] = useState<number>(null)
const [showContext, setShowContext] = useState(false)
const [canDeactivate] = useState(false)
const iconRef = useRef<any>(null)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const handleContextMenu = (e: SyntheticEvent & PointerEvent) => {
const deactivationState = verticalIconPlugin.appManager
.canDeactivatePlugin(verticalIconPlugin.defaultProfile, { name })
if (documentation && documentation.length > 0 && deactivationState) {
setLinks({ Documentation: documentation, CanDeactivate: deactivationState })
} else {
setLinks({ Documentation: documentation, CanDeactivate: deactivationState })
}
setShowContext(false)
setPageX(e.pageX)
setPageY(e.pageY)
setShowContext(true)
}
function closeContextMenu () {
setShowContext(false)
}
return (
<Fragment>
<div
className={name === 'pluginManager' ? 'remixui_icon ml-2 mt-2 mr-2 mb-0 pl-1' : 'remixui_icon m-2 pl-1'}
onLoad={() => {
if (name === 'filePanel') {
addActive(name)
}
}}
onClick={() => {
removeActive()
addActive(name)
verticalIconPlugin.toggle(name)
}}
// @ts-ignore
plugin={name}
title={title}
onContextMenu={(e: any) => {
e.preventDefault()
e.stopPropagation()
handleContextMenu(e)
}}
data-id={`verticalIconsKind${name}`}
id={`verticalIconsKind${name}`}
ref={iconRef}
>
<img className="remixui_image" src={icon} alt={name} />
<Badge
badgeStatus={badgeStatus!}
/>
</div>
{showContext ? (
<VerticalIconsContextMenu
pageX={pageX}
pageY={pageY}
links={links}
profileName={name}
hideContextMenu={closeContextMenu}
canBeDeactivated={canDeactivate}
verticalIconPlugin={verticalIconPlugin}
contextMenuAction={contextMenuAction}
/>
) : null}
</Fragment>
)
}
export default Icon

@ -0,0 +1,66 @@
/* 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, { useEffect, useReducer } from 'react'
import { iconBadgeReducer, IconBadgeReducerAction } from '../reducers/iconBadgeReducer'
import Icon, { IconStatus } 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
}
const initialState = {
text: '',
key: '',
title: '',
type: ''
}
function OtherIcons ({ verticalIconsPlugin, itemContextAction, addActive, removeActive }: OtherIconsProps) {
const [badgeStatus, dispatchStatusUpdate] = useReducer(iconBadgeReducer, initialState)
useEffect(() => {
Object.keys(verticalIconsPlugin.targetProfileForChange)
.filter(customFilter)
.forEach(p =>
verticalIconsPlugin.on(verticalIconsPlugin.targetProfileForChange[p].name, 'statusChanged', (iconStatus: IconStatus) => {
const action: IconBadgeReducerAction = {
type: verticalIconsPlugin.targetProfileForChange[p].name,
payload: { status: iconStatus, verticalIconPlugin: verticalIconsPlugin }
}
dispatchStatusUpdate(action)
}))
}, [])
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
}
badgeStatus={badgeStatus}
/>
))}
</div>
)
}
export default OtherIcons

@ -0,0 +1,36 @@
/* 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

@ -0,0 +1,67 @@
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}
/>
<Chevron
divElementRef={scrollableRef}
cssRule={'fa fa-chevron-up remixui_icon-chevron mt-0 mb-0 ml-1 pl-3'}
/>
</Fragment>
)
}
export { RequiredSection }

@ -0,0 +1,60 @@
/* 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}>
<Chevron
divElementRef={scrollableRef}
cssRule={'fa fa-chevron-down remixui_icon-chevron mt-0 mb-0 ml-1 pl-3'}
/>
{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

@ -0,0 +1,58 @@
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 Badge from './Badge'
import Icon, { IconStatus } from './Icon'
interface SolidityProps {
verticalIconsPlugin: VerticalIcons
itemContextAction: (e: any, name: string, documentation: string) => Promise<void>
addActive: (name: string) => void
removeActive: () => void
}
const initialState = {
text: '',
key: '',
title: '',
type: ''
}
function Solidity ({ verticalIconsPlugin, itemContextAction, addActive, removeActive }: SolidityProps) {
const [badgeStatus, dispatchStatusUpdate] = useReducer(iconBadgeReducer, initialState)
useEffect(() => {
verticalIconsPlugin.on('solidity', 'statusChanged', (iconStatus: IconStatus) => {
const action: IconBadgeReducerAction = { type: 'solidity', payload: { status: iconStatus, verticalIconPlugin: verticalIconsPlugin } }
dispatchStatusUpdate(action)
})
}, [])
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
}
badgeStatus={badgeStatus}
/>
</div>
))
: null}
</Fragment>
)
}
export default Solidity

@ -0,0 +1,58 @@
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 Badge from './Badge'
import Icon, { IconStatus } from './Icon'
interface SolidityStaticAnalysisProps {
verticalIconsPlugin: VerticalIcons
itemContextAction: (e: any, name: string, documentation: string) => Promise<void>
addActive: (name: string) => void
removeActive: () => void
}
const initialState = {
text: '',
key: '',
title: '',
type: ''
}
function SolidityStaticAnalysis ({ verticalIconsPlugin, itemContextAction, addActive, removeActive }: SolidityStaticAnalysisProps) {
const [badgeStatus, dispatchStatusUpdate] = useReducer(iconBadgeReducer, initialState)
useEffect(() => {
verticalIconsPlugin.on('solidityStaticAnalysis', 'statusChanged', (iconStatus: IconStatus) => {
const action: IconBadgeReducerAction = { type: 'solidityStaticAnalysis', payload: { status: iconStatus, verticalIconPlugin: verticalIconsPlugin } }
dispatchStatusUpdate(action)
})
}, [])
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
}
badgeStatus={badgeStatus}
/>
</div>
))
: null}
</Fragment>
)
}
export default SolidityStaticAnalysis

@ -0,0 +1,60 @@
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 Icon, { IconStatus } from './Icon'
interface UdappProps {
verticalIconsPlugin: VerticalIcons
itemContextAction: (e: any, name: string, documentation: string) => Promise<void>
addActive: (name: string) => void
removeActive: () => void
}
const initialState = {
text: '',
key: '',
title: '',
type: ''
}
function Udapp ({ verticalIconsPlugin, itemContextAction, addActive, removeActive }: UdappProps) {
const [badgeStatus, dispatchStatusUpdate] = useReducer(iconBadgeReducer, initialState)
useEffect(() => {
verticalIconsPlugin.on('udapp', 'statusChanged', (iconStatus: IconStatus) => {
const action: IconBadgeReducerAction = { type: 'udapp', payload: { status: iconStatus, verticalIconPlugin: verticalIconsPlugin } }
dispatchStatusUpdate(action)
})
}, [])
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
}
badgeStatus={badgeStatus}
/>
</div>
))
: null}
</Fragment>
)
}
export default Udapp

@ -0,0 +1,44 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import helper from 'apps/remix-ide/src/lib/helper'
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 = {
readonly type: string
readonly payload: any
}
/**
* Set a new status for the @arg name
* @param {String} name
* @param {Object} status
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function setIconStatus (name: string, status: IconStatus) {
if (status.key === 'none') return { ...status, text: '' } // remove status
let text = ''
let key = ''
if (typeof status.key === 'number') {
key = status.key
// eslint-disable-next-line @typescript-eslint/no-unused-vars
text = key
} else key = helper.checkSpecialChars(status.key) ? '' : status.key
let thisType = ''
if (status.type === 'error') {
thisType = 'danger' // to use with bootstrap
} else thisType = helper.checkSpecialChars(status.type) ? '' : status.type!
const title = helper.checkSpecialChars(status.title) ? '' : status.title
return { title, type: thisType, key, text }
}
export function iconBadgeReducer (state: BadgeStatus, action: IconBadgeReducerAction) {
const { status, ref, verticalIconPlugin } = action.payload
if (Object.keys(verticalIconPlugin.targetProfileForChange).includes(action.type)) {
const setStatus = setIconStatus(action.type, status)
return setStatus
}
return state
}

@ -0,0 +1,124 @@
.remixui_homeIcon {
/* display: block; */
width: 42px;
height: 42px;
cursor: pointer;
}
.remixui_homeIcon svg path {
fill: var(--primary);
}
.remixui_homeIcon svg polygon {
fill: var(--primary);
}
.remixui_icons {
display: flex;
flex-flow: column nowrap;
justify-content: space-between;
align-items: center;
}
.remixui_icon {
cursor: pointer;
margin-bottom: 3px;
position: 12px;
width: 36px;
height: 36px;
padding: relative;
border-radius: 8px;
}
.remixui_icon img {
width: 28px;
height: 28px;
padding: 4px;
filter: invert(0.5);
}
.remixui_image {
}
.remixui_icon svg {
width: 28px;
height: 28px;
padding: 4px;
}
.remixui_icon[title='Settings'] {
order: 5;
align-self: center;
bottom: 0;
}
.remixui_status {
position: relative;
bottom: 0;
right: 0;
left: 12px;
top: -13px;
}
.remixui_statusCheck {
font-size: 1.2em;
}
.remixui_statusWithBG {
border-radius: 8px;
background-color: var(--danger);
color: var(--light);
font-size: 12px;
height: 15px;
text-align: center;
font-weight: bold;
padding-left: 5px;
padding-right: 5px;
}
.remixui_verticalIconContextcontainer {
display: block;
position: fixed;
border-radius: 2px;
z-index: 1000;
box-shadow: 0 0 4px var(--dark);
}
.remixui_verticalIconContextcontainer:focus {
outline: 0;
}
.remixui_liitem {
padding: 2px;
padding-left: 6px;
cursor: pointer;
color: var(--text-dark);
background-color: var(--light);
}
.remixui_liitem:hover {
background-color: var(--secondary);
}
.remixui_scrollbar {
overflow-y: scroll;
scrollbar-width: none; /* Firefox hide scrollbar */
-ms-overflow-style: none;
}
.remixui_scrollable-container {
flex-basis: 510px;
flex-grow: 2;
/* border-bottom: 3px solid #3f4455; */
}
.remixui_scrollbar::-webkit-scrollbar { /* Chrome, Safari and other Webkit browsers*/
display: none;
}
.remixui_hide-scroll {
overflow-x: 'hidden';
}
.remixui_default-icons-container {
border-bottom: 2px solid #3f4455;
}
.remixui_icon-chevron {
z-index: 1000;
}
.remixui_settings {
flex-basis: 50px;
}
.remixui_requiredSection {
flex-basis: 180px;
flex-grow: 1;
}
#menuitems {
list-style: none;
margin: 0px;
}

@ -0,0 +1,139 @@
/* eslint-disable no-use-before-define */
/* eslint-disable @typescript-eslint/no-unused-vars */
import React, {
Fragment,
useEffect,
useRef
} from 'react'
import './remix-ui-vertical-icons-panel.css'
import OtherIcons from './components/OtherIcons'
import { VerticalIcons } from '../../types/vertical-icons-panel'
import Home from './components/Home'
import Settings from './components/Settings'
import { RequiredSection } from './components/RequiredSection'
export interface RemixUiVerticalIconsPanelProps {
verticalIconsPlugin: VerticalIcons
}
let scrollHeight: any
export function RemixUiVerticalIconsPanel ({
verticalIconsPlugin
}: RemixUiVerticalIconsPanelProps) {
const scrollableRef = useRef<any>()
const iconPanelRef = useRef<any>()
function onThemeChanged (themeType: any) {
const invert = themeType === 'dark' ? 1 : 0
// @ts-ignore
const active = iconPanelRef.current.querySelector('.active')
if (active) {
// @ts-ignore
const image = iconPanelRef.current.querySelector('.remixui_image')
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) {
if (name === 'home') return
const themeType = verticalIconsPlugin.registry.get('themeModule').api.currentTheme().quality
const invert = themeType === 'dark' ? 1 : 0
const brightness = themeType === 'dark' ? '150' : '0' // should be >100 for icons with color
// @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) {
verticalIconsPlugin.appManager.deactivatePlugin(name)
if (e.target.parentElement.classList.contains('active')) {
verticalIconsPlugin.select('filePanel')
}
verticalIconsPlugin.renderComponent()
}
useEffect(() => {
const themeModule = verticalIconsPlugin.registry.get('themeModule').api
themeModule.events.on('themeChanged', (theme: any) => {
onThemeChanged(theme.quality)
})
}, [])
useEffect(() => {
const themeModule = verticalIconsPlugin.registry.get('themeModule').api
themeModule.events.on('themeChanged', (theme: any) => {
onThemeChanged(theme.quality)
})
}, [])
useEffect(() => {
if (verticalIconsPlugin.targetProfileForChange && verticalIconsPlugin.targetProfileForChange.udapp) {
const doWalkThroughEvent = new Event('doWalkThrough')
document.dispatchEvent(doWalkThroughEvent)
}
}, [Object.keys(verticalIconsPlugin.targetProfileForChange).length])
return (
<div id="iconsP" className="h-100">
<div className="remixui_icons d-flex flex-column vh-100" ref={iconPanelRef}>
<Home verticalIconPlugin={verticalIconsPlugin} />
<div className={scrollableRef.current && scrollableRef.current.scrollHeight > scrollableRef.current.clientHeight
? 'remixui_default-icons-container remixui_requiredSection' : 'remixui_requiredSection'}>
<RequiredSection
verticalIconsPlugin={verticalIconsPlugin}
addActive={addActive}
removeActive={removeActive}
itemContextAction={itemContextAction}
scrollableRef={scrollableRef}
/>
</div>
<div
id="remixuiScrollable"
className={scrollableRef.current && scrollableRef.current.scrollHeight > scrollableRef.current.clientHeight
? 'remixui_default-icons-container remixui_scrollable-container remixui_scrollbar remixui_hide-scroll'
: 'remixui_scrollable-container remixui_scrollbar remixui_hide-scroll'}
ref={scrollableRef}
>
<OtherIcons
verticalIconsPlugin={verticalIconsPlugin}
addActive={addActive}
removeActive={removeActive}
itemContextAction={itemContextAction}
/>
</div>
{verticalIconsPlugin.targetProfileForChange &&
Object.keys(verticalIconsPlugin.targetProfileForChange).length ? (
<Fragment>
<Settings
verticalIconsPlugin={verticalIconsPlugin}
addActive={addActive}
removeActive={removeActive}
itemContextAction={itemContextAction}
scrollableRef={scrollableRef}
/>
</Fragment>
) : null}
</div>
</div>
)
}

@ -0,0 +1,121 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
/* 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 { VerticalIcons } from '../../types/vertical-icons-panel'
export interface VerticalIconsContextMenuProps extends React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
pageX: number
pageY: number
profileName: string
links: { Documentation: string, CanDeactivate: boolean }
canBeDeactivated: boolean
verticalIconPlugin: VerticalIcons
hideContextMenu: () => void
contextMenuAction: (evt: any, profileName: string, documentation: string) => void
}
interface MenuLinksProps {
listItems: { Documentation: string, CanDeactivate: boolean }
hide: () => void
profileName: string
canBeDeactivated: boolean
verticalIconPlugin: VerticalIcons
ref?: React.MutableRefObject<any>
toggle: (name: string) => void
contextMenuAction: (evt: any, profileName: string, documentation: string) => void
}
interface MenuProps {
verticalIconsPlugin: VerticalIcons
profileName: string
listItems: { Documentation: string, CanDeactivate: boolean }
hide: () => void
}
const requiredModules = [
'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)
useEffect(() => {
document.addEventListener('click', props.hideContextMenu)
return () => document.removeEventListener('click', props.hideContextMenu)
}, [])
useEffect(() => {
// @ts-ignore
menuRef.current.focus()
}, [])
return (
<div
id="menuItemsContainer"
className="p-1 remixui_verticalIconContextcontainer bg-light shadow border"
onBlur={props.hideContextMenu}
style={{
left: props.pageX,
top: props.pageY,
display: 'block'
}}
ref={menuRef}
tabIndex={1}
>
<ul id="menuitems">
<MenuForLinks
hide={props.hideContextMenu}
listItems={props.links}
profileName={props.profileName}
canBeDeactivated={props.canBeDeactivated}
verticalIconPlugin={props.verticalIconPlugin}
toggle={props.verticalIconPlugin.toggle}
contextMenuAction={props.contextMenuAction}
/>
</ul>
</div>
)
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function MenuForLinks ({
listItems,
hide,
profileName,
contextMenuAction
}: MenuLinksProps) {
return (
<Fragment>
{listItems.CanDeactivate && !requiredModules.includes(profileName)
? <li
id="menuitemdeactivate"
onClick={(evt) => {
contextMenuAction(evt, profileName, listItems.Documentation)
hide()
}}
className="remixui_liitem"
>
Deactivate
</li>
: null
}
{(listItems.Documentation && listItems.Documentation.length > 0) &&
<li
id="menuitemdocumentation"
className="remixui_liitem"
>
<a
onClick={hide}
href={listItems.Documentation}
target="_blank"
>
Documentation
</a>
</li>}
</Fragment>
)
}
export default VerticalIconsContextMenu

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

@ -0,0 +1,14 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../dist/out-tsc",
"types": ["node"],
"resolveJsonModule": true
},
"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"]
}

@ -0,0 +1,110 @@
/* 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 * as registry from 'apps/remix-ide/src/global/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')

@ -1,5 +1,4 @@
{
"npmScope": "remix-project",
"implicitDependencies": {
"workspace.json": "*",
"package.json": {
@ -11,6 +10,7 @@
".eslintrc": "*",
"nx.json": "*"
},
"npmScope": "remix-project",
"tasksRunnerOptions": {
"default": {
"runner": "@nrwl/workspace/tasks-runners/default",
@ -133,6 +133,9 @@
"remix-ui-helper": {
"tags": []
},
"remix-ui-vertical-icons-panel": {
"tags": []
},
"remix-ui-tabs": {
"tags": []
}

66
package-lock.json generated

@ -1517,6 +1517,14 @@
"requires": {
"core-js": "^2.6.5",
"regenerator-runtime": "^0.13.4"
},
"dependencies": {
"core-js": {
"version": "2.6.12",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz",
"integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==",
"dev": true
}
}
},
"@babel/preset-env": {
@ -12685,6 +12693,12 @@
"regenerator-runtime": "^0.11.0"
},
"dependencies": {
"core-js": {
"version": "2.6.12",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz",
"integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==",
"dev": true
},
"regenerator-runtime": {
"version": "0.11.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
@ -15408,12 +15422,6 @@
}
}
},
"core-js": {
"version": "2.6.12",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz",
"integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==",
"dev": true
},
"core-js-compat": {
"version": "3.19.1",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.19.1.tgz",
@ -35928,16 +35936,6 @@
"es-abstract": "^1.19.1"
}
},
"object.hasown": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.0.tgz",
"integrity": "sha512-MhjYRfj3GBlhSkDHo6QmvgjRLXQ2zndabdf3nX0yTyZK9rPfxb6uRpAac8HXNLy1GpqWtZ81Qh4v3uOls2sRAg==",
"dev": true,
"requires": {
"define-properties": "^1.1.3",
"es-abstract": "^1.19.1"
}
},
"object.map": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz",
@ -38943,9 +38941,9 @@
}
},
"regenerator-runtime": {
"version": "0.13.9",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
"integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
"version": "0.13.7",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
"integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew=="
},
"regenerator-transform": {
"version": "0.14.5",
@ -41476,6 +41474,36 @@
"internal-slot": "^1.0.3",
"regexp.prototype.flags": "^1.3.1",
"side-channel": "^1.0.4"
},
"dependencies": {
"es-abstract": {
"version": "1.19.1",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz",
"integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
"es-to-primitive": "^1.2.1",
"function-bind": "^1.1.1",
"get-intrinsic": "^1.1.1",
"get-symbol-description": "^1.0.0",
"has": "^1.0.3",
"has-symbols": "^1.0.2",
"internal-slot": "^1.0.3",
"is-callable": "^1.2.4",
"is-negative-zero": "^2.0.1",
"is-regex": "^1.1.4",
"is-shared-array-buffer": "^1.0.1",
"is-string": "^1.0.7",
"is-weakref": "^1.0.1",
"object-inspect": "^1.11.0",
"object-keys": "^1.1.1",
"object.assign": "^4.1.2",
"string.prototype.trimend": "^1.0.4",
"string.prototype.trimstart": "^1.0.4",
"unbox-primitive": "^1.0.1"
}
}
}
},
"string.prototype.padend": {

@ -261,8 +261,11 @@
"eslint-config-prettier": "^6.11.0",
"eslint-config-standard": "^14.1.1",
"eslint-plugin-import": "2.20.2",
"eslint-plugin-jsx-a11y": "6.4.1",
"eslint-plugin-node": "11.1.0",
"eslint-plugin-promise": "4.2.1",
"eslint-plugin-react": "7.23.1",
"eslint-plugin-react-hooks": "4.2.0",
"eslint-plugin-standard": "4.0.1",
"events": "^3.0.0",
"execr": "^1.0.1",

@ -66,7 +66,10 @@
"@remix-ui/plugin-manager": ["libs/remix-ui/plugin-manager/src/index.ts"],
"@remix-ui/editor": ["libs/remix-ui/editor/src/index.ts"],
"@remix-ui/tabs": ["libs/remix-ui/tabs/src/index.ts"],
"@remix-ui/helper": ["libs/remix-ui/helper/src/index.ts"]
"@remix-ui/helper": ["libs/remix-ui/helper/src/index.ts"],
"@remix-ui/vertical-icons-panel": [
"libs/remix-ui/vertical-icons-panel/src/index.ts"
]
}
},
"exclude": ["node_modules", "tmp"]

@ -1,6 +1,7 @@
{
"compileOnSave": false,
"compilerOptions": {
"jsx": "react",
"rootDir": ".",
"sourceMap": true,
"declaration": false,

@ -1053,25 +1053,32 @@
}
}
}
}
},
"cli": {
"defaultCollection": "@nrwl/react"
},
"schematics": {
"@nrwl/workspace": {
"library": {
"linter": "eslint"
"remix-ui-vertical-icons-panel": {
"root": "libs/remix-ui/vertical-icons-panel",
"sourceRoot": "libs/remix-ui/vertical-icons-panel/src",
"projectType": "library",
"architect": {
"lint": {
"builder": "@nrwl/linter:lint",
"options": {
"linter": "eslint",
"tsConfig": ["libs/remix-ui/vertical-icons-panel/tsconfig.lib.json"],
"exclude": ["**/node_modules/**", "!libs/remix-ui/vertical-icons-panel/**/*"]
}
}
}
},
"@nrwl/cypress": {
"cypress-project": {
"linter": "eslint"
}
},
"@nrwl/react": {
"application": {
"style": "css",
"remix-ui-editor": {
"root": "libs/remix-ui/editor",
"sourceRoot": "libs/remix-ui/editor/src",
"projectType": "library",
"schematics": {},
"architect": {
"lint": {
"builder": "@nrwl/linter:lint",
"options": {
"linter": "eslint",
"babel": true
},
@ -1083,8 +1090,12 @@
"linter": "eslint"
}
},
"@nrwl/next": {
"application": {
"library": {
"linter": "eslint"
}
},
"@nrwl/nx-plugin": {
"plugin": {
"linter": "eslint"
}
},
@ -1100,24 +1111,43 @@
"library": {
"linter": "eslint"
}
}
},
"@nrwl/nx-plugin": {
"plugin": {
"cli": {
"defaultCollection": "@nrwl/react"
},
"schematics": {
"@nrwl/workspace": {
"library": {
"linter": "eslint"
}
},
"@nrwl/nest": {
"application": {
"@nrwl/cypress": {
"cypress-project": {
"linter": "eslint"
}
},
"@nrwl/express": {
"@nrwl/react": {
"application": {
"style": "css",
"linter": "eslint",
"babel": true
},
"component": {
"style": "css"
},
"library": {
"style": "css",
"linter": "eslint"
}
},
"library": {
"linter": "eslint"
}
},
"@nrwl/nx-plugin": {
"plugin": {
"linter": "eslint"
}
},
"defaultProject": "remix-ide"

Loading…
Cancel
Save