fix tooltip so it originates from button and not text

pull/3958/head^2
Joseph Izang 1 year ago committed by Aniket
parent fa6a617c49
commit 880eebc732
  1. 360
      libs/remix-ui/run-tab/src/lib/components/contractGUI.tsx

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

Loading…
Cancel
Save