Commit 48222553 authored by tom goriunov's avatar tom goriunov Committed by GitHub

Merge pull request #940 from blockscout/bugfix/contract-editor-layout-fixes

bugfix: contract editor improvements
parents 9dfa309d 3aeb86e4
...@@ -14,7 +14,7 @@ export const read: Array<SmartContractReadMethod> = [ ...@@ -14,7 +14,7 @@ export const read: Array<SmartContractReadMethod> = [
method_id: '70a08231', method_id: '70a08231',
name: 'FLASHLOAN_PREMIUM_TOTAL', name: 'FLASHLOAN_PREMIUM_TOTAL',
outputs: [ outputs: [
{ internalType: 'uint256', name: '', type: 'uint256', value: '' }, { internalType: 'uint256', name: '', type: 'uint256' },
], ],
payable: false, payable: false,
stateMutability: 'view', stateMutability: 'view',
......
...@@ -91,7 +91,7 @@ export interface SmartContractMethodInput { ...@@ -91,7 +91,7 @@ export interface SmartContractMethodInput {
} }
export interface SmartContractMethodOutput extends SmartContractMethodInput { export interface SmartContractMethodOutput extends SmartContractMethodInput {
value?: string; value?: string | boolean;
} }
export interface SmartContractQueryMethodReadSuccess { export interface SmartContractQueryMethodReadSuccess {
......
...@@ -17,11 +17,11 @@ interface Props { ...@@ -17,11 +17,11 @@ interface Props {
const ContractMethodStatic = ({ data }: Props) => { const ContractMethodStatic = ({ data }: Props) => {
const isBigInt = data.type.includes('int256') || data.type.includes('int128'); const isBigInt = data.type.includes('int256') || data.type.includes('int128');
const [ value, setValue ] = React.useState(isBigInt && data.value ? BigNumber(data.value).toFixed() : data.value); const [ value, setValue ] = React.useState(isBigInt && data.value && typeof data.value === 'string' ? BigNumber(data.value).toFixed() : data.value);
const [ label, setLabel ] = React.useState('WEI'); const [ label, setLabel ] = React.useState('WEI');
const handleCheckboxChange = React.useCallback((event: ChangeEvent<HTMLInputElement>) => { const handleCheckboxChange = React.useCallback((event: ChangeEvent<HTMLInputElement>) => {
if (!data.value) { if (!data.value || typeof data.value !== 'string') {
return; return;
} }
...@@ -35,7 +35,7 @@ const ContractMethodStatic = ({ data }: Props) => { ...@@ -35,7 +35,7 @@ const ContractMethodStatic = ({ data }: Props) => {
}, [ data.value ]); }, [ data.value ]);
const content = (() => { const content = (() => {
if (data.type === 'address' && data.value) { if (typeof data.value === 'string' && data.type === 'address' && data.value) {
return ( return (
<Address> <Address>
<AddressLink type="address" hash={ data.value }/> <AddressLink type="address" hash={ data.value }/>
...@@ -44,7 +44,7 @@ const ContractMethodStatic = ({ data }: Props) => { ...@@ -44,7 +44,7 @@ const ContractMethodStatic = ({ data }: Props) => {
); );
} }
return <chakra.span wordBreak="break-all">({ data.type }): { value }</chakra.span>; return <chakra.span wordBreak="break-all">({ data.type }): { String(value) }</chakra.span>;
})(); })();
return ( return (
......
...@@ -61,7 +61,7 @@ const ContractRead = ({ addressHash, isProxy, isCustomAbi }: Props) => { ...@@ -61,7 +61,7 @@ const ContractRead = ({ addressHash, isProxy, isCustomAbi }: Props) => {
return <Alert status="error" fontSize="sm" wordBreak="break-word">{ item.error }</Alert>; return <Alert status="error" fontSize="sm" wordBreak="break-word">{ item.error }</Alert>;
} }
if (item.outputs.some(({ value }) => value)) { if (item.outputs.some(({ value }) => value !== undefined && value !== null)) {
return ( return (
<Flex flexDir="column" rowGap={ 1 }> <Flex flexDir="column" rowGap={ 1 }>
{ item.outputs.map((output, index) => <ContractMethodConstant key={ index } data={ output }/>) } { item.outputs.map((output, index) => <ContractMethodConstant key={ index } data={ output }/>) }
......
...@@ -75,8 +75,8 @@ const ContractSourceCode = ({ address, implementationAddress }: Props) => { ...@@ -75,8 +75,8 @@ const ContractSourceCode = ({ address, implementationAddress }: Props) => {
}, [ primaryContractQuery.data ]); }, [ primaryContractQuery.data ]);
const secondaryEditorData = React.useMemo(() => { const secondaryEditorData = React.useMemo(() => {
return getEditorData(secondaryContractQuery.data); return secondaryContractQuery.isPlaceholderData ? undefined : getEditorData(secondaryContractQuery.data);
}, [ secondaryContractQuery.data ]); }, [ secondaryContractQuery.data, secondaryContractQuery.isPlaceholderData ]);
const activeContract = sourceType === 'secondary' ? secondaryContractQuery.data : primaryContractQuery.data; const activeContract = sourceType === 'secondary' ? secondaryContractQuery.data : primaryContractQuery.data;
const activeContractData = sourceType === 'secondary' ? secondaryEditorData : primaryEditorData; const activeContractData = sourceType === 'secondary' ? secondaryEditorData : primaryEditorData;
...@@ -84,7 +84,8 @@ const ContractSourceCode = ({ address, implementationAddress }: Props) => { ...@@ -84,7 +84,8 @@ const ContractSourceCode = ({ address, implementationAddress }: Props) => {
const heading = ( const heading = (
<Skeleton isLoaded={ !isLoading } fontWeight={ 500 }> <Skeleton isLoaded={ !isLoading } fontWeight={ 500 }>
<span>Contract source code</span> <span>Contract source code</span>
<Text whiteSpace="pre" as="span" variant="secondary" textTransform="capitalize"> ({ activeContract?.language })</Text> { activeContract?.language &&
<Text whiteSpace="pre" as="span" variant="secondary" textTransform="capitalize"> ({ activeContract.language })</Text> }
</Skeleton> </Skeleton>
); );
......
import { Icon, chakra } from '@chakra-ui/react';
import React from 'react';
import iconFile from './icons/file.svg';
import iconSolidity from './icons/solidity.svg';
import iconVyper from './icons/vyper.svg';
interface Props {
className?: string;
fileName: string;
}
const CodeEditorFileIcon = ({ className, fileName }: Props) => {
const as = (() => {
if (/.vy$/.test(fileName)) {
return iconVyper;
}
if (/.sol|.yul$/.test(fileName)) {
return iconSolidity;
}
return iconFile;
})();
return <Icon className={ className } as={ as } boxSize="16px"/>;
};
export default React.memo(chakra(CodeEditorFileIcon));
...@@ -4,10 +4,9 @@ import React from 'react'; ...@@ -4,10 +4,9 @@ import React from 'react';
import type { FileTree } from './types'; import type { FileTree } from './types';
import iconFile from './icons/file.svg'; import CodeEditorFileIcon from './CodeEditorFileIcon';
import iconFolderOpen from './icons/folder-open.svg'; import iconFolderOpen from './icons/folder-open.svg';
import iconFolder from './icons/folder.svg'; import iconFolder from './icons/folder.svg';
import iconSolidity from './icons/solidity.svg';
import useThemeColors from './utils/useThemeColors'; import useThemeColors from './utils/useThemeColors';
interface Props { interface Props {
...@@ -74,8 +73,6 @@ const CodeEditorFileTree = ({ tree, level = 0, onItemClick, isCollapsed, selecte ...@@ -74,8 +73,6 @@ const CodeEditorFileTree = ({ tree, level = 0, onItemClick, isCollapsed, selecte
); );
} }
const icon = /.sol|.yul|.vy$/.test(leaf.name) ? iconSolidity : iconFile;
return ( return (
<AccordionItem <AccordionItem
key={ index } key={ index }
...@@ -92,7 +89,7 @@ const CodeEditorFileTree = ({ tree, level = 0, onItemClick, isCollapsed, selecte ...@@ -92,7 +89,7 @@ const CodeEditorFileTree = ({ tree, level = 0, onItemClick, isCollapsed, selecte
}} }}
bgColor={ selectedFile === leaf.file_path ? themeColors['list.inactiveSelectionBackground'] : 'none' } bgColor={ selectedFile === leaf.file_path ? themeColors['list.inactiveSelectionBackground'] : 'none' }
> >
<Icon as={ icon } boxSize="16px" mr="4px"/> <CodeEditorFileIcon fileName={ leaf.name } mr="4px"/>
{ leafName } { leafName }
</AccordionItem> </AccordionItem>
); );
......
import { AccordionButton, AccordionItem, AccordionPanel, Icon, Box } from '@chakra-ui/react'; import { AccordionButton, AccordionItem, AccordionPanel, Box } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { SearchResult } from './types'; import type { SearchResult } from './types';
import CodeEditorFileIcon from './CodeEditorFileIcon';
import CodeEditorSearchResultItem from './CodeEditorSearchResultItem'; import CodeEditorSearchResultItem from './CodeEditorSearchResultItem';
import iconFile from './icons/file.svg';
import iconSolidity from './icons/solidity.svg';
import getFileName from './utils/getFileName'; import getFileName from './utils/getFileName';
import useThemeColors from './utils/useThemeColors'; import useThemeColors from './utils/useThemeColors';
...@@ -24,8 +23,6 @@ const CodeEditorSearchSection = ({ data, onItemClick }: Props) => { ...@@ -24,8 +23,6 @@ const CodeEditorSearchSection = ({ data, onItemClick }: Props) => {
} }
}, [ data.file_path, onItemClick ]); }, [ data.file_path, onItemClick ]);
const icon = /.sol|.yul|.vy$/.test(fileName) ? iconSolidity : iconFile;
const themeColors = useThemeColors(); const themeColors = useThemeColors();
return ( return (
...@@ -47,10 +44,26 @@ const CodeEditorSearchSection = ({ data, onItemClick }: Props) => { ...@@ -47,10 +44,26 @@ const CodeEditorSearchSection = ({ data, onItemClick }: Props) => {
width="20px" width="20px"
height="22px" height="22px"
py="3px" py="3px"
flexShrink={ 0 }
/> />
<Icon as={ icon } boxSize="16px" mr="4px"/> <CodeEditorFileIcon mr="4px" fileName={ fileName }/>
<span>{ fileName }</span> <Box
<Box className="monaco-count-badge" ml="auto" bgColor={ themeColors['badge.background'] }>{ data.matches.length }</Box> mr="8px"
whiteSpace="nowrap"
overflow="hidden"
textOverflow="ellipsis"
textAlign="left"
>
{ fileName }
</Box>
<Box
className="monaco-count-badge"
ml="auto"
bgColor={ themeColors['badge.background'] }
flexShrink={ 0 }
>
{ data.matches.length }
</Box>
</AccordionButton> </AccordionButton>
<AccordionPanel p={ 0 }> <AccordionPanel p={ 0 }>
{ data.matches.map((match) => ( { data.matches.map((match) => (
......
import { Flex, Icon, chakra, Box } from '@chakra-ui/react'; import { Flex, chakra, Box } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import { alt } from 'lib/html-entities'; import { alt } from 'lib/html-entities';
import useThemeColors from 'ui/shared/monaco/utils/useThemeColors'; import useThemeColors from 'ui/shared/monaco/utils/useThemeColors';
import iconFile from './icons/file.svg'; import CodeEditorFileIcon from './CodeEditorFileIcon';
import iconSolidity from './icons/solidity.svg';
import getFilePathParts from './utils/getFilePathParts'; import getFilePathParts from './utils/getFilePathParts';
interface Props { interface Props {
...@@ -30,8 +29,6 @@ const CodeEditorTab = ({ isActive, path, onClick, onClose, isCloseDisabled, tabs ...@@ -30,8 +29,6 @@ const CodeEditorTab = ({ isActive, path, onClick, onClose, isCloseDisabled, tabs
!isCloseDisabled && onClose(path); !isCloseDisabled && onClose(path);
}, [ isCloseDisabled, onClose, path ]); }, [ isCloseDisabled, onClose, path ]);
const icon = /.sol|.yul|.vy$/.test(fileName) ? iconSolidity : iconFile;
return ( return (
<Flex <Flex
pl="10px" pl="10px"
...@@ -55,7 +52,7 @@ const CodeEditorTab = ({ isActive, path, onClick, onClose, isCloseDisabled, tabs ...@@ -55,7 +52,7 @@ const CodeEditorTab = ({ isActive, path, onClick, onClose, isCloseDisabled, tabs
}} }}
userSelect="none" userSelect="none"
> >
<Icon as={ icon } boxSize="16px" mr="4px"/> <CodeEditorFileIcon mr="4px" fileName={ fileName }/>
<span>{ fileName }</span> <span>{ fileName }</span>
{ folderName && <chakra.span fontSize="11px" opacity={ 0.8 } ml={ 1 }>{ folderName[0] === '.' ? '' : '...' }{ folderName }</chakra.span> } { folderName && <chakra.span fontSize="11px" opacity={ 0.8 } ml={ 1 }>{ folderName[0] === '.' ? '' : '...' }{ folderName }</chakra.span> }
<Box <Box
......
<svg xmlns="http://www.w3.org/2000/svg" data-name="Transparent Logo" viewBox="0 0 2048 1773.62">
<path d="m1024 886.81-256 443.41 256 443.4 256-443.4-256-443.41z" style="opacity:.8"/>
<path d="m1280 443.41-256 443.4 256 443.41 256-443.41-256-443.4zm-512 0-256 443.4 256 443.41 256-443.41-256-443.4z" style="opacity:.6"/>
<path d="m1536 0-256 443.41 256 443.4 256-443.4L1536 0zm-384 221.7H896L768 443.41l256 443.4 256-443.4-128-221.71zM512 0 256 443.41l256 443.4 256-443.4L512 0z" style="opacity:.45"/>
<path d="M1792 443.4 2048 0h-512l256 443.4zm-1536 0L512 0H0l256 443.4z" style="opacity:.3"/>
</svg>
...@@ -9,6 +9,15 @@ it('construct correct absolute path', () => { ...@@ -9,6 +9,15 @@ it('construct correct absolute path', () => {
expect(result).toBe('/foo/abc/contract.sol'); expect(result).toBe('/foo/abc/contract.sol');
}); });
it('construct correct absolute path if file is in the current directory', () => {
const result = getFullPathOfImportedFile(
'/abc/index.sol',
'./contract.sol',
);
expect(result).toBe('/abc/contract.sol');
});
it('returns undefined if imported file is outside the base file folder', () => { it('returns undefined if imported file is outside the base file folder', () => {
const result = getFullPathOfImportedFile( const result = getFullPathOfImportedFile(
'/index.sol', '/index.sol',
......
...@@ -19,7 +19,7 @@ export default function getFullPathOfImportedFile(baseFilePath: string, imported ...@@ -19,7 +19,7 @@ export default function getFullPathOfImportedFile(baseFilePath: string, imported
const result: Array<string> = baseFileChunks.slice(0, -1); const result: Array<string> = baseFileChunks.slice(0, -1);
for (let index = 0; index < importedFileChunks.length - 1; index++) { for (let index = 0; index < importedFileChunks.length; index++) {
const element = importedFileChunks[index]; const element = importedFileChunks[index];
if (element === '.') { if (element === '.') {
...@@ -41,7 +41,5 @@ export default function getFullPathOfImportedFile(baseFilePath: string, imported ...@@ -41,7 +41,5 @@ export default function getFullPathOfImportedFile(baseFilePath: string, imported
return; return;
} }
result.push(importedFileChunks[importedFileChunks.length - 1]);
return '/' + result.join('/'); return '/' + result.join('/');
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment