fix tooltip so it originates from button and not text

pull/5370/head
Joseph Izang 1 year ago committed by Aniket
parent 6ff28da33f
commit 831cd5fe67
  1. 288
      libs/remix-ui/run-tab/src/lib/components/contractGUI.tsx

@ -4,7 +4,15 @@ import { FormattedMessage, useIntl } from 'react-intl'
import * as remixLib from '@remix-project/remix-lib' import * as remixLib from '@remix-project/remix-lib'
import {ContractGUIProps} from '../types' import {ContractGUIProps} from '../types'
import {CopyToClipboard} from '@remix-ui/clipboard' import {CopyToClipboard} from '@remix-ui/clipboard'
import { CustomTooltip, ProxyAddressToggle, ProxyDropdownMenu, shortenDate, shortenProxyAddress, unavailableProxyLayoutMsg, upgradeReportMsg } from '@remix-ui/helper' import {
CustomTooltip,
ProxyAddressToggle,
ProxyDropdownMenu,
shortenDate,
shortenProxyAddress,
unavailableProxyLayoutMsg,
upgradeReportMsg
} from '@remix-ui/helper'
import {Dropdown} from 'react-bootstrap' import {Dropdown} from 'react-bootstrap'
const txFormat = remixLib.execution.txFormat const txFormat = remixLib.execution.txFormat
@ -14,14 +22,17 @@ export function ContractGUI (props: ContractGUIProps) {
const [basicInput, setBasicInput] = useState<string>('') const [basicInput, setBasicInput] = useState<string>('')
const [toggleContainer, setToggleContainer] = useState<boolean>(false) const [toggleContainer, setToggleContainer] = useState<boolean>(false)
const [buttonOptions, setButtonOptions] = useState<{ const [buttonOptions, setButtonOptions] = useState<{
title: string, title: string
content: string, content: string
classList: string, classList: string
dataId: string dataId: string
}>({title: '', content: '', classList: '', dataId: ''}) }>({title: '', content: '', classList: '', dataId: ''})
const [toggleDeployProxy, setToggleDeployProxy] = useState<boolean>(false) const [toggleDeployProxy, setToggleDeployProxy] = useState<boolean>(false)
const [toggleUpgradeImp, setToggleUpgradeImp] = useState<boolean>(false) const [toggleUpgradeImp, setToggleUpgradeImp] = useState<boolean>(false)
const [deployState, setDeployState] = useState<{ deploy: boolean, upgrade: boolean }>({ deploy: false, upgrade: false }) const [deployState, setDeployState] = useState<{
deploy: boolean
upgrade: boolean
}>({deploy: false, upgrade: false})
const [proxyAddress, setProxyAddress] = useState<string>('') const [proxyAddress, setProxyAddress] = useState<string>('')
const [proxyAddressError, setProxyAddressError] = useState<string>('') const [proxyAddressError, setProxyAddressError] = useState<string>('')
const [showDropdown, setShowDropdown] = useState<boolean>(false) const [showDropdown, setShowDropdown] = useState<boolean>(false)
@ -32,8 +43,18 @@ export function ContractGUI (props: ContractGUIProps) {
useEffect(() => { useEffect(() => {
if (props.deployOption && Array.isArray(props.deployOption)) { if (props.deployOption && Array.isArray(props.deployOption)) {
if (props.deployOption[0] && props.deployOption[0].title === 'Deploy with Proxy' && props.deployOption[0].active) handleDeployProxySelect(true) if (
else if (props.deployOption[1] && props.deployOption[1].title === 'Upgrade with Proxy' && props.deployOption[1].active) handleUpgradeImpSelect(true) props.deployOption[0] &&
props.deployOption[0].title === 'Deploy with Proxy' &&
props.deployOption[0].active
)
handleDeployProxySelect(true)
else if (
props.deployOption[1] &&
props.deployOption[1].title === 'Upgrade with Proxy' &&
props.deployOption[1].active
)
handleUpgradeImpSelect(true)
} }
}, [props.deployOption]) }, [props.deployOption])
@ -48,7 +69,9 @@ export function ContractGUI (props: ContractGUIProps) {
setBasicInput('') setBasicInput('')
// we have the reset the fields before reseting the previous references. // we have the reset the fields before reseting the previous references.
basicInputRef.current.value = '' basicInputRef.current.value = ''
multiFields.current.filter((el) => el !== null && el !== undefined).forEach((el) => el.value = '') multiFields.current
.filter((el) => el !== null && el !== undefined)
.forEach((el) => (el.value = ''))
multiFields.current = [] multiFields.current = []
}, [props.title, props.funcABI]) }, [props.title, props.funcABI])
@ -61,7 +84,10 @@ export function ContractGUI (props: ContractGUIProps) {
classList: 'btn-info', classList: 'btn-info',
dataId: title + ' - call' dataId: title + ' - call'
}) })
} else if (props.funcABI.stateMutability === 'payable' || props.funcABI.payable) { } else if (
props.funcABI.stateMutability === 'payable' ||
props.funcABI.payable
) {
// // transact. stateMutability = payable // // transact. stateMutability = payable
setButtonOptions({ setButtonOptions({
title: title + ' - transact (payable)', title: title + ' - transact (payable)',
@ -91,7 +117,8 @@ export function ContractGUI (props: ContractGUIProps) {
const encodeObj = txFormat.encodeData( const encodeObj = txFormat.encodeData(
props.funcABI, props.funcABI,
multiJSON, multiJSON,
props.funcABI.type === 'constructor' ? props.evmBC : null) props.funcABI.type === 'constructor' ? props.evmBC : null
)
if (encodeObj.error) { if (encodeObj.error) {
console.error(encodeObj.error) console.error(encodeObj.error)
@ -138,7 +165,10 @@ export function ContractGUI (props: ContractGUIProps) {
valArrayTest.push(elVal) valArrayTest.push(elVal)
elVal = elVal.replace(/(^|,\s+|,)(\d+)(\s+,|,|$)/g, '$1"$2"$3') // replace non quoted number by quoted number elVal = elVal.replace(/(^|,\s+|,)(\d+)(\s+,|,|$)/g, '$1"$2"$3') // replace non quoted number by quoted number
elVal = elVal.replace(/(^|,\s+|,)(0[xX][0-9a-fA-F]+)(\s+,|,|$)/g, '$1"$2"$3') // replace non quoted hex string by quoted hex string elVal = elVal.replace(
/(^|,\s+|,)(0[xX][0-9a-fA-F]+)(\s+,|,|$)/g,
'$1"$2"$3'
) // replace non quoted hex string by quoted hex string
if (elVal) { if (elVal) {
try { try {
JSON.parse(elVal) JSON.parse(elVal)
@ -161,7 +191,8 @@ export function ContractGUI (props: ContractGUIProps) {
const inputString = basicInput const inputString = basicInput
if (inputString) { if (inputString) {
const inputJSON = remixLib.execution.txFormat.parseFunctionParams(inputString) const inputJSON =
remixLib.execution.txFormat.parseFunctionParams(inputString)
const multiInputs = multiFields.current const multiInputs = multiFields.current
for (let k = 0; k < multiInputs.length; k++) { for (let k = 0; k < multiInputs.length; k++) {
@ -175,26 +206,61 @@ export function ContractGUI (props: ContractGUIProps) {
const handleActionClick = async () => { const handleActionClick = async () => {
if (deployState.deploy) { if (deployState.deploy) {
const proxyInitializeString = getMultiValsString(initializeFields.current) const proxyInitializeString = getMultiValsString(initializeFields.current)
props.clickCallBack(props.initializerOptions.inputs.inputs, proxyInitializeString, ['Deploy with Proxy']) props.clickCallBack(
props.initializerOptions.inputs.inputs,
proxyInitializeString,
['Deploy with Proxy']
)
} else if (deployState.upgrade) { } else if (deployState.upgrade) {
if (proxyAddress === '') { if (proxyAddress === '') {
setProxyAddressError('proxy address cannot be empty') setProxyAddressError('proxy address cannot be empty')
} else { } else {
const isValidProxyAddress = await props.isValidProxyAddress(proxyAddress) const isValidProxyAddress = await props.isValidProxyAddress(
proxyAddress
)
if (isValidProxyAddress) { if (isValidProxyAddress) {
setProxyAddressError('') setProxyAddressError('')
const upgradeReport: any = await props.isValidProxyUpgrade(proxyAddress) const upgradeReport: any = await props.isValidProxyUpgrade(
proxyAddress
)
if (upgradeReport.ok) { if (upgradeReport.ok) {
!proxyAddressError && props.clickCallBack(props.funcABI.inputs, proxyAddress, ['Upgrade with Proxy']) !proxyAddressError &&
props.clickCallBack(props.funcABI.inputs, proxyAddress, [
'Upgrade with Proxy'
])
} else { } else {
if (upgradeReport.warning) { if (upgradeReport.warning) {
props.modal('Proxy Upgrade Warning', unavailableProxyLayoutMsg(), 'Proceed', () => { props.modal(
!proxyAddressError && props.clickCallBack(props.funcABI.inputs, proxyAddress, ['Upgrade with Proxy']) 'Proxy Upgrade Warning',
}, 'Cancel', () => {}, 'btn-warning', 'btn-secondary') unavailableProxyLayoutMsg(),
'Proceed',
() => {
!proxyAddressError &&
props.clickCallBack(props.funcABI.inputs, proxyAddress, [
'Upgrade with Proxy'
])
},
'Cancel',
() => {},
'btn-warning',
'btn-secondary'
)
} else { } else {
props.modal('Proxy Upgrade Error', upgradeReportMsg(upgradeReport), 'Continue anyway ', () => { props.modal(
!proxyAddressError && props.clickCallBack(props.funcABI.inputs, proxyAddress, ['Upgrade with Proxy']) 'Proxy Upgrade Error',
}, 'Cancel', () => {}, 'btn-warning', 'btn-secondary') upgradeReportMsg(upgradeReport),
'Continue anyway ',
() => {
!proxyAddressError &&
props.clickCallBack(props.funcABI.inputs, proxyAddress, [
'Upgrade with Proxy'
])
},
'Cancel',
() => {},
'btn-warning',
'btn-secondary'
)
} }
} }
} else { } else {
@ -262,50 +328,58 @@ export function ContractGUI (props: ContractGUIProps) {
<div <div
className={`udapp_contractProperty ${ className={`udapp_contractProperty ${
(props.funcABI.inputs && props.funcABI.inputs.length > 0) || (props.funcABI.inputs && props.funcABI.inputs.length > 0) ||
props.funcABI.type === "fallback" || props.funcABI.type === 'fallback' ||
props.funcABI.type === "receive" props.funcABI.type === 'receive'
? "udapp_hasArgs" ? 'udapp_hasArgs'
: "" : ''
}`} }`}
> >
<div <div
className="udapp_contractActionsContainerSingle pt-2" className="udapp_contractActionsContainerSingle pt-2"
style={{ display: toggleContainer ? "none" : "flex" }} style={{display: toggleContainer ? 'none' : 'flex'}}
> >
<div <CustomTooltip
className='d-flex' delay={0}
onClick={handleActionClick} placement={'right'}
tooltipClasses="text-wrap"
tooltipId="remixUdappInstanceButtonTooltip"
tooltipText={
toggleUpgradeImp && !proxyAddress
? 'Proxy address cannot be empty'
: props.inputs !== '' && basicInput === ''
? 'Input required'
: buttonOptions.title
}
> >
<div className="d-flex" onClick={handleActionClick}>
<button <button
className={`udapp_instanceButton text-nowrap overflow-hidden text-truncate ${props.widthClass} btn btn-sm ${buttonOptions.classList}`} className={`udapp_instanceButton text-nowrap overflow-hidden text-truncate ${props.widthClass} btn btn-sm ${buttonOptions.classList}`}
data-id={buttonOptions.dataId} data-id={buttonOptions.dataId}
data-title={buttonOptions.title} data-title={buttonOptions.title}
disabled={(toggleUpgradeImp && !proxyAddress) || props.disabled || (props.inputs !=='' && basicInput === '')} disabled={
> (toggleUpgradeImp && !proxyAddress) ||
<CustomTooltip props.disabled ||
delay={0} (props.inputs !== '' && basicInput === '')
placement={"right"} }
tooltipClasses="text-wrap" style={{pointerEvents: 'none'}}
tooltipId="remixUdappInstanceButtonTooltip"
tooltipText={toggleUpgradeImp && !proxyAddress ? 'Proxy address cannot be empty' : (props.inputs !=='' && basicInput === '') ? 'Input required' : buttonOptions.title}
> >
<div>{title}</div> {title}
</CustomTooltip>
</button> </button>
</div> </div>
</CustomTooltip>
<input <input
className="form-control" className="form-control"
data-id={ data-id={
props.funcABI.type === "fallback" || props.funcABI.type === 'fallback' ||
props.funcABI.type === "receive" props.funcABI.type === 'receive'
? `'(${props.funcABI.type}')` ? `'(${props.funcABI.type}')`
: "multiParamManagerBasicInputField" : 'multiParamManagerBasicInputField'
} }
placeholder={props.inputs} placeholder={props.inputs}
onChange={handleBasicInput} onChange={handleBasicInput}
data-title={ data-title={
props.funcABI.type === "fallback" || props.funcABI.type === 'fallback' ||
props.funcABI.type === "receive" props.funcABI.type === 'receive'
? `'(${props.funcABI.type}')` ? `'(${props.funcABI.type}')`
: props.inputs : props.inputs
} }
@ -314,11 +388,11 @@ export function ContractGUI (props: ContractGUIProps) {
height: '2rem', height: '2rem',
visibility: !( visibility: !(
(props.funcABI.inputs && props.funcABI.inputs.length > 0) || (props.funcABI.inputs && props.funcABI.inputs.length > 0) ||
props.funcABI.type === "fallback" || props.funcABI.type === 'fallback' ||
props.funcABI.type === "receive" props.funcABI.type === 'receive'
) )
? "hidden" ? 'hidden'
: "visible", : 'visible'
}} }}
/> />
<i <i
@ -328,14 +402,14 @@ export function ContractGUI (props: ContractGUIProps) {
visibility: !( visibility: !(
props.funcABI.inputs && props.funcABI.inputs.length > 0 props.funcABI.inputs && props.funcABI.inputs.length > 0
) )
? "hidden" ? 'hidden'
: "visible", : 'visible'
}} }}
></i> ></i>
</div> </div>
<div <div
className="udapp_contractActionsContainerMulti" className="udapp_contractActionsContainerMulti"
style={{ display: toggleContainer ? "flex" : "none" }} style={{display: toggleContainer ? 'flex' : 'none'}}
> >
<div className="udapp_contractActionsContainerMultiInner text-dark"> <div className="udapp_contractActionsContainerMultiInner text-dark">
<div onClick={switchMethodViewOff} className="udapp_multiHeader"> <div onClick={switchMethodViewOff} className="udapp_multiHeader">
@ -357,7 +431,7 @@ export function ContractGUI (props: ContractGUIProps) {
> >
<input <input
ref={(el) => { ref={(el) => {
multiFields.current[index] = el; multiFields.current[index] = el
}} }}
className="form-control" className="form-control"
placeholder={inp.type} placeholder={inp.type}
@ -366,14 +440,14 @@ export function ContractGUI (props: ContractGUIProps) {
/> />
</CustomTooltip> </CustomTooltip>
</div> </div>
); )
})} })}
</div> </div>
<div className="d-flex udapp_group udapp_multiArg"> <div className="d-flex udapp_group udapp_multiArg">
<CopyToClipboard <CopyToClipboard
tip={intl.formatMessage({id: 'udapp.copyCalldata'})} tip={intl.formatMessage({id: 'udapp.copyCalldata'})}
icon="fa-clipboard" icon="fa-clipboard"
direction={"bottom"} direction={'bottom'}
getContent={getEncodedCall} getContent={getEncodedCall}
> >
<button className="btn remixui_copyButton"> <button className="btn remixui_copyButton">
@ -388,7 +462,7 @@ export function ContractGUI (props: ContractGUIProps) {
<CopyToClipboard <CopyToClipboard
tip={intl.formatMessage({id: 'udapp.copyParameters'})} tip={intl.formatMessage({id: 'udapp.copyParameters'})}
icon="fa-clipboard" icon="fa-clipboard"
direction={"bottom"} direction={'bottom'}
getContent={getEncodedParams} getContent={getEncodedParams}
> >
<button className="btn remixui_copyButton"> <button className="btn remixui_copyButton">
@ -397,23 +471,25 @@ export function ContractGUI (props: ContractGUIProps) {
className="m-0 remixui_copyIcon far fa-copy" className="m-0 remixui_copyIcon far fa-copy"
aria-hidden="true" aria-hidden="true"
></i> ></i>
<label htmlFor="copyParameters"><FormattedMessage id='udapp.parameters' /></label> <label htmlFor="copyParameters">
<FormattedMessage id="udapp.parameters" />
</label>
</button> </button>
</CopyToClipboard> </CopyToClipboard>
<CustomTooltip <CustomTooltip
placement={"right"} placement={'right'}
tooltipClasses="text-nowrap" tooltipClasses="text-nowrap"
tooltipId="remixUdappInstanceButtonTooltip" tooltipId="remixUdappInstanceButtonTooltip"
tooltipText={buttonOptions.title} tooltipText={buttonOptions.title}
> >
<div <div onClick={handleExpandMultiClick}>
onClick={handleExpandMultiClick}
>
<button <button
type="button" type="button"
data-id={buttonOptions.dataId} data-id={buttonOptions.dataId}
className={`udapp_instanceButton btn ${buttonOptions.classList}`} className={`udapp_instanceButton btn ${buttonOptions.classList}`}
disabled={props.disabled || (props.inputs !=='' && basicInput === '')} disabled={
props.disabled || (props.inputs !== '' && basicInput === '')
}
> >
{buttonOptions.content} {buttonOptions.content}
</button> </button>
@ -439,14 +515,19 @@ export function ContractGUI (props: ContractGUIProps) {
data-id="contractGUIDeployWithProxyLabel" data-id="contractGUIDeployWithProxyLabel"
className="m-0 form-check-label w-100 custom-control-label udapp_checkboxAlign" className="m-0 form-check-label w-100 custom-control-label udapp_checkboxAlign"
> >
<FormattedMessage id='udapp.deployWithProxy' /> <FormattedMessage id="udapp.deployWithProxy" />
</label> </label>
</div> </div>
<div> <div>
{props.initializerOptions && {props.initializerOptions &&
props.initializerOptions.initializeInputs ? ( props.initializerOptions.initializeInputs ? (
<span onClick={handleToggleDeployProxy}> <span onClick={handleToggleDeployProxy}>
<i className={!toggleDeployProxy ? "fas fa-angle-right pt-2" : "fas fa-angle-down"} <i
className={
!toggleDeployProxy
? 'fas fa-angle-right pt-2'
: 'fas fa-angle-down'
}
aria-hidden="true" aria-hidden="true"
></i> ></i>
</span> </span>
@ -457,7 +538,7 @@ export function ContractGUI (props: ContractGUIProps) {
props.initializerOptions.initializeInputs ? ( props.initializerOptions.initializeInputs ? (
<div <div
className={`pl-4 flex-column ${ className={`pl-4 flex-column ${
toggleDeployProxy ? "d-flex" : "d-none" toggleDeployProxy ? 'd-flex' : 'd-none'
}`} }`}
> >
<div className={`flex-column 'd-flex'}`}> <div className={`flex-column 'd-flex'}`}>
@ -468,19 +549,19 @@ export function ContractGUI (props: ContractGUIProps) {
className="mt-2 text-left d-block" className="mt-2 text-left d-block"
htmlFor={inp.name} htmlFor={inp.name}
> >
{" "} {' '}
{inp.name}:{" "} {inp.name}:{' '}
</label> </label>
<input <input
ref={(el) => { ref={(el) => {
initializeFields.current[index] = el; initializeFields.current[index] = el
}} }}
style={{height: 32}} style={{height: 32}}
className="form-control udapp_input" className="form-control udapp_input"
placeholder={inp.type} placeholder={inp.type}
/> />
</div> </div>
); )
})} })}
</div> </div>
</div> </div>
@ -500,15 +581,15 @@ export function ContractGUI (props: ContractGUIProps) {
data-id="contractGUIUpgradeImplementationLabel" data-id="contractGUIUpgradeImplementationLabel"
className="m-0 form-check-label custom-control-label udapp_checkboxAlign" className="m-0 form-check-label custom-control-label udapp_checkboxAlign"
> >
<FormattedMessage id='udapp.upgradeWithProxy' /> <FormattedMessage id="udapp.upgradeWithProxy" />
</label> </label>
</div> </div>
<span onClick={handleToggleUpgradeImp}> <span onClick={handleToggleUpgradeImp}>
<i <i
className={ className={
!toggleUpgradeImp !toggleUpgradeImp
? "fas fa-angle-right pt-2" ? 'fas fa-angle-right pt-2'
: "fas fa-angle-down" : 'fas fa-angle-down'
} }
aria-hidden="true" aria-hidden="true"
></i> ></i>
@ -516,19 +597,28 @@ export function ContractGUI (props: ContractGUIProps) {
</div> </div>
<div <div
className={`pl-4 flex-column ${ className={`pl-4 flex-column ${
toggleUpgradeImp ? "d-flex" : "d-none" toggleUpgradeImp ? 'd-flex' : 'd-none'
}`} }`}
> >
<div data-id="proxy-dropdown-items"> <div data-id="proxy-dropdown-items">
<Dropdown onToggle={toggleDropdown} show={showDropdown}> <Dropdown onToggle={toggleDropdown} show={showDropdown}>
<Dropdown.Toggle id="dropdown-custom-components" as={ProxyAddressToggle} address={proxyAddress} onChange={handleAddressChange} className="d-inline-block border border-dark bg-dark" /> <Dropdown.Toggle
id="dropdown-custom-components"
as={ProxyAddressToggle}
address={proxyAddress}
onChange={handleAddressChange}
className="d-inline-block border border-dark bg-dark"
/>
{ props.proxy.deployments.length > 0 && {props.proxy.deployments.length > 0 && (
<Dropdown.Menu as={ProxyDropdownMenu} className='w-100 custom-dropdown-items' style={{ overflow: 'hidden' }}> <Dropdown.Menu
{ as={ProxyDropdownMenu}
props.proxy.deployments.map((deployment, index) => ( className="w-100 custom-dropdown-items"
style={{overflow: 'hidden'}}
>
{props.proxy.deployments.map((deployment, index) => (
<CustomTooltip <CustomTooltip
placement={"right"} placement={'right'}
tooltipClasses="text-nowrap" tooltipClasses="text-nowrap"
tooltipId={`proxyAddressTooltip${index}`} tooltipId={`proxyAddressTooltip${index}`}
tooltipText={'Deployed ' + shortenDate(deployment.date)} tooltipText={'Deployed ' + shortenDate(deployment.date)}
@ -536,30 +626,50 @@ export function ContractGUI (props: ContractGUIProps) {
> >
<Dropdown.Item <Dropdown.Item
key={index} key={index}
onClick={() => { switchProxyAddress(deployment.address) }} onClick={() => {
switchProxyAddress(deployment.address)
}}
data-id={`proxyAddress${index}`} data-id={`proxyAddress${index}`}
> >
<span> <span>
{ proxyAddress === deployment.address ? {proxyAddress === deployment.address ? (
<span>&#10003; { deployment.contractName + ' ' + shortenProxyAddress(deployment.address) } </span> <span>
: <span className="pl-3">{ deployment.contractName + ' ' + shortenProxyAddress(deployment.address) }</span> } &#10003;{' '}
{deployment.contractName +
' ' +
shortenProxyAddress(deployment.address)}{' '}
</span>
) : (
<span className="pl-3">
{deployment.contractName +
' ' +
shortenProxyAddress(deployment.address)}
</span>
)}
</span> </span>
</Dropdown.Item> </Dropdown.Item>
</CustomTooltip> </CustomTooltip>
)) ))}
}
</Dropdown.Menu> </Dropdown.Menu>
} )}
</Dropdown> </Dropdown>
</div> </div>
<div className='d-flex'> <div className="d-flex">
<div className="mb-2"> <div className="mb-2">
{ proxyAddressError && <span className='text-lowercase text-danger' data-id="errorMsgProxyAddress" style={{ fontSize: '.8em' }}>{ proxyAddressError }</span> } {proxyAddressError && (
<span
className="text-lowercase text-danger"
data-id="errorMsgProxyAddress"
style={{fontSize: '.8em'}}
>
{proxyAddressError}
</span>
)}
</div> </div>
</div> </div>
</div> </div>
</> </>
) : null} ) : null}
</div> </div>
); )
} }

Loading…
Cancel
Save