Merge pull request #1629 from ethereum/addAutoCompletePopupPaddy

Add auto complete popup -rebased version of PR #1528 for @PaddyMc
pull/1/head
yann300 6 years ago committed by GitHub
commit ce1ef91a79
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 58
      src/app/constants/commands.js
  2. 91
      src/app/panels/terminal.js
  3. 135
      src/app/ui/auto-complete-popup.js
  4. 67
      src/app/ui/styles/auto-complete-popup-styles.js

@ -0,0 +1,58 @@
const allPrograms = [
{'ethers': 'The ethers.js library is a compact and complete JavaScript library for Ethereum.'},
{'remix': 'Ethereum IDE and tools for the web.'},
{'web3': 'The web3.js library is a collection of modules which contain specific functionality for the ethereum ecosystem.'},
{'swarmgw': 'This library can be used to upload/download files to Swarm via https://swarm-gateways.net/.'}
]
const allCommands = [
{'remix.debug(hash)': 'Start debugging a transaction.'},
{'remix.debugHelp()': 'Display help message for debugging'},
{'remix.execute(filepath)': 'Run the script specified by file path. If filepath is empty, script currently displayed in the editor is executed.'},
{'remix.exeCurrent()': 'Run the script currently displayed in the editor.'},
{'remix.getFile(path)': 'Returns the content of the file located at the given path'},
{'remix.help()': 'Display this help message.'},
{'remix.loadgist(id)': 'Load a gist in the file explorer.'},
{'remix.loadurl(url)': 'Load the given url in the file explorer. The url can be of type github, swarm or ipfs.'},
{'remix.setFile(path, content)': 'set the content of the file located at the given path'},
{'remix.setproviderurl(url)': 'Change the current provider to Web3 provider and set the url endpoint.'},
{'swarmgw.get(url, cb)': 'Download files from Swarm via https://swarm-gateways.net/'},
{'swarmgw.put(content, cb)': 'Upload files to Swarm via https://swarm-gateways.net/'},
{'ethers.Contract': 'This API provides a graceful connection to a contract deployed on the blockchain, simplifying calling and querying its functions and handling all the binary protocol and conversion as necessarily.'},
{'ethers.HDNode': 'A Hierarchical Deterministic Wallet represents a large tree of private keys which can reliably be reproduced from an initial seed.'},
{'ethers.Interface': 'The Interface Object is a meta-class that accepts a Solidity (or compatible) Application Binary Interface (ABI) and populates functions to deal with encoding and decoding the parameters to pass in and results returned.'},
{'ethers.providers': 'A Provider abstracts a connection to the Ethereum blockchain, for issuing queries and sending state changing transactions.'},
{'ethers.SigningKey': 'The SigningKey interface provides an abstraction around the secp256k1 elliptic curve cryptography library.'},
{'ethers.utils': 'The utility functions exposed in both the ethers umbrella package and the ethers-utils.'},
{'ethers.utils.AbiCoder': 'Create a new ABI Coder object'},
{'ethers.utils.RLP': 'This encoding method is used internally for several aspects of Ethereum, such as encoding transactions and determining contract addresses.'},
{'ethers.Wallet': 'A wallet manages a private/public key pair which is used to cryptographically sign transactions and prove ownership on the Ethereum network.'},
{'ethers.version': 'Contains the version of the ethers container object.'},
{'web3.bzz': 'Bzz module for interacting with the swarm network.'},
{'web3.eth': 'Eth module for interacting with the Ethereum network.'},
{'web3.eth.accounts': 'The web3.eth.accounts contains functions to generate Ethereum accounts and sign transactions and data.'},
{'web3.eth.abi': 'The web3.eth.abi functions let you de- and encode parameters to ABI (Application Binary Interface) for function calls to the EVM (Ethereum Virtual Machine).'},
{'web3.eth.ens': 'The web3.eth.ens functions let you interacting with ENS.'},
{'web3.eth.Iban': 'The web3.eth.Iban function lets convert Ethereum addresses from and to IBAN and BBAN.'},
{'web3.eth.net': 'Net module for interacting with network properties.'},
{'web3.eth.personal': 'Personal module for interacting with the Ethereum accounts.'},
{'web3.eth.subscribe': 'The web3.eth.subscribe function lets you subscribe to specific events in the blockchain.'},
{'web3.givenProvider': 'When using web3.js in an Ethereum compatible browser, it will set with the current native provider by that browser. Will return the given provider by the (browser) environment, otherwise null.'},
{'web3.modules': 'Contains the version of the web3 container object.'},
{'web3.providers': 'Contains the current available providers.'},
{'web3.shh': 'Shh module for interacting with the whisper protocol'},
{'web3.utils': 'This package provides utility functions for Ethereum dapps and other web3.js packages.'},
{'web3.version': 'Contains the version of the web3 container object.'},
{'web3.eth.clearSubscriptions();': 'Resets subscriptions.'},
{'web3.eth.Contract(jsonInterface[, address][, options])': 'The web3.eth.Contract object makes it easy to interact with smart contracts on the ethereum blockchain.'},
{'web3.eth.accounts.create([entropy]);': 'The web3.eth.accounts contains functions to generate Ethereum accounts and sign transactions and data.'}
]
module.exports = {
allPrograms,
allCommands
}

@ -13,6 +13,8 @@ var swarmgw = require('swarmgw')()
var CommandInterpreterAPI = require('../../lib/cmdInterpreterAPI')
var executionContext = require('../../execution-context')
var Dropdown = require('../ui/dropdown')
var AutoCompletePopup = require('../ui/auto-complete-popup')
var Commands = require('../constants/commands')
var csjs = require('csjs-inject')
var styleGuide = require('../ui/styles-guide/theme-chooser')
@ -62,6 +64,20 @@ class Terminal {
self.updateJournal({ type: 'select', value: label })
}
})
self._components.autoCompletePopup = new AutoCompletePopup()
self._components.autoCompletePopup.event.register('handleSelect', function (input) {
self._components.autoCompletePopup.data._options = []
self._components.autoCompletePopup._startingElement = 0
let textList = self._view.input.innerText.split(' ')
textList.pop()
textList.push(input)
self._view.input.innerText = `${textList}`.replace(/,/g, ' ')
self._view.input.focus()
yo.update(self._view.autoCompletePopup, self._components.autoCompletePopup.render())
})
self._components.autoCompletePopup.event.register('updateList', function () {
yo.update(self._view.autoCompletePopup, self._components.autoCompletePopup.render())
})
self._commands = {}
self.commands = {}
self._JOURNAL = []
@ -149,6 +165,7 @@ class Terminal {
</div>
</div>
`
self._view.autoCompletePopup = self._components.autoCompletePopup.render()
self._view.el = yo`
<div class=${css.panel}>
${self._view.bar}
@ -391,12 +408,14 @@ class Terminal {
return self._view.el
function change (event) {
handleAutoComplete(event)
if (self._view.input.innerText.length === 0) self._view.input.innerText += '\n'
if (event.which === 13) {
if (event.ctrlKey) { // <ctrl+enter>
self._view.input.innerText += '\n'
putCursor2End(self._view.input)
self.scroll2bottom()
removeAutoComplete()
} else { // <enter>
self._cmdIndex = -1
self._cmdTemp = ''
@ -407,23 +426,32 @@ class Terminal {
self._cmdHistory.unshift(script)
self.commands.script(script)
}
removeAutoComplete()
}
} else if (event.which === 38) { // <arrowUp>
var len = self._cmdHistory.length
if (len === 0) return event.preventDefault()
if (self._cmdHistory.length - 1 > self._cmdIndex) {
self._cmdIndex++
if (self._components.autoCompletePopup.data._options.length > self._components.autoCompletePopup._elementsToShow) {
self._components.autoCompletePopup._view.autoComplete.children[1].children[0].onclick(event)
} else {
var len = self._cmdHistory.length
if (len === 0) return event.preventDefault()
if (self._cmdHistory.length - 1 > self._cmdIndex) {
self._cmdIndex++
}
self._view.input.innerText = self._cmdHistory[self._cmdIndex]
putCursor2End(self._view.input)
self.scroll2bottom()
}
self._view.input.innerText = self._cmdHistory[self._cmdIndex]
putCursor2End(self._view.input)
self.scroll2bottom()
} else if (event.which === 40) { // <arrowDown>
if (self._cmdIndex > -1) {
self._cmdIndex--
if (self._components.autoCompletePopup.data._options.length > self._components.autoCompletePopup._elementsToShow) {
self._components.autoCompletePopup._view.autoComplete.children[1].children[1].onclick(event)
} else {
if (self._cmdIndex > -1) {
self._cmdIndex--
}
self._view.input.innerText = self._cmdIndex >= 0 ? self._cmdHistory[self._cmdIndex] : self._cmdTemp
putCursor2End(self._view.input)
self.scroll2bottom()
}
self._view.input.innerText = self._cmdIndex >= 0 ? self._cmdHistory[self._cmdIndex] : self._cmdTemp
putCursor2End(self._view.input)
self.scroll2bottom()
} else {
self._cmdTemp = self._view.input.innerText
}
@ -455,6 +483,45 @@ class Terminal {
editable.focus()
}
function handleAutoComplete (event) {
if (event.which === 9) {
event.preventDefault()
let textList = self._view.input.innerText.split(' ')
let autoCompleteInput = textList.length > 1 ? textList[textList.length - 1] : textList[0]
if (self._view.input.innerText.length >= 2) {
self._components.autoCompletePopup.data._options = []
Commands.allPrograms.forEach(item => {
if (Object.keys(item)[0].substring(0, Object.keys(item)[0].length - 1).includes(autoCompleteInput.trim())) {
self._components.autoCompletePopup.data._options.push(item)
} else if (autoCompleteInput.trim().includes(Object.keys(item)[0]) || (Object.keys(item)[0] === autoCompleteInput.trim())) {
Commands.allCommands.forEach(item => {
if (Object.keys(item)[0].includes(autoCompleteInput.trim())) {
self._components.autoCompletePopup.data._options.push(item)
}
})
}
})
}
if (self._components.autoCompletePopup.data._options.length === 1) {
textList.pop()
textList.push(Object.keys(self._components.autoCompletePopup.data._options[0])[0])
self._view.input.innerText = `${textList}`.replace(/,/g, ' ')
self._components.autoCompletePopup.data._options = []
putCursor2End(self._view.input)
}
}
if (event.which === 27 || event.which === 8 || event.which === 46) {
self._components.autoCompletePopup.data._options = []
self._components.autoCompletePopup._startingElement = 0
}
yo.update(self._view.autoCompletePopup, self._components.autoCompletePopup.render())
}
function removeAutoComplete () {
self._components.autoCompletePopup.data._options = []
self._components.autoCompletePopup._startingElement = 0
self._components.autoCompletePopup._removePopUp()
yo.update(self._view.autoCompletePopup, self._components.autoCompletePopup.render())
}
}
updateJournal (filterEvent) {
var self = this

@ -0,0 +1,135 @@
var yo = require('yo-yo')
var remixLib = require('remix-lib')
var EventManager = remixLib.EventManager
var modal = require('./modaldialog.js')
// -------------- styling ----------------------
var css = require('./styles/auto-complete-popup-styles')
var cssModal = require('./styles/modaldialog-styles')
/* USAGE:
var autoCompletePopup = new AutoCompletePopup({
options: []
})
autoCompletePopup.event.register('handleSelect', function (input) { })
autoCompletePopup.event.register('updateList', function () { })
*/
class AutoCompletePopup {
constructor (opts = {}) {
var self = this
self.event = new EventManager()
self.data = {
_options: opts.options || []
}
self._view = {}
self._startingElement = 0
self._elementsToShow = 3
self._removePopUp = this.resetCSSValuesModalContainer
}
resetCSSValuesModalContainer () {
var modalContainer = document.querySelector(`.${cssModal.modal}`)
modalContainer.style.display = 'none'
var modalContent = document.querySelector(`.${css.modalContent}`)
let newModalContent = modalContent ? document.querySelector(`.${css.modalContent}`) : document.querySelector(`.${cssModal.modalContent}`)
newModalContent.className = cssModal.modalContent
}
render () {
var self = this
var header = yo`<div class="${css.text}">Remix Commands</div>`
self._view.autoComplete = yo`
<div class="${css.popup}">
<div>
${self.data._options.map((item, index) => {
return yo`
<div class="${css.listHandlerHide}">
<a value=${index}>
<div onclick=${handleSelect}>
${Object.keys(item)[0]}
</div>
</a>
<div>
${Object.values(item)[0]}
</div>
<hr/>
</div>
`
})}
</div>
<div class="${css.listHandlerHide}">
<button value=false onclick=${handleListIteration}></button>
<button value=true onclick=${handleListIteration}></button>
<div class="${css.pageNumberAlignment}">Page ${(self._startingElement / self._elementsToShow) + 1} of ${Math.ceil(self.data._options.length / self._elementsToShow)}</div>
</div>
</div>
`
function setUpPopUp () {
handleOpenPopup()
handleNagivationButtons()
handleListSize()
}
function handleOpenPopup () {
if (self.data._options.length > 1) {
self._view.autoComplete.style.display = 'block'
modal(header.innerText, self._view.autoComplete, {label: null},
{
fn: () => { self._removePopUp() }
})
editCSSValuesModalContainer()
}
}
function handleSelect (event) {
self._removePopUp()
self._view.autoComplete.style.display = 'none'
self.event.trigger('handleSelect', [event.srcElement.innerText])
}
function handleNagivationButtons () {
if (self.data._options.length > self._elementsToShow) {
self._view.autoComplete.children[1].className = css.listHandlerButtonShow
}
}
function handleListSize () {
if (self.data._options.length >= self._startingElement) {
for (let i = self._startingElement; i < (self._elementsToShow + self._startingElement); i++) {
if (self._view.autoComplete.children[0].children[i]) {
self._view.autoComplete.children[0].children[i].className = css.listHandlerShow
}
}
}
}
function handleListIteration (event) {
if (event.srcElement.value === 'true' || event.which === 40) {
if ((self._startingElement + self._elementsToShow) < self.data._options.length) {
self._startingElement += self._elementsToShow
}
} else {
if (self._startingElement > 0) {
self._startingElement -= self._elementsToShow
}
}
self.event.trigger('updateList')
}
function editCSSValuesModalContainer () {
var modalContent = document.querySelector(`.${cssModal.modalContent}`)
let newModalContent = modalContent ? document.querySelector(`.${cssModal.modalContent}`) : document.querySelector(`.${css.modalContent}`)
newModalContent.className = css.modalContent
}
setUpPopUp()
return self._view
}
}
module.exports = AutoCompletePopup

@ -0,0 +1,67 @@
var csjs = require('csjs-inject')
var styleGuide = require('../styles-guide/theme-chooser')
var styles = styleGuide.chooser()
var css = csjs`
.popup {
text-align : left;
display : none;
width : 100%;
font-family : monospace;
font-size : 10px;
overflow : auto;
padding-bottom : 13px;
}
.popup a {
cursor : pointer;
}
.listHandlerShow {
display : block;
}
.listHandlerHide {
display : none;
}
.listHandlerButtonShow {
position : fixed;
width : 46%;
}
.pageNumberAlignment {
font-size : 10px;
float : right;
}
.modalContent {
position : absolute;
margin-left : 20%;
margin-bottom : 32px;
bottom : 0px;
background-color : ${styles.colors.black};
padding : 0;
line-height : 18px;
font-size : 12px;
border : 1px solid ${styles.colors.grey};
width : 50%;
box-shadow : 0 4px 8px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19);
-webkit-animation-name: animatebottom;
-webkit-animation-duration: 0.4s;
animation-name : animatetop;
animation-duration: 0.4s
}
@-webkit-keyframes animatetop {
from {bottom: -300px; opacity: 0}
to {bottom: 0; opacity: 1}
}
@keyframes animatetop {
from {bottom: -300px; opacity: 0}
to {bottom: 0; opacity: 1}
}
`
module.exports = css
Loading…
Cancel
Save