Commit 464c15f9 authored by tom's avatar tom

base implementation of the selector

parent f3412634
...@@ -18,8 +18,12 @@ const TAB_LIST_PROPS = { ...@@ -18,8 +18,12 @@ const TAB_LIST_PROPS = {
const AddressContract = ({ addressHash, tabs }: Props) => { const AddressContract = ({ addressHash, tabs }: Props) => {
const fallback = React.useCallback(() => { const fallback = React.useCallback(() => {
const noProviderTabs = tabs.filter(({ id }) => id === 'contact_code'); const noProviderTabs = tabs.filter(({ id }) => id === 'contact_code');
return <RoutedTabs tabs={ noProviderTabs } variant="outline" colorScheme="gray" size="sm" tabListProps={ TAB_LIST_PROPS }/>; return (
}, [ tabs ]); <ContractContextProvider addressHash={ addressHash }>
<RoutedTabs tabs={ noProviderTabs } variant="outline" colorScheme="gray" size="sm" tabListProps={ TAB_LIST_PROPS }/>
</ContractContextProvider>
);
}, [ addressHash, tabs ]);
return ( return (
<Web3ModalProvider fallback={ fallback }> <Web3ModalProvider fallback={ fallback }>
......
...@@ -212,18 +212,10 @@ const ContractCode = ({ addressHash, noSocket }: Props) => { ...@@ -212,18 +212,10 @@ const ContractCode = ({ addressHash, noSocket }: Props) => {
isLoading={ isPlaceholderData } isLoading={ isPlaceholderData }
/> />
) } ) }
{ data?.source_code && ( <ContractSourceCode
<ContractSourceCode address={ addressHash }
data={ data.source_code } isLoading={ isPlaceholderData }
hasSol2Yml={ Boolean(data.can_be_visualized_via_sol2uml) } />
address={ addressHash }
isViper={ Boolean(data.is_vyper_contract) }
filePath={ data.file_path }
additionalSource={ data.additional_sources }
remappings={ data.compiler_settings?.remappings }
isLoading={ isPlaceholderData }
/>
) }
{ data?.compiler_settings ? ( { data?.compiler_settings ? (
<RawDataSnippet <RawDataSnippet
data={ JSON.stringify(data.compiler_settings, undefined, 4) } data={ JSON.stringify(data.compiler_settings, undefined, 4) }
......
import { Flex, Skeleton, Text, Tooltip } from '@chakra-ui/react'; import { Box, Flex, Select, Skeleton, Text, Tooltip } from '@chakra-ui/react';
import { route } from 'nextjs-routes'; import { route } from 'nextjs-routes';
import React from 'react'; import React from 'react';
import type { SmartContract } from 'types/api/contract'; import type { SmartContract } from 'types/api/contract';
import type { ArrayElement } from 'types/utils';
import CopyToClipboard from 'ui/shared/CopyToClipboard'; import CopyToClipboard from 'ui/shared/CopyToClipboard';
import LinkInternal from 'ui/shared/LinkInternal'; import LinkInternal from 'ui/shared/LinkInternal';
import CodeEditor from 'ui/shared/monaco/CodeEditor'; import CodeEditor from 'ui/shared/monaco/CodeEditor';
import formatFilePath from 'ui/shared/monaco/utils/formatFilePath'; import formatFilePath from 'ui/shared/monaco/utils/formatFilePath';
import { useContractContext } from './context';
const SOURCE_CODE_OPTIONS = [
{ id: 'primary', label: 'Proxy' } as const,
{ id: 'secondary', label: 'Implementation' } as const,
];
type SourceCodeType = ArrayElement<typeof SOURCE_CODE_OPTIONS>['id'];
function getEditorData(contractInfo: SmartContract | undefined) {
if (!contractInfo || !contractInfo.source_code) {
return undefined;
}
const defaultName = contractInfo.is_vyper_contract ? '/index.vy' : '/index.sol';
return [
{ file_path: formatFilePath(contractInfo.file_path || defaultName), source_code: contractInfo.source_code },
...(contractInfo.additional_sources || []).map((source) => ({ ...source, file_path: formatFilePath(source.file_path) })),
];
}
interface Props { interface Props {
data: string; address?: string; // todo_tom need address of proxy contract
hasSol2Yml: boolean; isLoading?: boolean; // todo_tom should be true if proxyInfo is not loaded
address?: string;
isViper: boolean;
filePath?: string;
additionalSource?: SmartContract['additional_sources'];
remappings?: Array<string>;
isLoading?: boolean;
} }
const ContractSourceCode = ({ data, hasSol2Yml, address, isViper, filePath, additionalSource, remappings, isLoading }: Props) => { // todo_tom fix mobile layout
const ContractSourceCode = ({ address, isLoading }: Props) => {
const [ sourceType, setSourceType ] = React.useState<SourceCodeType>('primary');
const { contractInfo: primaryContract, proxyInfo: secondaryContract } = useContractContext();
const editorDataPrimary = React.useMemo(() => {
return getEditorData(primaryContract);
}, [ primaryContract ]);
const editorDataSecondary = React.useMemo(() => {
return getEditorData(secondaryContract);
}, [ secondaryContract ]);
const activeContract = sourceType === 'secondary' ? secondaryContract : primaryContract;
const activeContractData = sourceType === 'secondary' ? editorDataSecondary : editorDataPrimary;
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"> ({ isViper ? 'Vyper' : 'Solidity' })</Text> <Text whiteSpace="pre" as="span" variant="secondary"> ({ activeContract?.is_vyper_contract ? 'Vyper' : 'Solidity' })</Text>
</Skeleton> </Skeleton>
); );
const diagramLink = hasSol2Yml && address ? ( const diagramLink = activeContract?.can_be_visualized_via_sol2uml && address ? (
<Tooltip label="Visualize contract code using Sol2Uml JS library"> <Tooltip label="Visualize contract code using Sol2Uml JS library">
<LinkInternal <LinkInternal
href={ route({ pathname: '/visualize/sol2uml', query: { address } }) } href={ route({ pathname: '/visualize/sol2uml', query: { address } }) }
...@@ -41,25 +72,64 @@ const ContractSourceCode = ({ data, hasSol2Yml, address, isViper, filePath, addi ...@@ -41,25 +72,64 @@ const ContractSourceCode = ({ data, hasSol2Yml, address, isViper, filePath, addi
</Tooltip> </Tooltip>
) : null; ) : null;
const editorData = React.useMemo(() => { const copyToClipboard = activeContractData?.length === 1 ?
const defaultName = isViper ? '/index.vy' : '/index.sol'; <CopyToClipboard text={ activeContractData[0].source_code } isLoading={ isLoading } ml={ 3 }/> :
return [
{ file_path: formatFilePath(filePath || defaultName), source_code: data },
...(additionalSource || []).map((source) => ({ ...source, file_path: formatFilePath(source.file_path) })) ];
}, [ additionalSource, data, filePath, isViper ]);
const copyToClipboard = editorData.length === 1 ?
<CopyToClipboard text={ editorData[0].source_code } isLoading={ isLoading } ml={ 3 }/> :
null; null;
const handleSelectChange = React.useCallback((event: React.ChangeEvent<HTMLSelectElement>) => {
setSourceType(event.target.value as SourceCodeType);
}, []);
const editorSourceTypeSelector = secondaryContract?.source_code ? (
<Select
size="xs"
borderRadius="base"
value={ sourceType }
onChange={ handleSelectChange }
focusBorderColor="none"
w="auto"
ml={ 3 }
>
{ SOURCE_CODE_OPTIONS.map((option) => <option key={ option.id } value={ option.id }>{ option.label }</option>) }
</Select>
) : null;
const content = (() => {
if (isLoading) {
return <Skeleton h="557px" w="100%"/>;
}
if (!editorDataPrimary) {
return null;
}
return (
<>
<Box display={ sourceType === 'primary' ? 'block' : 'none' }>
<CodeEditor data={ editorDataPrimary } remappings={ primaryContract?.compiler_settings?.remappings }/>
</Box>
{ editorDataSecondary && (
<Box display={ sourceType === 'secondary' ? 'block' : 'none' }>
<CodeEditor data={ editorDataSecondary } remappings={ secondaryContract?.compiler_settings?.remappings }/>
</Box>
) }
</>
);
})();
if (!editorDataPrimary) {
return null;
}
return ( return (
<section> <section>
<Flex justifyContent="space-between" alignItems="center" mb={ 3 }> <Flex justifyContent="space-between" alignItems="center" mb={ 3 }>
{ heading } { heading }
{ editorSourceTypeSelector }
{ diagramLink } { diagramLink }
{ copyToClipboard } { copyToClipboard }
</Flex> </Flex>
{ isLoading ? <Skeleton h="557px" w="100%"/> : <CodeEditor data={ editorData } remappings={ remappings }/> } { content }
</section> </section>
); );
}; };
......
...@@ -46,6 +46,7 @@ export function ContractContextProvider({ addressHash, children }: ProviderProps ...@@ -46,6 +46,7 @@ export function ContractContextProvider({ addressHash, children }: ProviderProps
}, },
}); });
// todo_tom check custom abi case
const { data: customInfo } = useApiQuery('contract_methods_write', { const { data: customInfo } = useApiQuery('contract_methods_write', {
pathParams: { hash: addressHash }, pathParams: { hash: addressHash },
queryParams: { is_custom_abi: 'true' }, queryParams: { is_custom_abi: 'true' },
......
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