mirror of
https://github.com/luxfi/explore.git
synced 2026-09-24 00:28:18 +00:00
sources field changes
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
<svg viewBox="0 0 51 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.693 5.058c.498-.57 1.172-.891 1.875-.891h15.91c.351 0 .688.16.937.446l9.28 10.657c.249.285.388.672.388 1.076v24.36c0 .807-.279 1.581-.776 2.152-.498.571-1.172.892-1.875.892H13.568c-.703 0-1.377-.32-1.875-.892-.497-.57-.776-1.345-.776-2.153V7.212c0-.808.28-1.582.776-2.154Zm17.235 2.154h-15.36v33.493h23.864V16.977l-8.504-9.765Z" fill="currentColor"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M28.556 5c.767 0 1.389.622 1.389 1.389v8.333h8.333a1.389 1.389 0 0 1 0 2.778h-9.722a1.389 1.389 0 0 1-1.39-1.389V6.39c0-.767.623-1.389 1.39-1.389ZM22.46 25.151a1.326 1.326 0 0 0-1.875 1.875l3.04 3.04-3.04 3.04a1.326 1.326 0 0 0 1.875 1.875l3.04-3.04 3.04 3.04a1.326 1.326 0 0 0 1.875-1.875l-3.04-3.04 3.04-3.04a1.326 1.326 0 0 0-1.875-1.875l-3.04 3.04-3.04-3.04Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 923 B |
@@ -1,4 +1,4 @@
|
||||
import { Text, Button, Box, chakra, Flex } from '@chakra-ui/react';
|
||||
import { Text, Button, Box, Flex } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
import type { ControllerRenderProps, FieldPathValue, ValidateResult } from 'react-hook-form';
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
@@ -6,7 +6,7 @@ import { Controller, useFormContext } from 'react-hook-form';
|
||||
import type { FormFields } from '../types';
|
||||
|
||||
import { Mb } from 'lib/consts';
|
||||
// import DragAndDropArea from 'ui/shared/forms/DragAndDropArea';
|
||||
import DragAndDropArea from 'ui/shared/forms/DragAndDropArea';
|
||||
import FieldError from 'ui/shared/forms/FieldError';
|
||||
import FileInput from 'ui/shared/forms/FileInput';
|
||||
import FileSnippet from 'ui/shared/forms/FileSnippet';
|
||||
@@ -19,11 +19,10 @@ interface Props {
|
||||
fileTypes: Array<FileTypes>;
|
||||
multiple?: boolean;
|
||||
title: string;
|
||||
className?: string;
|
||||
hint: string;
|
||||
}
|
||||
|
||||
const ContractVerificationFieldSources = ({ fileTypes, multiple, title, className, hint }: Props) => {
|
||||
const ContractVerificationFieldSources = ({ fileTypes, multiple, title, hint }: Props) => {
|
||||
const { setValue, getValues, control, formState, clearErrors } = useFormContext<FormFields>();
|
||||
|
||||
const error = 'sources' in formState.errors ? formState.errors.sources : undefined;
|
||||
@@ -42,11 +41,28 @@ const ContractVerificationFieldSources = ({ fileTypes, multiple, title, classNam
|
||||
|
||||
}, [ getValues, clearErrors, setValue ]);
|
||||
|
||||
const renderUploadButton = React.useCallback(() => {
|
||||
return (
|
||||
<div>
|
||||
<Text fontWeight={ 500 } color="text_secondary" mb={ 3 }>{ title }</Text>
|
||||
<Button size="sm" variant="outline">
|
||||
Drop file{ multiple ? 's' : '' } or click here
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}, [ multiple, title ]);
|
||||
|
||||
const renderFiles = React.useCallback((files: Array<File>) => {
|
||||
const errorList = fileError?.message?.split(';');
|
||||
|
||||
return (
|
||||
<Box display="grid" gridTemplateColumns={{ base: 'minmax(0, 1fr)', lg: 'minmax(0, 1fr) minmax(0, 1fr)' }} columnGap={ 3 } rowGap={ 3 }>
|
||||
<Box
|
||||
display="grid"
|
||||
gridTemplateColumns={{ base: 'minmax(0, 1fr)', lg: 'minmax(0, 1fr) minmax(0, 1fr)' }}
|
||||
columnGap={ 3 }
|
||||
rowGap={ 3 }
|
||||
w="100%"
|
||||
>
|
||||
{ files.map((file, index) => (
|
||||
<Box key={ file.name + file.lastModified + index }>
|
||||
<FileSnippet
|
||||
@@ -55,42 +71,36 @@ const ContractVerificationFieldSources = ({ fileTypes, multiple, title, classNam
|
||||
onRemove={ handleFileRemove }
|
||||
index={ index }
|
||||
isDisabled={ formState.isSubmitting }
|
||||
error={ errorList?.[index] }
|
||||
/>
|
||||
{ errorList?.[index] && <FieldError message={ errorList?.[index] } mt={ 1 } px={ 3 }/> }
|
||||
</Box>
|
||||
)) }
|
||||
</Box>
|
||||
);
|
||||
}, [ formState.isSubmitting, handleFileRemove, fileError ]);
|
||||
|
||||
const renderControl = React.useCallback(({ field }: {field: ControllerRenderProps<FormFields, 'sources'>}) => (
|
||||
<>
|
||||
<FileInput<FormFields, 'sources'> accept={ fileTypes.join(',') } multiple={ multiple } field={ field }>
|
||||
{ () => (
|
||||
<Flex
|
||||
flexDir="column"
|
||||
alignItems="flex-start"
|
||||
rowGap={ 2 }
|
||||
w="100%"
|
||||
display={ field.value && field.value.length > 0 && !multiple ? 'none' : 'block' }
|
||||
mb={ field.value && field.value.length > 0 ? 2 : 0 }
|
||||
>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
// mb={ 2 }
|
||||
const renderControl = React.useCallback(({ field }: {field: ControllerRenderProps<FormFields, 'sources'>}) => {
|
||||
const hasValue = field.value && field.value.length > 0;
|
||||
return (
|
||||
<>
|
||||
<FileInput<FormFields, 'sources'> accept={ fileTypes.join(',') } multiple={ multiple } field={ field }>
|
||||
{ ({ onChange }) => (
|
||||
<Flex
|
||||
flexDir="column"
|
||||
alignItems="flex-start"
|
||||
rowGap={ 2 }
|
||||
w="100%"
|
||||
>
|
||||
Upload file{ multiple ? 's' : '' }
|
||||
</Button>
|
||||
{ /* design is not ready */ }
|
||||
{ /* <DragAndDropArea onDrop={ onChange }/> */ }
|
||||
</Flex>
|
||||
) }
|
||||
</FileInput>
|
||||
{ field.value && field.value.length > 0 && renderFiles(field.value) }
|
||||
{ commonError?.message && <FieldError message={ commonError.type === 'required' ? 'Field is required' : commonError.message }/> }
|
||||
</>
|
||||
), [ fileTypes, commonError, multiple, renderFiles ]);
|
||||
<DragAndDropArea onDrop={ onChange } p={{ base: 3, lg: 6 }}>
|
||||
{ hasValue ? renderFiles(field.value) : renderUploadButton() }
|
||||
</DragAndDropArea>
|
||||
</Flex>
|
||||
) }
|
||||
</FileInput>
|
||||
{ commonError?.message && <FieldError message={ commonError.type === 'required' ? 'Field is required' : commonError.message }/> }
|
||||
</>
|
||||
);
|
||||
}, [ fileTypes, multiple, renderFiles, commonError, renderUploadButton ]);
|
||||
|
||||
const validateFileType = React.useCallback(async(value: FieldPathValue<FormFields, 'sources'>): Promise<ValidateResult> => {
|
||||
if (Array.isArray(value)) {
|
||||
@@ -114,30 +124,34 @@ const ContractVerificationFieldSources = ({ fileTypes, multiple, title, classNam
|
||||
return true;
|
||||
}, []);
|
||||
|
||||
const validateQuantity = React.useCallback(async(value: FieldPathValue<FormFields, 'sources'>): Promise<ValidateResult> => {
|
||||
if (!multiple && Array.isArray(value) && value.length > 1) {
|
||||
return 'You can upload only one file';
|
||||
}
|
||||
|
||||
return true;
|
||||
}, [ multiple ]);
|
||||
|
||||
const rules = React.useMemo(() => ({
|
||||
required: true,
|
||||
validate: {
|
||||
file_type: validateFileType,
|
||||
file_size: validateFileSize,
|
||||
quantity: validateQuantity,
|
||||
},
|
||||
}), [ validateFileSize, validateFileType ]);
|
||||
}), [ validateFileSize, validateFileType, validateQuantity ]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ContractVerificationFormRow >
|
||||
<Text fontWeight={ 500 } className={ className } mt={ 4 }>{ title }</Text>
|
||||
</ContractVerificationFormRow>
|
||||
<ContractVerificationFormRow>
|
||||
<Controller
|
||||
name="sources"
|
||||
control={ control }
|
||||
render={ renderControl }
|
||||
rules={ rules }
|
||||
/>
|
||||
{ hint ? <span>{ hint }</span> : null }
|
||||
</ContractVerificationFormRow>
|
||||
</>
|
||||
<ContractVerificationFormRow>
|
||||
<Controller
|
||||
name="sources"
|
||||
control={ control }
|
||||
render={ renderControl }
|
||||
rules={ rules }
|
||||
/>
|
||||
{ hint ? <span>{ hint }</span> : null }
|
||||
</ContractVerificationFormRow>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(chakra(ContractVerificationFieldSources));
|
||||
export default React.memo(ContractVerificationFieldSources);
|
||||
|
||||
@@ -12,7 +12,7 @@ import ContractVerificationFieldOptimization from '../fields/ContractVerificatio
|
||||
|
||||
const ContractVerificationFlattenSourceCode = () => {
|
||||
return (
|
||||
<ContractVerificationMethod title="New Solidity/Yul Smart Contract Verification">
|
||||
<ContractVerificationMethod title="Contract verification via Solidity (fattened source code)">
|
||||
<ContractVerificationFieldName/>
|
||||
<ContractVerificationFieldIsYul/>
|
||||
<ContractVerificationFieldCompiler/>
|
||||
|
||||
@@ -11,7 +11,7 @@ const FILE_TYPES = [ '.sol' as const, '.yul' as const ];
|
||||
|
||||
const ContractVerificationMultiPartFile = () => {
|
||||
return (
|
||||
<ContractVerificationMethod title="New Solidity/Yul Smart Contract Verification">
|
||||
<ContractVerificationMethod title="Contract verification via Solidity (multi-part files)">
|
||||
<ContractVerificationFieldCompiler/>
|
||||
<ContractVerificationFieldEvmVersion/>
|
||||
<ContractVerificationFieldOptimization/>
|
||||
|
||||
@@ -7,11 +7,11 @@ const FILE_TYPES = [ '.json' as const, '.sol' as const ];
|
||||
|
||||
const ContractVerificationSourcify = () => {
|
||||
return (
|
||||
<ContractVerificationMethod title="New Smart Contract Verification">
|
||||
<ContractVerificationMethod title="Contract verification via Solidity (Sourcify)">
|
||||
<ContractVerificationFieldSources
|
||||
fileTypes={ FILE_TYPES }
|
||||
multiple
|
||||
title="Sources and Metadata JSON" mt={ 0 }
|
||||
title="Sources and Metadata JSON"
|
||||
hint="Upload all Solidity contract source files and JSON metadata file(s) created during contract compilation."
|
||||
/>
|
||||
</ContractVerificationMethod>
|
||||
|
||||
@@ -10,7 +10,7 @@ const FILE_TYPES = [ '.json' as const ];
|
||||
|
||||
const ContractVerificationStandardInput = () => {
|
||||
return (
|
||||
<ContractVerificationMethod title="New Smart Contract Verification">
|
||||
<ContractVerificationMethod title="Contract verification via Solidity (standard JSON input) ">
|
||||
<ContractVerificationFieldName/>
|
||||
<ContractVerificationFieldCompiler/>
|
||||
<ContractVerificationFieldSources
|
||||
|
||||
@@ -8,7 +8,7 @@ import ContractVerificationFieldName from '../fields/ContractVerificationFieldNa
|
||||
|
||||
const ContractVerificationVyperContract = () => {
|
||||
return (
|
||||
<ContractVerificationMethod title="New Vyper Smart Contract Verification">
|
||||
<ContractVerificationMethod title="Contract verification via Vyper (contract)">
|
||||
<ContractVerificationFieldName hint="Must match the name specified in the code."/>
|
||||
<ContractVerificationFieldCompiler isVyper/>
|
||||
<ContractVerificationFieldCode isVyper/>
|
||||
|
||||
@@ -9,7 +9,7 @@ const FILE_TYPES = [ '.vy' as const ];
|
||||
|
||||
const ContractVerificationVyperMultiPartFile = () => {
|
||||
return (
|
||||
<ContractVerificationMethod title="New Vyper Smart Contract Verification">
|
||||
<ContractVerificationMethod title="Contract verification via Vyper (multi-part files)">
|
||||
<ContractVerificationFieldCompiler isVyper/>
|
||||
<ContractVerificationFieldEvmVersion isVyper/>
|
||||
<ContractVerificationFieldSources
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import { chakra, Center } from '@chakra-ui/react';
|
||||
import type { DragEvent } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import { getAllFileEntries, convertFileEntryToFile } from './utils/files';
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
onDrop: (files: Array<File>) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const DragAndDropArea = ({ onDrop }: Props) => {
|
||||
const DragAndDropArea = ({ onDrop, children, className }: Props) => {
|
||||
const [ isDragOver, setIsDragOver ] = React.useState(false);
|
||||
|
||||
const handleDrop = React.useCallback(async(event: DragEvent<HTMLDivElement>) => {
|
||||
@@ -36,17 +38,27 @@ const DragAndDropArea = ({ onDrop }: Props) => {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box
|
||||
<Center
|
||||
className={ className }
|
||||
w="100%"
|
||||
h="200px"
|
||||
bgColor="lightpink"
|
||||
opacity={ isDragOver ? 0.8 : 1 }
|
||||
minH="120px"
|
||||
borderWidth="2px"
|
||||
borderColor={ isDragOver ? 'link_hovered' : 'link' }
|
||||
_hover={{
|
||||
borderColor: 'link_hovered',
|
||||
}}
|
||||
borderRadius="base"
|
||||
borderStyle="dashed"
|
||||
cursor="pointer"
|
||||
textAlign="center"
|
||||
onDrop={ handleDrop }
|
||||
onDragOver={ handleDragOver }
|
||||
onDragEnter={ handleDragEnter }
|
||||
onDragLeave={ handleDragLeave }
|
||||
/>
|
||||
>
|
||||
{ children }
|
||||
</Center>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(DragAndDropArea);
|
||||
export default React.memo(chakra(DragAndDropArea));
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { Box, Flex, Icon, Text, useColorModeValue, IconButton, chakra } from '@chakra-ui/react';
|
||||
import { Box, Flex, Icon, Text, useColorModeValue, IconButton, chakra, Tooltip } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import CrossIcon from 'icons/cross.svg';
|
||||
import imageIcon from 'icons/files/image.svg';
|
||||
import jsonIcon from 'icons/files/json.svg';
|
||||
import placeholderIcon from 'icons/files/placeholder.svg';
|
||||
import solIcon from 'icons/files/sol.svg';
|
||||
import yulIcon from 'icons/files/yul.svg';
|
||||
import infoIcon from 'icons/info.svg';
|
||||
import { shortenNumberWithLetter } from 'lib/formatters';
|
||||
|
||||
const FILE_ICONS: Record<string, React.FunctionComponent<React.SVGAttributes<SVGElement>>> = {
|
||||
@@ -29,42 +30,79 @@ interface Props {
|
||||
index?: number;
|
||||
onRemove?: (index?: number) => void;
|
||||
isDisabled?: boolean;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
const FileSnippet = ({ file, className, index, onRemove, isDisabled }: Props) => {
|
||||
const handleRemove = React.useCallback(() => {
|
||||
const FileSnippet = ({ file, className, index, onRemove, isDisabled, error }: Props) => {
|
||||
const handleRemove = React.useCallback((event: React.MouseEvent) => {
|
||||
event.stopPropagation();
|
||||
onRemove?.(index);
|
||||
}, [ index, onRemove ]);
|
||||
|
||||
const handleErrorHintIconClick = React.useCallback((event: React.MouseEvent) => {
|
||||
event.stopPropagation();
|
||||
|
||||
}, []);
|
||||
|
||||
const fileExtension = getFileExtension(file.name);
|
||||
const fileIcon = FILE_ICONS[fileExtension] || imageIcon;
|
||||
const fileIcon = FILE_ICONS[fileExtension] || placeholderIcon;
|
||||
const iconColor = useColorModeValue('gray.600', 'gray.400');
|
||||
|
||||
return (
|
||||
<Flex
|
||||
p={ 3 }
|
||||
borderWidth="2px"
|
||||
borderRadius="md"
|
||||
borderColor={ useColorModeValue('blackAlpha.100', 'whiteAlpha.200') }
|
||||
maxW="300px"
|
||||
overflow="hidden"
|
||||
className={ className }
|
||||
alignItems="center"
|
||||
textAlign="left"
|
||||
>
|
||||
<Icon as={ fileIcon } boxSize="50px" color={ useColorModeValue('gray.600', 'gray.400') } mr={ 2 }/>
|
||||
<Box width="calc(100% - 58px - 24px)" >
|
||||
<Text fontWeight={ 600 } overflow="hidden" textOverflow="ellipsis" whiteSpace="nowrap">{ file.name }</Text>
|
||||
<Icon
|
||||
as={ fileIcon }
|
||||
boxSize="74px"
|
||||
color={ error ? 'error' : iconColor }
|
||||
mr={ 2 }
|
||||
borderWidth="2px"
|
||||
borderRadius="md"
|
||||
borderColor={ useColorModeValue('blackAlpha.100', 'whiteAlpha.200') }
|
||||
p={ 3 }
|
||||
/>
|
||||
<Box maxW="calc(100% - 58px - 24px)">
|
||||
<Flex alignItems="center">
|
||||
<Text
|
||||
fontWeight={ 600 }
|
||||
overflow="hidden"
|
||||
textOverflow="ellipsis"
|
||||
whiteSpace="nowrap"
|
||||
color={ error ? 'error' : 'initial' }
|
||||
>
|
||||
{ file.name }
|
||||
</Text>
|
||||
{ Boolean(error) && (
|
||||
<Tooltip
|
||||
label={ error }
|
||||
placement="top"
|
||||
maxW="320px"
|
||||
>
|
||||
<Box cursor="pointer" display="inherit" onClick={ handleErrorHintIconClick } ml={ 1 }>
|
||||
<Icon as={ infoIcon } boxSize={ 5 } color="error"/>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
) }
|
||||
<IconButton
|
||||
aria-label="remove"
|
||||
icon={ <CrossIcon/> }
|
||||
boxSize={ 6 }
|
||||
variant="simple"
|
||||
display="inline-block"
|
||||
flexShrink={ 0 }
|
||||
ml="auto"
|
||||
onClick={ handleRemove }
|
||||
isDisabled={ isDisabled }
|
||||
alignSelf="flex-start"
|
||||
/>
|
||||
</Flex>
|
||||
<Text variant="secondary" mt={ 1 }>{ shortenNumberWithLetter(file.size) }B</Text>
|
||||
</Box>
|
||||
<IconButton
|
||||
aria-label="remove"
|
||||
icon={ <CrossIcon/> }
|
||||
boxSize={ 6 }
|
||||
variant="simple"
|
||||
display="inline-block"
|
||||
flexShrink={ 0 }
|
||||
ml="auto"
|
||||
onClick={ handleRemove }
|
||||
isDisabled={ isDisabled }
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user