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> = [
method_id: '70a08231',
name: 'FLASHLOAN_PREMIUM_TOTAL',
outputs: [
{ internalType: 'uint256', name: '', type: 'uint256', value: '' },
{ internalType: 'uint256', name: '', type: 'uint256' },
],
payable: false,
stateMutability: 'view',
......
......@@ -91,7 +91,7 @@ export interface SmartContractMethodInput {
}
export interface SmartContractMethodOutput extends SmartContractMethodInput {
value?: string;
value?: string | boolean;
}
export interface SmartContractQueryMethodReadSuccess {
......
......@@ -17,11 +17,11 @@ interface Props {
const ContractMethodStatic = ({ data }: Props) => {
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 handleCheckboxChange = React.useCallback((event: ChangeEvent<HTMLInputElement>) => {
if (!data.value) {
if (!data.value || typeof data.value !== 'string') {
return;
}
......@@ -35,7 +35,7 @@ const ContractMethodStatic = ({ data }: Props) => {
}, [ data.value ]);
const content = (() => {
if (data.type === 'address' && data.value) {
if (typeof data.value === 'string' && data.type === 'address' && data.value) {
return (
<Address>
<AddressLink type="address" hash={ data.value }/>
......@@ -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 (
......
......@@ -61,7 +61,7 @@ const ContractRead = ({ addressHash, isProxy, isCustomAbi }: Props) => {
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 (
<Flex flexDir="column" rowGap={ 1 }>
{ item.outputs.map((output, index) => <ContractMethodConstant key={ index } data={ output }/>) }
......
......@@ -75,8 +75,8 @@ const ContractSourceCode = ({ address, implementationAddress }: Props) => {
}, [ primaryContractQuery.data ]);
const secondaryEditorData = React.useMemo(() => {
return getEditorData(secondaryContractQuery.data);
}, [ secondaryContractQuery.data ]);
return secondaryContractQuery.isPlaceholderData ? undefined : getEditorData(secondaryContractQuery.data);
}, [ secondaryContractQuery.data, secondaryContractQuery.isPlaceholderData ]);
const activeContract = sourceType === 'secondary' ? secondaryContractQuery.data : primaryContractQuery.data;
const activeContractData = sourceType === 'secondary' ? secondaryEditorData : primaryEditorData;
......@@ -84,7 +84,8 @@ const ContractSourceCode = ({ address, implementationAddress }: Props) => {
const heading = (
<Skeleton isLoaded={ !isLoading } fontWeight={ 500 }>
<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>
);
......
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';
import type { FileTree } from './types';
import iconFile from './icons/file.svg';
import CodeEditorFileIcon from './CodeEditorFileIcon';
import iconFolderOpen from './icons/folder-open.svg';
import iconFolder from './icons/folder.svg';
import iconSolidity from './icons/solidity.svg';
import useThemeColors from './utils/useThemeColors';
interface Props {
......@@ -74,8 +73,6 @@ const CodeEditorFileTree = ({ tree, level = 0, onItemClick, isCollapsed, selecte
);
}
const icon = /.sol|.yul|.vy$/.test(leaf.name) ? iconSolidity : iconFile;
return (
<AccordionItem
key={ index }
......@@ -92,7 +89,7 @@ const CodeEditorFileTree = ({ tree, level = 0, onItemClick, isCollapsed, selecte
}}
bgColor={ selectedFile === leaf.file_path ? themeColors['list.inactiveSelectionBackground'] : 'none' }
>
<Icon as={ icon } boxSize="16px" mr="4px"/>
<CodeEditorFileIcon fileName={ leaf.name } mr="4px"/>
{ leafName }
</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 type { SearchResult } from './types';
import CodeEditorFileIcon from './CodeEditorFileIcon';
import CodeEditorSearchResultItem from './CodeEditorSearchResultItem';
import iconFile from './icons/file.svg';
import iconSolidity from './icons/solidity.svg';
import getFileName from './utils/getFileName';
import useThemeColors from './utils/useThemeColors';
......@@ -24,8 +23,6 @@ const CodeEditorSearchSection = ({ data, onItemClick }: Props) => {
}
}, [ data.file_path, onItemClick ]);
const icon = /.sol|.yul|.vy$/.test(fileName) ? iconSolidity : iconFile;
const themeColors = useThemeColors();
return (
......@@ -47,10 +44,26 @@ const CodeEditorSearchSection = ({ data, onItemClick }: Props) => {
width="20px"
height="22px"
py="3px"
flexShrink={ 0 }
/>
<Icon as={ icon } boxSize="16px" mr="4px"/>
<span>{ fileName }</span>
<Box className="monaco-count-badge" ml="auto" bgColor={ themeColors['badge.background'] }>{ data.matches.length }</Box>
<CodeEditorFileIcon mr="4px" fileName={ fileName }/>
<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>
<AccordionPanel p={ 0 }>
{ 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 { alt } from 'lib/html-entities';
import useThemeColors from 'ui/shared/monaco/utils/useThemeColors';
import iconFile from './icons/file.svg';
import iconSolidity from './icons/solidity.svg';
import CodeEditorFileIcon from './CodeEditorFileIcon';
import getFilePathParts from './utils/getFilePathParts';
interface Props {
......@@ -30,8 +29,6 @@ const CodeEditorTab = ({ isActive, path, onClick, onClose, isCloseDisabled, tabs
!isCloseDisabled && onClose(path);
}, [ isCloseDisabled, onClose, path ]);
const icon = /.sol|.yul|.vy$/.test(fileName) ? iconSolidity : iconFile;
return (
<Flex
pl="10px"
......@@ -55,7 +52,7 @@ const CodeEditorTab = ({ isActive, path, onClick, onClose, isCloseDisabled, tabs
}}
userSelect="none"
>
<Icon as={ icon } boxSize="16px" mr="4px"/>
<CodeEditorFileIcon mr="4px" fileName={ fileName }/>
<span>{ fileName }</span>
{ folderName && <chakra.span fontSize="11px" opacity={ 0.8 } ml={ 1 }>{ folderName[0] === '.' ? '' : '...' }{ folderName }</chakra.span> }
<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', () => {
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', () => {
const result = getFullPathOfImportedFile(
'/index.sol',
......
......@@ -19,7 +19,7 @@ export default function getFullPathOfImportedFile(baseFilePath: string, imported
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];
if (element === '.') {
......@@ -41,7 +41,5 @@ export default function getFullPathOfImportedFile(baseFilePath: string, imported
return;
}
result.push(importedFileChunks[importedFileChunks.length - 1]);
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