Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
F
frontend
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
vicotor
frontend
Commits
27872bfe
Commit
27872bfe
authored
Feb 13, 2025
by
tom
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
contract details tab
parent
4a55cf1a
Changes
35
Show whitespace changes
Inline
Side-by-side
Showing
35 changed files
with
517 additions
and
388 deletions
+517
-388
accordion.tsx
toolkit/chakra/accordion.tsx
+4
-3
alert.tsx
toolkit/chakra/alert.tsx
+4
-1
tabs.tsx
toolkit/chakra/tabs.tsx
+3
-1
AdaptiveTabsMenu.tsx
toolkit/components/AdaptiveTabs/AdaptiveTabsMenu.tsx
+2
-0
semanticTokens.ts
toolkit/theme/foundations/semanticTokens.ts
+9
-0
accordion.recipe.ts
toolkit/theme/recipes/accordion.recipe.ts
+15
-0
tabs.recipe.ts
toolkit/theme/recipes/tabs.recipe.ts
+33
-0
AddressContract.tsx
ui/address/AddressContract.tsx
+4
-4
ContractCodeIdes.tsx
ui/address/contract/ContractCodeIdes.tsx
+18
-27
ContractDetails.tsx
ui/address/contract/ContractDetails.tsx
+3
-3
ContractSourceCode.tsx
ui/address/contract/ContractSourceCode.tsx
+13
-12
ContractDetailsAlertProxyPattern.tsx
...ress/contract/alerts/ContractDetailsAlertProxyPattern.tsx
+4
-4
ContractDetailsAlertVerificationSource.tsx
...ontract/alerts/ContractDetailsAlertVerificationSource.tsx
+9
-8
ContractDetailsAlerts.tsx
ui/address/contract/alerts/ContractDetailsAlerts.tsx
+11
-11
ContractSecurityAudits.tsx
ui/address/contract/audits/ContractSecurityAudits.tsx
+8
-6
ContractSubmitAuditForm.tsx
ui/address/contract/audits/ContractSubmitAuditForm.tsx
+16
-25
ContractDetailsInfo.tsx
ui/address/contract/info/ContractDetailsInfo.tsx
+41
-29
ContractDetailsInfoItem.tsx
ui/address/contract/info/ContractDetailsInfoItem.tsx
+8
-9
useContractDetailsTabs.tsx
ui/address/contract/useContractDetailsTabs.tsx
+2
-1
useContractTabs.tsx
ui/address/contract/useContractTabs.tsx
+6
-6
Address.tsx
ui/pages/Address.tsx
+23
-23
CodeViewSnippet.tsx
ui/shared/CodeViewSnippet.tsx
+3
-3
CoinzillaTextAd.tsx
ui/shared/ad/CoinzillaTextAd.tsx
+1
-1
FormFieldText.tsx
ui/shared/forms/fields/FormFieldText.tsx
+4
-3
types.ts
ui/shared/forms/fields/types.ts
+3
-2
CodeEditor.tsx
ui/shared/monaco/CodeEditor.tsx
+21
-20
CodeEditorFileTree.tsx
ui/shared/monaco/CodeEditorFileTree.tsx
+52
-39
CodeEditorMainFileIndicator.tsx
ui/shared/monaco/CodeEditorMainFileIndicator.tsx
+3
-2
CodeEditorSearch.tsx
ui/shared/monaco/CodeEditorSearch.tsx
+73
-50
CodeEditorSearchSection.tsx
ui/shared/monaco/CodeEditorSearchSection.tsx
+56
-53
CodeEditorSideBar.tsx
ui/shared/monaco/CodeEditorSideBar.tsx
+47
-38
CodeEditorTab.tsx
ui/shared/monaco/CodeEditorTab.tsx
+1
-1
themes.ts
ui/shared/monaco/utils/themes.ts
+4
-2
useThemeColors.ts
ui/shared/monaco/utils/useThemeColors.ts
+1
-1
Tabs.tsx
ui/showcases/Tabs.tsx
+12
-0
No files found.
toolkit/chakra/accordion.tsx
View file @
27872bfe
...
...
@@ -5,6 +5,7 @@ import IconSvg from 'ui/shared/IconSvg';
interface
AccordionItemTriggerProps
extends
Accordion
.
ItemTriggerProps
{
indicatorPlacement
?:
'
start
'
|
'
end
'
;
noIndicator
?:
boolean
;
variant
?:
Accordion
.
RootProps
[
'
variant
'
];
}
...
...
@@ -12,7 +13,7 @@ export const AccordionItemTrigger = React.forwardRef<
HTMLButtonElement
,
AccordionItemTriggerProps
>
(
function
AccordionItemTrigger
(
props
,
ref
)
{
const
{
children
,
indicatorPlacement
:
indicatorPlacementProp
,
variant
,
...
rest
}
=
props
;
const
{
children
,
indicatorPlacement
:
indicatorPlacementProp
,
variant
,
noIndicator
,
...
rest
}
=
props
;
const
indicatorPlacement
=
variant
===
'
faq
'
?
'
start
'
:
(
indicatorPlacementProp
??
'
end
'
);
...
...
@@ -59,9 +60,9 @@ export const AccordionItemTrigger = React.forwardRef<
return
(
<
Accordion
.
ItemTrigger
className=
"group"
{
...
rest
}
ref=
{
ref
}
>
{
indicatorPlacement
===
'
start
'
&&
indicator
}
{
indicatorPlacement
===
'
start
'
&&
!
noIndicator
&&
indicator
}
{
children
}
{
indicatorPlacement
===
'
end
'
&&
indicator
}
{
indicatorPlacement
===
'
end
'
&&
!
noIndicator
&&
indicator
}
</
Accordion
.
ItemTrigger
>
);
});
...
...
toolkit/chakra/alert.tsx
View file @
27872bfe
import
type
{
AlertDescriptionProps
}
from
'
@chakra-ui/react
'
;
import
{
Alert
as
ChakraAlert
}
from
'
@chakra-ui/react
'
;
import
*
as
React
from
'
react
'
;
...
...
@@ -9,6 +10,7 @@ import { Skeleton } from './skeleton';
export
interface
AlertProps
extends
Omit
<
ChakraAlert
.
RootProps
,
'
title
'
>
{
startElement
?:
React
.
ReactNode
;
endElement
?:
React
.
ReactNode
;
descriptionProps
?:
AlertDescriptionProps
;
title
?:
React
.
ReactNode
;
icon
?:
React
.
ReactElement
;
closable
?:
boolean
;
...
...
@@ -29,6 +31,7 @@ export const Alert = React.forwardRef<HTMLDivElement, AlertProps>(
endElement
,
loading
,
showIcon
=
false
,
descriptionProps
,
...
rest
}
=
props
;
...
...
@@ -53,7 +56,7 @@ export const Alert = React.forwardRef<HTMLDivElement, AlertProps>(
{
children
?
(
<
ChakraAlert
.
Content
>
{
title
&&
<
ChakraAlert
.
Title
>
{
title
}
</
ChakraAlert
.
Title
>
}
<
ChakraAlert
.
Description
display=
"inline-flex"
>
{
children
}
</
ChakraAlert
.
Description
>
<
ChakraAlert
.
Description
display=
"inline-flex"
flexWrap=
"wrap"
{
...
descriptionProps
}
>
{
children
}
</
ChakraAlert
.
Description
>
</
ChakraAlert
.
Content
>
)
:
(
<
ChakraAlert
.
Title
flex=
"1"
>
{
title
}
</
ChakraAlert
.
Title
>
...
...
toolkit/chakra/tabs.tsx
View file @
27872bfe
...
...
@@ -12,7 +12,9 @@ export const TabsRoot = React.forwardRef<HTMLDivElement, TabsProps>(
export
const
TabsList
=
ChakraTabs
.
List
;
export
const
TabsTrigger
=
React
.
forwardRef
<
HTMLButtonElement
,
ChakraTabs
.
TriggerProps
>
(
export
interface
TabsTriggerProps
extends
ChakraTabs
.
TriggerProps
{}
export
const
TabsTrigger
=
React
.
forwardRef
<
HTMLButtonElement
,
TabsTriggerProps
>
(
function
TabsTrigger
(
props
,
ref
)
{
return
<
ChakraTabs
.
Trigger
ref=
{
ref
}
className=
"group"
{
...
props
}
/>;
},
...
...
toolkit/components/AdaptiveTabs/AdaptiveTabsMenu.tsx
View file @
27872bfe
...
...
@@ -21,6 +21,8 @@ const AdaptiveTabsMenu = ({ tabs, tabsCut, isActive, ...props }: Props, ref: Rea
<
PopoverRoot
positioning=
{
{
placement
:
'
bottom-end
'
}
}
>
<
PopoverTrigger
>
<
Button
// we use "div" so the :last-of-type pseudo-class targets the last tab and not the menu trigger
as=
"div"
variant=
"plain"
color=
"tabs.solid.fg"
_hover=
{
{
...
...
toolkit/theme/foundations/semanticTokens.ts
View file @
27872bfe
...
...
@@ -175,6 +175,15 @@ const semanticTokens: ThemingConfig['semanticTokens'] = {
DEFAULT
:
{
value
:
{
_light
:
'
{colors.gray.200}
'
,
_dark
:
'
{colors.gray.600}
'
}
},
},
},
segmented
:
{
fg
:
{
DEFAULT
:
{
value
:
{
_light
:
'
{colors.blue.600}
'
,
_dark
:
'
{colors.blue.300}
'
}
},
selected
:
{
value
:
{
_light
:
'
{colors.blue.700}
'
,
_dark
:
'
{colors.gray.50}
'
}
},
},
border
:
{
DEFAULT
:
{
value
:
{
_light
:
'
{colors.blue.50}
'
,
_dark
:
'
{colors.gray.800}
'
}
},
},
},
},
'
switch
'
:
{
primary
:
{
...
...
toolkit/theme/recipes/accordion.recipe.ts
View file @
27872bfe
...
...
@@ -49,6 +49,21 @@ export const recipe = defineSlotRecipe({
},
variants
:
{
noAnimation
:
{
'
true
'
:
{
itemContent
:
{
_open
:
{
animationName
:
'
none
'
,
},
_closed
:
{
animationName
:
'
none
'
,
},
},
itemIndicator
:
{
transition
:
'
none
'
,
},
},
},
variant
:
{
outline
:
{
item
:
{
...
...
toolkit/theme/recipes/tabs.recipe.ts
View file @
27872bfe
...
...
@@ -124,6 +124,7 @@ export const recipe = defineSlotRecipe({
textStyle
:
'
md
'
,
},
},
free
:
{},
},
variant
:
{
...
...
@@ -178,6 +179,38 @@ export const recipe = defineSlotRecipe({
},
},
},
segmented
:
{
trigger
:
{
color
:
'
tabs.segmented.fg
'
,
bg
:
'
transparent
'
,
borderWidth
:
'
2px
'
,
borderStyle
:
'
solid
'
,
borderColor
:
'
tabs.segmented.border
'
,
_hover
:
{
color
:
'
link.primary.hover
'
,
},
_selected
:
{
color
:
'
tabs.segmented.fg.selected
'
,
bg
:
'
tabs.segmented.border
'
,
borderColor
:
'
tabs.segmented.border
'
,
_hover
:
{
color
:
'
tabs.segmented.fg.selected
'
,
},
},
_notFirst
:
{
borderLeftWidth
:
'
0
'
,
},
_first
:
{
borderTopLeftRadius
:
'
base
'
,
borderBottomLeftRadius
:
'
base
'
,
},
_last
:
{
borderTopRightRadius
:
'
base
'
,
borderBottomRightRadius
:
'
base
'
,
},
},
},
unstyled
:
{},
},
},
...
...
ui/address/AddressContract.tsx
View file @
27872bfe
import
React
from
'
react
'
;
import
type
{
RoutedSubTab
}
from
'
ui/shared/
Tabs/types
'
;
import
type
{
TabItemRegular
}
from
'
toolkit/components/Adaptive
Tabs/types
'
;
import
RoutedTabs
from
'
ui/shared/
Tabs/RoutedTabs
'
;
import
RoutedTabs
from
'
toolkit/components/Routed
Tabs/RoutedTabs
'
;
interface
Props
{
tabs
:
Array
<
RoutedSubTab
>
;
tabs
:
Array
<
TabItemRegular
>
;
isLoading
:
boolean
;
shouldRender
?:
boolean
;
}
...
...
@@ -20,7 +20,7 @@ const AddressContract = ({ tabs, isLoading, shouldRender }: Props) => {
}
return
(
<
RoutedTabs
tabs=
{
tabs
}
variant=
"outline"
colorScheme=
"gray"
size=
"sm"
tabL
istProps=
{
TAB_LIST_PROPS
}
isLoading=
{
isLoading
}
/>
<
RoutedTabs
tabs=
{
tabs
}
variant=
"outline"
colorScheme=
"gray"
size=
"sm"
l
istProps=
{
TAB_LIST_PROPS
}
isLoading=
{
isLoading
}
/>
);
};
...
...
ui/address/contract/ContractCodeIdes.tsx
View file @
27872bfe
import
{
Flex
,
Button
,
chakra
,
PopoverTrigger
,
PopoverBody
,
PopoverContent
,
Image
,
useDisclosure
,
useColorModeValue
,
}
from
'
@chakra-ui/react
'
;
import
{
Flex
,
chakra
}
from
'
@chakra-ui/react
'
;
import
React
from
'
react
'
;
import
config
from
'
configs/app
'
;
import
Popover
from
'
ui/shared/chakra/Popover
'
;
import
Skeleton
from
'
ui/shared/chakra/Skeleton
'
;
import
{
Button
}
from
'
toolkit/chakra/button
'
;
import
{
useColorModeValue
}
from
'
toolkit/chakra/color-mode
'
;
import
{
Image
}
from
'
toolkit/chakra/image
'
;
import
{
Link
}
from
'
toolkit/chakra/link
'
;
import
{
PopoverRoot
,
PopoverTrigger
,
PopoverContent
,
PopoverBody
}
from
'
toolkit/chakra/popover
'
;
import
{
Skeleton
}
from
'
toolkit/chakra/skeleton
'
;
import
{
useDisclosure
}
from
'
toolkit/hooks/useDisclosure
'
;
import
IconSvg
from
'
ui/shared/IconSvg
'
;
import
LinkExternal
from
'
ui/shared/links/LinkExternal
'
;
interface
Props
{
className
?:
string
;
hash
:
string
;
isLoading
?:
string
;
isLoading
?:
boolean
;
}
const
ContractCodeIde
=
({
className
,
hash
,
isLoading
}:
Props
)
=>
{
const
{
isOpen
,
onToggle
,
onClos
e
}
=
useDisclosure
();
const
{
open
,
onOpenChang
e
}
=
useDisclosure
();
const
defaultIconColor
=
useColorModeValue
(
'
gray.600
'
,
'
gray.500
'
);
const
ideLinks
=
React
.
useMemo
(()
=>
{
...
...
@@ -36,16 +30,16 @@ const ContractCodeIde = ({ className, hash, isLoading }: Props) => {
<
IconSvg
name=
"ABI_slim"
boxSize=
{
5
}
color=
{
defaultIconColor
}
mr=
{
2
}
/>;
return
(
<
Link
E
xternal
key=
{
ide
.
title
}
href=
{
url
}
display=
"inline-flex"
alignItems=
"center"
>
<
Link
e
xternal
key=
{
ide
.
title
}
href=
{
url
}
display=
"inline-flex"
alignItems=
"center"
>
{
icon
}
{
ide
.
title
}
</
Link
External
>
</
Link
>
);
});
},
[
defaultIconColor
,
hash
]);
if
(
isLoading
)
{
return
<
Skeleton
h=
{
8
}
w=
"92px"
borderRadius=
"base"
/>;
return
<
Skeleton
loading
h=
{
8
}
w=
"92px"
borderRadius=
"base"
/>;
}
if
(
ideLinks
.
length
===
0
)
{
...
...
@@ -53,23 +47,20 @@ const ContractCodeIde = ({ className, hash, isLoading }: Props) => {
}
return
(
<
Popover
isOpen=
{
isOpen
}
onClose=
{
onClose
}
placement=
"bottom-start"
isLazy
>
<
Popover
Root
open=
{
open
}
onOpenChange=
{
onOpenChange
}
>
<
PopoverTrigger
>
<
Button
className=
{
className
}
size=
"sm"
variant=
"outline"
colorScheme=
"gray"
onClick=
{
onToggle
}
isActive=
{
isOpen
}
variant=
"dropdown"
aria
-
label=
"Open source code in IDE"
fontWeight=
{
500
}
px=
{
2
}
gap=
{
0
}
h=
"32px"
flexShrink=
{
0
}
>
<
span
>
Open in
</
span
>
<
IconSvg
name=
"arrows/east-mini"
transform=
{
isO
pen
?
'
rotate(90deg)
'
:
'
rotate(-90deg)
'
}
transitionDuration=
"faster"
boxSize=
{
5
}
/>
<
IconSvg
name=
"arrows/east-mini"
transform=
{
o
pen
?
'
rotate(90deg)
'
:
'
rotate(-90deg)
'
}
transitionDuration=
"faster"
boxSize=
{
5
}
/>
</
Button
>
</
PopoverTrigger
>
<
PopoverContent
w=
"240px"
>
...
...
@@ -86,7 +77,7 @@ const ContractCodeIde = ({ className, hash, isLoading }: Props) => {
</
Flex
>
</
PopoverBody
>
</
PopoverContent
>
</
Popover
>
</
Popover
Root
>
);
};
...
...
ui/address/contract/ContractDetails.tsx
View file @
27872bfe
...
...
@@ -15,8 +15,8 @@ import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery';
import
getQueryParamString
from
'
lib/router/getQueryParamString
'
;
import
useSocketMessage
from
'
lib/socket/useSocketMessage
'
;
import
*
as
stubs
from
'
stubs/contract
'
;
import
RoutedTabs
from
'
toolkit/components/RoutedTabs/RoutedTabs
'
;
import
DataFetchAlert
from
'
ui/shared/DataFetchAlert
'
;
import
RoutedTabs
from
'
ui/shared/Tabs/RoutedTabs
'
;
import
ContractDetailsAlerts
from
'
./alerts/ContractDetailsAlerts
'
;
import
ContractSourceAddressSelector
from
'
./ContractSourceAddressSelector
'
;
...
...
@@ -114,10 +114,10 @@ const ContractDetails = ({ addressHash, channel, mainContractQuery }: Props) =>
<
RoutedTabs
tabs=
{
tabs
}
isLoading=
{
isPlaceholderData
}
variant=
"
radio_group
"
variant=
"
segmented
"
size=
"sm"
leftSlot=
{
addressSelector
}
tabL
istProps=
{
TAB_LIST_PROPS
}
l
istProps=
{
TAB_LIST_PROPS
}
leftSlotProps=
{
LEFT_SLOT_PROPS
}
/>
)
:
(
...
...
ui/address/contract/ContractSourceCode.tsx
View file @
27872bfe
import
{
Flex
,
Text
,
Tooltip
}
from
'
@chakra-ui/react
'
;
import
{
Flex
,
Text
}
from
'
@chakra-ui/react
'
;
import
React
from
'
react
'
;
import
type
{
SmartContract
}
from
'
types/api/contract
'
;
...
...
@@ -6,9 +6,10 @@ import type { SmartContract } from 'types/api/contract';
import
{
route
}
from
'
nextjs-routes
'
;
import
formatLanguageName
from
'
lib/contracts/formatLanguageName
'
;
import
Skeleton
from
'
ui/shared/chakra/Skeleton
'
;
import
{
Link
}
from
'
toolkit/chakra/link
'
;
import
{
Skeleton
}
from
'
toolkit/chakra/skeleton
'
;
import
{
Tooltip
}
from
'
toolkit/chakra/tooltip
'
;
import
CopyToClipboard
from
'
ui/shared/CopyToClipboard
'
;
import
LinkInternal
from
'
ui/shared/links/LinkInternal
'
;
import
CodeEditor
from
'
ui/shared/monaco/CodeEditor
'
;
import
formatFilePath
from
'
ui/shared/monaco/utils/formatFilePath
'
;
...
...
@@ -54,10 +55,10 @@ export const ContractSourceCode = ({ data, isLoading, sourceAddress }: Props) =>
},
[
data
]);
const
heading
=
(
<
Skeleton
isLoaded=
{
!
isLoading
}
fontWeight=
{
500
}
>
<
Skeleton
loading=
{
isLoading
}
fontWeight=
{
500
}
>
<
span
>
Contract source code
</
span
>
{
data
?.
language
&&
<
Text
whiteSpace=
"pre"
as=
"span"
variant=
"
secondary"
>
(
{
formatLanguageName
(
data
.
language
)
}
)
</
Text
>
}
<
Text
whiteSpace=
"pre"
as=
"span"
color=
"text.
secondary"
>
(
{
formatLanguageName
(
data
.
language
)
}
)
</
Text
>
}
</
Skeleton
>
);
...
...
@@ -66,16 +67,16 @@ export const ContractSourceCode = ({ data, isLoading, sourceAddress }: Props) =>
null
;
const
diagramLink
=
data
?.
can_be_visualized_via_sol2uml
?
(
<
Tooltip
label
=
"Visualize contract code using Sol2Uml JS library"
>
<
Link
Internal
<
Tooltip
content
=
"Visualize contract code using Sol2Uml JS library"
>
<
Link
href=
{
route
({
pathname
:
'
/visualize/sol2uml
'
,
query
:
{
address
:
sourceAddress
}
})
}
ml=
{
{
base
:
'
0
'
,
lg
:
'
auto
'
}
}
isL
oading=
{
isLoading
}
l
oading=
{
isLoading
}
>
<
Skeleton
isLoaded=
{
!
isLoading
}
>
<
Skeleton
loading=
{
isLoading
}
>
View UML diagram
</
Skeleton
>
</
Link
Internal
>
</
Link
>
</
Tooltip
>
)
:
null
;
...
...
@@ -83,7 +84,7 @@ export const ContractSourceCode = ({ data, isLoading, sourceAddress }: Props) =>
<
ContractCodeIdes
hash=
{
sourceAddress
}
isLoading=
{
isLoading
}
/>
:
null
;
const
copyToClipboard
=
data
&&
editorData
?.
length
===
1
?
(
const
copyToClipboard
=
data
&&
editorData
?.
length
===
1
&&
data
.
source_code
?
(
<
CopyToClipboard
text=
{
data
.
source_code
}
isLoading=
{
isLoading
}
...
...
@@ -94,7 +95,7 @@ export const ContractSourceCode = ({ data, isLoading, sourceAddress }: Props) =>
const
content
=
(()
=>
{
if
(
isLoading
)
{
return
<
Skeleton
h=
"557px"
w=
"100%"
/>;
return
<
Skeleton
loading
h=
"557px"
w=
"100%"
/>;
}
if
(
!
editorData
)
{
...
...
ui/address/contract/alerts/ContractDetailsAlertProxyPattern.tsx
View file @
27872bfe
import
{
Alert
}
from
'
@chakra-ui/react
'
;
import
React
from
'
react
'
;
import
type
{
SmartContractProxyType
}
from
'
types/api/contract
'
;
import
LinkExternal
from
'
ui/shared/links/LinkExternal
'
;
import
{
Alert
}
from
'
toolkit/chakra/alert
'
;
import
{
Link
}
from
'
toolkit/chakra/link
'
;
interface
Props
{
type
:
NonNullable
<
SmartContractProxyType
>
;
...
...
@@ -70,10 +70,10 @@ const ContractCodeProxyPattern = ({ type }: Props) => {
}
return
(
<
Alert
status=
"warning"
flexWrap=
"wrap"
whiteSpace=
"pre-wrap"
>
<
Alert
status=
"warning"
whiteSpace=
"pre-wrap"
>
{
proxyInfo
.
link
?
(
<>
This proxy smart-contract is detected via
<
Link
External
href=
{
proxyInfo
.
link
}
>
{
proxyInfo
.
name
}
</
LinkExternal
>
This proxy smart-contract is detected via
<
Link
href=
{
proxyInfo
.
link
}
external
>
{
proxyInfo
.
name
}
</
Link
>
{
proxyInfo
.
description
&&
` - ${ proxyInfo.description }`
}
</>
)
:
(
...
...
ui/address/contract/alerts/ContractDetailsAlertVerificationSource.tsx
View file @
27872bfe
import
{
Alert
}
from
'
@chakra-ui/react
'
;
import
React
from
'
react
'
;
import
type
{
SmartContract
}
from
'
types/api/contract
'
;
import
LinkExternal
from
'
ui/shared/links/LinkExternal
'
;
import
{
Alert
}
from
'
toolkit/chakra/alert
'
;
import
{
Link
}
from
'
toolkit/chakra/link
'
;
interface
Props
{
data
:
SmartContract
|
undefined
;
...
...
@@ -12,23 +12,24 @@ interface Props {
const
ContractDetailsAlertVerificationSource
=
({
data
}:
Props
)
=>
{
if
(
data
?.
is_verified_via_eth_bytecode_db
)
{
return
(
<
Alert
status=
"warning"
whiteSpace=
"pre-wrap"
flexWrap=
"wrap"
>
<
Alert
status=
"warning"
whiteSpace=
"pre-wrap"
>
<
span
>
This contract has been
{
data
.
is_partially_verified
?
'
partially
'
:
''
}
verified using
</
span
>
<
Link
External
<
Link
href=
"https://docs.blockscout.com/about/features/ethereum-bytecode-database-microservice"
fontSize=
"md"
textStyle=
"md"
external
>
Blockscout Bytecode Database
</
Link
External
>
</
Link
>
</
Alert
>
);
}
if
(
data
?.
is_verified_via_sourcify
)
{
return
(
<
Alert
status=
"warning"
whiteSpace=
"pre-wrap"
flexWrap=
"wrap"
>
<
Alert
status=
"warning"
whiteSpace=
"pre-wrap"
>
<
span
>
This contract has been
{
data
.
is_partially_verified
?
'
partially
'
:
''
}
verified via Sourcify.
</
span
>
{
data
.
sourcify_repo_url
&&
<
Link
External
href=
{
data
.
sourcify_repo_url
}
fontSize=
"md"
>
View contract in Sourcify repository
</
LinkExternal
>
}
{
data
.
sourcify_repo_url
&&
<
Link
href=
{
data
.
sourcify_repo_url
}
textStyle=
"md"
external
>
View contract in Sourcify repository
</
Link
>
}
</
Alert
>
);
}
...
...
ui/address/contract/alerts/ContractDetailsAlerts.tsx
View file @
27872bfe
import
{
chakra
,
Alert
,
Box
,
Flex
}
from
'
@chakra-ui/react
'
;
import
{
chakra
,
Box
,
Flex
}
from
'
@chakra-ui/react
'
;
import
type
{
Channel
}
from
'
phoenix
'
;
import
React
from
'
react
'
;
...
...
@@ -8,10 +8,10 @@ import type { SmartContract } from 'types/api/contract';
import
{
route
}
from
'
nextjs-routes
'
;
import
useSocketMessage
from
'
lib/socket/useSocketMessage
'
;
import
Skeleton
from
'
ui/shared/chakra/Skeleton
'
;
import
{
Alert
}
from
'
toolkit/chakra/alert
'
;
import
{
Link
}
from
'
toolkit/chakra/link
'
;
import
{
Skeleton
}
from
'
toolkit/chakra/skeleton
'
;
import
AddressEntity
from
'
ui/shared/entities/address/AddressEntity
'
;
import
LinkExternal
from
'
ui/shared/links/LinkExternal
'
;
import
LinkInternal
from
'
ui/shared/links/LinkInternal
'
;
import
ContractDetailsVerificationButton
from
'
../ContractDetailsVerificationButton
'
;
import
ContractDetailsAlertProxyPattern
from
'
./ContractDetailsAlertProxyPattern
'
;
...
...
@@ -42,14 +42,14 @@ const ContractDetailsAlerts = ({ data, isLoading, addressHash, channel }: Props)
{
data
?.
is_blueprint
&&
(
<
Box
>
<
span
>
This is an
</
span
>
<
Link
E
xternal
href=
"https://eips.ethereum.org/EIPS/eip-5202"
>
<
Link
e
xternal
href=
"https://eips.ethereum.org/EIPS/eip-5202"
>
ERC-5202 Blueprint contract
</
Link
External
>
</
Link
>
</
Box
>
)
}
{
data
?.
is_verified
&&
(
<
Skeleton
isLoaded=
{
!
isLoading
}
>
<
Alert
status=
"success"
flexWrap=
"wrap"
rowGap=
{
3
}
columnGap=
{
5
}
>
<
Skeleton
loading=
{
isLoading
}
>
<
Alert
status=
"success"
descriptionProps=
{
{
alignItems
:
'
center
'
,
flexWrap
:
'
wrap
'
,
rowGap
:
3
,
columnGap
:
5
}
}
>
<
span
>
Contract Source Code Verified (
{
data
.
is_partially_verified
?
'
Partial
'
:
'
Exact
'
}
Match)
</
span
>
{
data
.
is_partially_verified
?
(
...
...
@@ -70,7 +70,7 @@ const ContractDetailsAlerts = ({ data, isLoading, addressHash, channel }: Props)
</
Alert
>
)
}
{
!
data
?.
is_verified
&&
data
?.
verified_twin_address_hash
&&
(
!
data
?.
proxy_type
||
data
.
proxy_type
===
'
unknown
'
)
&&
(
<
Alert
status=
"warning"
whiteSpace=
"pre-wrap"
flexWrap=
"wrap"
>
<
Alert
status=
"warning"
whiteSpace=
"pre-wrap"
>
<
span
>
Contract is not verified. However, we found a verified contract with the same bytecode in Blockscout DB
</
span
>
<
AddressEntity
address=
{
{
hash
:
data
.
verified_twin_address_hash
,
filecoin
:
{
robust
:
data
.
verified_twin_filecoin_robust_address
},
is_contract
:
true
}
}
...
...
@@ -79,9 +79,9 @@ const ContractDetailsAlerts = ({ data, isLoading, addressHash, channel }: Props)
fontWeight=
"500"
/>
<
chakra
.
span
mt=
{
1
}
>
All functions displayed below are from ABI of that contract. In order to verify current contract, proceed with
</
chakra
.
span
>
<
Link
Internal
href=
{
route
({
pathname
:
'
/address/[hash]/contract-verification
'
,
query
:
{
hash
:
addressHash
}
})
}
>
<
Link
href=
{
route
({
pathname
:
'
/address/[hash]/contract-verification
'
,
query
:
{
hash
:
addressHash
}
})
}
>
Verify
&
Publish
</
Link
Internal
>
</
Link
>
<
span
>
page
</
span
>
</
Alert
>
)
}
...
...
ui/address/contract/audits/ContractSecurityAudits.tsx
View file @
27872bfe
import
{
Box
,
Button
,
useDisclosure
}
from
'
@chakra-ui/react
'
;
import
{
Box
}
from
'
@chakra-ui/react
'
;
import
React
from
'
react
'
;
import
type
{
SmartContractSecurityAuditSubmission
}
from
'
types/api/contract
'
;
import
useApiQuery
from
'
lib/api/useApiQuery
'
;
import
dayjs
from
'
lib/date/dayjs
'
;
import
{
Button
}
from
'
toolkit/chakra/button
'
;
import
{
Link
}
from
'
toolkit/chakra/link
'
;
import
{
useDisclosure
}
from
'
toolkit/hooks/useDisclosure
'
;
import
ContainerWithScrollY
from
'
ui/shared/ContainerWithScrollY
'
;
import
FormModal
from
'
ui/shared/FormModal
'
;
import
LinkExternal
from
'
ui/shared/links/LinkExternal
'
;
import
ContractSubmitAuditForm
from
'
./ContractSubmitAuditForm
'
;
...
...
@@ -46,16 +48,16 @@ const ContractSecurityAudits = ({ addressHash }: Props) => {
mt=
{
2
}
>
{
data
.
items
.
map
(
item
=>
(
<
Link
External
href=
{
item
.
audit_report_url
}
key=
{
item
.
audit_company_name
+
item
.
audit_publish_date
}
isL
oading=
{
isPlaceholderData
}
>
<
Link
external
href=
{
item
.
audit_report_url
}
key=
{
item
.
audit_company_name
+
item
.
audit_publish_date
}
l
oading=
{
isPlaceholderData
}
>
{
`${ item.audit_company_name }, ${ dayjs(item.audit_publish_date).format('MMM DD, YYYY') }`
}
</
Link
External
>
</
Link
>
))
}
</
ContainerWithScrollY
>
</
Box
>
)
}
<
FormModal
<
SmartContractSecurityAuditSubmission
>
isOpen=
{
modalProps
.
isO
pen
}
on
Close=
{
modalProps
.
onClos
e
}
open=
{
modalProps
.
o
pen
}
on
OpenChange=
{
modalProps
.
onOpenChang
e
}
title=
{
formTitle
}
renderForm=
{
renderForm
}
/
>
...
...
ui/address/contract/audits/ContractSubmitAuditForm.tsx
View file @
27872bfe
import
{
Button
,
VStack
}
from
'
@chakra-ui/react
'
;
import
{
VStack
}
from
'
@chakra-ui/react
'
;
import
React
from
'
react
'
;
import
type
{
SubmitHandler
}
from
'
react-hook-form
'
;
import
{
FormProvider
,
useForm
}
from
'
react-hook-form
'
;
...
...
@@ -8,7 +8,8 @@ import type { SmartContractSecurityAuditSubmission } from 'types/api/contract';
import
type
{
ResourceError
}
from
'
lib/api/resources
'
;
import
useApiFetch
from
'
lib/api/useApiFetch
'
;
import
dayjs
from
'
lib/date/dayjs
'
;
import
useToast
from
'
lib/hooks/useToast
'
;
import
{
Button
}
from
'
toolkit/chakra/button
'
;
import
{
toaster
}
from
'
toolkit/chakra/toaster
'
;
import
FormFieldCheckbox
from
'
ui/shared/forms/fields/FormFieldCheckbox
'
;
import
FormFieldEmail
from
'
ui/shared/forms/fields/FormFieldEmail
'
;
import
FormFieldText
from
'
ui/shared/forms/fields/FormFieldText
'
;
...
...
@@ -39,7 +40,6 @@ const ContractSubmitAuditForm = ({ address, onSuccess }: Props) => {
const
containerRef
=
React
.
useRef
<
HTMLFormElement
>
(
null
);
const
apiFetch
=
useApiFetch
();
const
toast
=
useToast
();
const
formApi
=
useForm
<
Inputs
>
({
mode
:
'
onTouched
'
,
...
...
@@ -57,13 +57,9 @@ const ContractSubmitAuditForm = ({ address, onSuccess }: Props) => {
},
});
toast
({
position
:
'
top-right
'
,
toaster
.
success
({
title
:
'
Success
'
,
description
:
'
Your audit report has been successfully submitted for review
'
,
status
:
'
success
'
,
variant
:
'
subtle
'
,
isClosable
:
true
,
});
onSuccess
();
...
...
@@ -77,37 +73,32 @@ const ContractSubmitAuditForm = ({ address, onSuccess }: Props) => {
setError
(
errorField
,
{
type
:
'
custom
'
,
message
:
errorMap
[
errorField
].
join
(
'
,
'
)
});
});
}
else
{
toast
({
position
:
'
top-right
'
,
toaster
.
error
({
title
:
'
Error
'
,
description
:
(
_error
as
ResourceError
<
{
message
:
string
}
>
)?.
payload
?.
message
||
'
Something went wrong. Try again later.
'
,
status
:
'
error
'
,
variant
:
'
subtle
'
,
isClosable
:
true
,
});
}
}
},
[
apiFetch
,
address
,
toast
,
setError
,
onSuccess
]);
},
[
apiFetch
,
address
,
setError
,
onSuccess
]);
return
(
<
FormProvider
{
...
formApi
}
>
<
form
noValidate
onSubmit=
{
handleSubmit
(
onFormSubmit
)
}
autoComplete=
"off"
ref=
{
containerRef
}
>
<
VStack
gap=
{
5
}
alignItems=
"flex-start"
>
<
FormFieldText
<
Inputs
>
name="submitter_name"
isR
equired placeholder="Submitter name"/
>
<
FormFieldEmail
<
Inputs
>
name="submitter_email"
isR
equired placeholder="Submitter email"/
>
<
FormFieldText
<
Inputs
>
name="submitter_name"
r
equired placeholder="Submitter name"/
>
<
FormFieldEmail
<
Inputs
>
name="submitter_email"
r
equired placeholder="Submitter email"/
>
<
FormFieldCheckbox
<
Inputs
,
'
is_project_owner
'
>
name="is_project_owner"
label="I'm the contract owner"
/
>
<
FormFieldText
<
Inputs
>
name="project_name"
isR
equired placeholder="Project name"/
>
<
FormFieldUrl
<
Inputs
>
name="project_url"
isR
equired placeholder="Project URL"/
>
<
FormFieldText
<
Inputs
>
name="audit_company_name"
isR
equired placeholder="Audit company name"/
>
<
FormFieldUrl
<
Inputs
>
name="audit_report_url"
isR
equired placeholder="Audit report URL"/
>
<
FormFieldText
<
Inputs
>
name="project_name"
r
equired placeholder="Project name"/
>
<
FormFieldUrl
<
Inputs
>
name="project_url"
r
equired placeholder="Project URL"/
>
<
FormFieldText
<
Inputs
>
name="audit_company_name"
r
equired placeholder="Audit company name"/
>
<
FormFieldUrl
<
Inputs
>
name="audit_report_url"
r
equired placeholder="Audit report URL"/
>
<
FormFieldText
<
Inputs
>
name="audit_publish_date"
type="date"
max=
{
dayjs
().
format
(
'
YYYY-MM-DD
'
)
}
isRequired
inputProps=
{
{
type
:
'
date
'
,
max
:
dayjs
().
format
(
'
YYYY-MM-DD
'
)
}
}
required
placeholder="Audit publish date"
/
>
<
FormFieldText
<
Inputs
>
...
...
@@ -122,9 +113,9 @@ const ContractSubmitAuditForm = ({ address, onSuccess }: Props) => {
type=
"submit"
size=
"lg"
mt=
{
8
}
isL
oading=
{
formState
.
isSubmitting
}
l
oading=
{
formState
.
isSubmitting
}
loadingText=
"Send request"
isD
isabled=
{
!
formState
.
isDirty
}
d
isabled=
{
!
formState
.
isDirty
}
>
Send request
</
Button
>
...
...
ui/address/contract/info/ContractDetailsInfo.tsx
View file @
27872bfe
...
...
@@ -6,9 +6,9 @@ import type { SmartContract } from 'types/api/contract';
import
config
from
'
configs/app
'
;
import
{
CONTRACT_LICENSES
}
from
'
lib/contracts/licenses
'
;
import
dayjs
from
'
lib/date/dayjs
'
;
import
{
Link
}
from
'
toolkit/chakra/link
'
;
import
{
getGitHubOwnerAndRepo
}
from
'
ui/contractVerification/utils
'
;
import
ContractCertifiedLabel
from
'
ui/shared/ContractCertifiedLabel
'
;
import
LinkExternal
from
'
ui/shared/links/LinkExternal
'
;
import
ContractSecurityAudits
from
'
../audits/ContractSecurityAudits
'
;
import
ContractDetailsInfoItem
from
'
./ContractDetailsInfoItem
'
;
...
...
@@ -40,9 +40,9 @@ const ContractDetailsInfo = ({ data, isLoading, addressHash }: Props) => {
}
return
(
<
Link
E
xternal
href=
{
license
.
url
}
>
<
Link
e
xternal
href=
{
license
.
url
}
>
{
license
.
label
}
</
Link
External
>
</
Link
>
);
})();
...
...
@@ -57,9 +57,9 @@ const ContractDetailsInfo = ({ data, isLoading, addressHash }: Props) => {
const
commit
=
data
.
github_repository_metadata
.
commit
;
const
pathPrefix
=
data
.
github_repository_metadata
.
path_prefix
;
return
(
<
Link
E
xternal
href=
{
`${ repoUrl }/tree/${ commit }${ pathPrefix ? `
/
$
{
pathPrefix
}
` : '' }`
}
>
<
Link
e
xternal
href=
{
`${ repoUrl }/tree/${ commit }${ pathPrefix ? `
/
$
{
pathPrefix
}
` : '' }`
}
>
{
owner
&&
repo
?
`${ owner }/${ repo }`
:
data
.
github_repository_metadata
.
repository_url
}
</
Link
External
>
</
Link
>
);
})();
...
...
@@ -70,90 +70,102 @@ const ContractDetailsInfo = ({ data, isLoading, addressHash }: Props) => {
{
data
.
name
&&
(
<
ContractDetailsInfoItem
label=
"Contract name"
content=
{
contractNameWithCertifiedIcon
}
isLoading=
{
isLoading
}
/>
>
{
contractNameWithCertifiedIcon
}
</
ContractDetailsInfoItem
>
)
}
{
data
.
compiler_version
&&
(
<
ContractDetailsInfoItem
label=
"Compiler version"
content=
{
data
.
compiler_version
}
isLoading=
{
isLoading
}
/>
>
{
data
.
compiler_version
}
</
ContractDetailsInfoItem
>
)
}
{
data
.
zk_compiler_version
&&
(
<
ContractDetailsInfoItem
label=
"ZK compiler version"
content=
{
data
.
zk_compiler_version
}
isLoading=
{
isLoading
}
/>
>
{
data
.
zk_compiler_version
}
</
ContractDetailsInfoItem
>
)
}
{
data
.
evm_version
&&
(
<
ContractDetailsInfoItem
label=
"EVM version"
content=
{
data
.
evm_version
}
textTransform=
"capitalize"
isLoading=
{
isLoading
}
/>
>
{
data
.
evm_version
}
</
ContractDetailsInfoItem
>
)
}
{
licenseLink
&&
(
<
ContractDetailsInfoItem
label=
"License"
content=
{
licenseLink
}
hint=
"License type is entered manually during verification. The initial source code may contain a different license type than the one displayed."
isLoading=
{
isLoading
}
/>
>
{
licenseLink
}
</
ContractDetailsInfoItem
>
)
}
{
typeof
data
.
optimization_enabled
===
'
boolean
'
&&
!
isStylusContract
&&
(
<
ContractDetailsInfoItem
label=
"Optimization enabled"
content=
{
data
.
optimization_enabled
?
'
true
'
:
'
false
'
}
isLoading=
{
isLoading
}
/>
>
{
data
.
optimization_enabled
?
'
true
'
:
'
false
'
}
</
ContractDetailsInfoItem
>
)
}
{
data
.
optimization_runs
!==
null
&&
!
isStylusContract
&&
(
<
ContractDetailsInfoItem
label=
{
rollupFeature
.
isEnabled
&&
rollupFeature
.
type
===
'
zkSync
'
?
'
Optimization mode
'
:
'
Optimization runs
'
}
content=
{
String
(
data
.
optimization_runs
)
}
isLoading=
{
isLoading
}
/>
>
{
String
(
data
.
optimization_runs
)
}
</
ContractDetailsInfoItem
>
)
}
{
data
.
package_name
&&
(
<
ContractDetailsInfoItem
label=
"Package name"
content=
{
data
.
package_name
}
isLoading=
{
isLoading
}
/>
>
{
data
.
package_name
}
</
ContractDetailsInfoItem
>
)
}
{
data
.
verified_at
&&
(
<
ContractDetailsInfoItem
label=
"Verified at"
content=
{
dayjs
(
data
.
verified_at
).
format
(
'
llll
'
)
}
wordBreak=
"break-word"
isLoading=
{
isLoading
}
/>
>
{
dayjs
(
data
.
verified_at
).
format
(
'
llll
'
)
}
</
ContractDetailsInfoItem
>
)
}
{
data
.
file_path
&&
!
isStylusContract
&&
(
<
ContractDetailsInfoItem
label=
"Contract file path"
content=
{
data
.
file_path
}
wordBreak=
"break-word"
isLoading=
{
isLoading
}
/>
>
{
data
.
file_path
}
</
ContractDetailsInfoItem
>
)
}
{
sourceCodeLink
&&
(
<
ContractDetailsInfoItem
label=
"Source code"
content=
{
sourceCodeLink
}
isLoading=
{
isLoading
}
/>
>
{
sourceCodeLink
}
</
ContractDetailsInfoItem
>
)
}
{
config
.
UI
.
hasContractAuditReports
&&
(
<
ContractDetailsInfoItem
label=
"Security audit"
content=
{
<
ContractSecurityAudits
addressHash=
{
addressHash
}
/>
}
isLoading=
{
isLoading
}
/>
>
<
ContractSecurityAudits
addressHash=
{
addressHash
}
/>
</
ContractDetailsInfoItem
>
)
}
</
Grid
>
);
...
...
ui/address/contract/info/ContractDetailsInfoItem.tsx
View file @
27872bfe
import
{
chakra
,
useColorModeValue
,
Flex
,
GridItem
}
from
'
@chakra-ui/react
'
;
import
{
chakra
,
Flex
,
GridItem
}
from
'
@chakra-ui/react
'
;
import
React
from
'
react
'
;
import
Skeleton
from
'
ui/shared/chakra/S
keleton
'
;
import
{
Skeleton
}
from
'
toolkit/chakra/s
keleton
'
;
import
Hint
from
'
ui/shared/Hint
'
;
interface
Props
{
label
:
string
;
c
ontent
:
string
|
React
.
ReactNode
;
c
hildren
:
React
.
ReactNode
;
className
?:
string
;
isLoading
:
boolean
;
hint
?:
string
;
}
const
ContractDetailsInfoItem
=
({
label
,
content
,
className
,
isLoading
,
hint
}:
Props
)
=>
{
const
hintIconColor
=
useColorModeValue
(
'
gray.600
'
,
'
gray.400
'
);
const
ContractDetailsInfoItem
=
({
label
,
children
,
className
,
isLoading
,
hint
}:
Props
)
=>
{
return
(
<
GridItem
display=
"flex"
columnGap=
{
6
}
wordBreak=
"break-all"
className=
{
className
}
alignItems=
"baseline"
>
<
Skeleton
isLoaded=
{
!
isLoading
}
w=
"170px"
flexShrink=
{
0
}
fontWeight=
{
500
}
>
<
Skeleton
loading=
{
isLoading
}
w=
"170px"
flexShrink=
{
0
}
fontWeight=
{
500
}
>
<
Flex
alignItems=
"center"
>
{
label
}
{
hint
&&
(
<
Hint
label=
{
hint
}
ml=
{
2
}
color=
{
hintIconColor
}
tooltipProps=
{
{
p
lacement
:
'
bottom
'
}
}
color=
{
{
_light
:
'
gray.600
'
,
_dark
:
'
gray.400
'
}
}
tooltipProps=
{
{
p
ositioning
:
{
placement
:
'
bottom
'
}
}
}
/>
)
}
</
Flex
>
</
Skeleton
>
<
Skeleton
isLoaded=
{
!
isLoading
}
>
{
content
}
</
Skeleton
>
<
Skeleton
loading=
{
isLoading
}
>
{
children
}
</
Skeleton
>
</
GridItem
>
);
};
...
...
ui/address/contract/useContractDetailsTabs.tsx
View file @
27872bfe
import
{
Alert
,
Flex
}
from
'
@chakra-ui/react
'
;
import
{
Flex
}
from
'
@chakra-ui/react
'
;
import
React
from
'
react
'
;
import
type
{
SmartContract
}
from
'
types/api/contract
'
;
import
{
Alert
}
from
'
toolkit/chakra/alert
'
;
import
CodeViewSnippet
from
'
ui/shared/CodeViewSnippet
'
;
import
RawDataSnippet
from
'
ui/shared/RawDataSnippet
'
;
...
...
ui/address/contract/useContractTabs.tsx
View file @
27872bfe
...
...
@@ -74,12 +74,12 @@ export default function useContractTabs(data: Address | undefined, isPlaceholder
return React.useMemo(() => {
return {
tabs: [
//
data?.hash && {
//
id: 'contract_code' as const,
//
title: 'Code',
//
component: <ContractDetails mainContractQuery={ contractQuery } channel={ channel } addressHash={ data.hash }/>,
//
subTabs: CONTRACT_DETAILS_TAB_IDS as unknown as Array<string>,
//
},
data?.hash && {
id: 'contract_code' as const,
title: 'Code',
component: <ContractDetails mainContractQuery={ contractQuery } channel={ channel } addressHash={ data.hash }/>,
subTabs: CONTRACT_DETAILS_TAB_IDS as unknown as Array<string>,
},
// contractQuery.data?.abi && {
// id: [ 'read_write_contract' as const, 'read_contract' as const, 'write_contract' as const ],
// title: 'Read/Write contract',
...
...
ui/pages/Address.tsx
View file @
27872bfe
...
...
@@ -235,29 +235,29 @@ const AddressPageContent = () => {
} :
undefined,
//
addressQuery.data?.is_contract ? {
//
id: 'contract',
//
title: () => {
//
if (addressQuery.data.is_verified) {
//
return (
//
<>
//
<span>Contract</span>
//
<IconSvg name="status/success" boxSize="14px" color="green.500" ml={ 1 }/>
//
</>
//
);
//
}
//
return 'Contract';
//
},
//
component: (
//
<AddressContract
//
tabs={ contractTabs.tabs }
//
shouldRender={ !isTabsLoading }
//
isLoading={ contractTabs.isLoading }
//
/>
//
),
//
subTabs: CONTRACT_TAB_IDS,
//
} : undefined,
addressQuery.data?.is_contract ? {
id: 'contract',
title: () => {
if (addressQuery.data.is_verified) {
return (
<>
<span>Contract</span>
<IconSvg name="status/success" boxSize="14px" color="green.500" ml={ 1 }/>
</>
);
}
return 'Contract';
},
component: (
<AddressContract
tabs={ contractTabs.tabs }
shouldRender={ !isTabsLoading }
isLoading={ contractTabs.isLoading }
/>
),
subTabs: CONTRACT_TAB_IDS,
} : undefined,
].filter(Boolean);
}, [
addressQuery.data,
...
...
ui/shared/CodeViewSnippet.tsx
View file @
27872bfe
import
{
Box
,
chakra
,
Flex
}
from
'
@chakra-ui/react
'
;
import
React
from
'
react
'
;
import
Skeleton
from
'
ui/shared/chakra/S
keleton
'
;
import
{
Skeleton
}
from
'
toolkit/chakra/s
keleton
'
;
import
CopyToClipboard
from
'
ui/shared/CopyToClipboard
'
;
import
CodeEditor
from
'
ui/shared/monaco/CodeEditor
'
;
...
...
@@ -25,12 +25,12 @@ const CodeViewSnippet = ({ data, copyData, language, title, className, rightSlot
<
Box
className=
{
className
}
as=
"section"
title=
{
title
}
>
{
(
title
||
rightSlot
)
&&
(
<
Flex
justifyContent=
{
title
?
'
space-between
'
:
'
flex-end
'
}
alignItems=
"center"
mb=
{
3
}
>
{
title
&&
<
Skeleton
fontWeight=
{
500
}
isLoaded=
{
!
isLoading
}
>
{
title
}
</
Skeleton
>
}
{
title
&&
<
Skeleton
loading=
{
isLoading
}
fontWeight=
{
500
}
>
{
title
}
</
Skeleton
>
}
{
rightSlot
}
<
CopyToClipboard
text=
{
copyData
??
data
}
isLoading=
{
isLoading
}
/>
</
Flex
>
)
}
{
isLoading
?
<
Skeleton
height=
"500px"
w=
"100%"
/>
:
<
CodeEditor
data=
{
editorData
}
language=
{
language
}
/>
}
{
isLoading
?
<
Skeleton
loading
height=
"500px"
w=
"100%"
/>
:
<
CodeEditor
data=
{
editorData
}
language=
{
language
}
/>
}
</
Box
>
);
};
...
...
ui/shared/ad/CoinzillaTextAd.tsx
View file @
27872bfe
...
...
@@ -87,7 +87,7 @@ const CoinzillaTextAd = ({ className }: { className?: string }) => {
src=
{
adData
.
ad
.
thumbnail
}
width=
"20px"
height=
"20px"
mb=
"
-4
px"
mb=
"
2
px"
mr=
{
1
}
display=
"inline-block"
alt=
""
...
...
ui/shared/forms/fields/FormFieldText.tsx
View file @
27872bfe
import
type
{
HTMLChakraProps
}
from
'
@chakra-ui/react
'
;
import
React
from
'
react
'
;
import
type
{
FieldValues
,
Path
}
from
'
react-hook-form
'
;
import
{
useController
,
useFormContext
}
from
'
react-hook-form
'
;
...
...
@@ -6,7 +5,9 @@ import { useController, useFormContext } from 'react-hook-form';
import
type
{
FormFieldPropsBase
}
from
'
./types
'
;
import
{
Field
}
from
'
toolkit/chakra/field
'
;
import
type
{
InputProps
}
from
'
toolkit/chakra/input
'
;
import
{
Input
}
from
'
toolkit/chakra/input
'
;
import
type
{
TextareaProps
}
from
'
toolkit/chakra/textarea
'
;
import
{
Textarea
}
from
'
toolkit/chakra/textarea
'
;
import
getFieldErrorText
from
'
../utils/getFieldErrorText
'
;
...
...
@@ -49,14 +50,14 @@ const FormFieldText = <
<
Textarea
{
...
field
}
autoComplete=
"off"
{
...
inputProps
as
HTMLChakraProps
<'
textarea
'
>
}
{
...
inputProps
as
TextareaProps
}
onBlur=
{
handleBlur
}
/>
)
:
(
<
Input
{
...
field
}
autoComplete=
"off"
{
...
inputProps
as
HTMLChakraProps
<'
input
'
>
}
{
...
inputProps
as
InputProps
}
onBlur=
{
handleBlur
}
/>
);
...
...
ui/shared/forms/fields/types.ts
View file @
27872bfe
import
type
{
HTMLChakraProps
}
from
'
@chakra-ui/react
'
;
import
type
React
from
'
react
'
;
import
type
{
ControllerRenderProps
,
FieldValues
,
Path
,
RegisterOptions
}
from
'
react-hook-form
'
;
import
type
{
FieldProps
}
from
'
toolkit/chakra/field
'
;
import
type
{
InputProps
}
from
'
toolkit/chakra/input
'
;
import
type
{
TextareaProps
}
from
'
toolkit/chakra/textarea
'
;
export
interface
FormFieldPropsBase
<
FormFields
extends
FieldValues
,
...
...
@@ -14,5 +15,5 @@ export interface FormFieldPropsBase<
onBlur
?:
()
=>
void
;
onChange
?:
()
=>
void
;
rightElement
?:
({
field
}:
{
field
:
ControllerRenderProps
<
FormFields
,
Name
>
})
=>
React
.
ReactNode
;
inputProps
?:
HTMLChakraProps
<
'
input
'
|
'
textarea
'
>
;
inputProps
?:
InputProps
|
TextareaProps
;
}
ui/shared/monaco/CodeEditor.tsx
View file @
27872bfe
import
type
{
SystemStyleObject
}
from
'
@chakra-ui/react
'
;
import
{
Box
,
useColorMode
,
Flex
,
useToken
,
Center
}
from
'
@chakra-ui/react
'
;
import
{
Box
,
Flex
,
useToken
,
Center
}
from
'
@chakra-ui/react
'
;
import
type
{
EditorProps
}
from
'
@monaco-editor/react
'
;
import
MonacoEditor
from
'
@monaco-editor/react
'
;
import
type
*
as
monaco
from
'
monaco-editor/esm/vs/editor/editor.api
'
;
...
...
@@ -11,6 +11,7 @@ import type { SmartContractExternalLibrary } from 'types/api/contract';
import
useClientRect
from
'
lib/hooks/useClientRect
'
;
import
useIsMobile
from
'
lib/hooks/useIsMobile
'
;
import
isMetaKey
from
'
lib/isMetaKey
'
;
import
{
useColorMode
}
from
'
toolkit/chakra/color-mode
'
;
import
ErrorBoundary
from
'
ui/shared/ErrorBoundary
'
;
import
CodeEditorBreadcrumbs
from
'
./CodeEditorBreadcrumbs
'
;
...
...
@@ -56,7 +57,7 @@ const CodeEditor = ({ data, remappings, libraries, language, mainFile, contractN
const
[
containerRect
,
containerNodeRef
]
=
useClientRect
<
HTMLDivElement
>
();
const
{
colorMode
}
=
useColorMode
();
const
borderRadius
=
useToken
(
'
radii
'
,
'
md
'
);
const
[
borderRadius
]
=
useToken
(
'
radii
'
,
'
md
'
);
const
isMobile
=
useIsMobile
();
const
themeColors
=
useThemeColors
();
...
...
@@ -202,28 +203,28 @@ const CodeEditor = ({ data, remappings, libraries, language, mainFile, contractN
setIsMetaPressed
(
false
);
},
[]);
const
container
Sx
:
SystemStyleObject
=
React
.
useMemo
(()
=>
({
'
.editor-container
'
:
{
const
container
Css
:
SystemStyleObject
=
React
.
useMemo
(()
=>
({
'
&
.editor-container
'
:
{
position
:
'
absolute
'
,
top
:
0
,
left
:
0
,
width
:
`
${
editorWidth
}
px`
,
height
:
'
100%
'
,
},
'
.monaco-editor
'
:
{
'
&
.monaco-editor
'
:
{
'
border-bottom-left-radius
'
:
borderRadius
,
},
'
.monaco-editor .overflow-guard
'
:
{
'
&
.monaco-editor .overflow-guard
'
:
{
'
border-bottom-left-radius
'
:
borderRadius
,
},
'
.monaco-editor .core-guide
'
:
{
'
&
.monaco-editor .core-guide
'
:
{
zIndex
:
1
,
},
// '.monaco-editor .currentFindMatch': // TODO: find a better way to style this
'
.monaco-editor .findMatch
'
:
{
'
&
.monaco-editor .findMatch
'
:
{
backgroundColor
:
themeColors
[
'
custom.findMatchHighlightBackground
'
],
},
'
.highlight
'
:
{
'
&
.highlight
'
:
{
backgroundColor
:
themeColors
[
'
custom.findMatchHighlightBackground
'
],
},
'
&&.meta-pressed .import-link:hover, &&.meta-pressed .import-link:hover + .import-link
'
:
{
...
...
@@ -231,19 +232,19 @@ const CodeEditor = ({ data, remappings, libraries, language, mainFile, contractN
textDecoration
:
'
underline
'
,
cursor
:
'
pointer
'
,
},
'
.risk-warning-primary
'
:
{
'
&
.risk-warning-primary
'
:
{
backgroundColor
:
themeColors
[
'
custom.riskWarning.primaryBackground
'
],
},
'
.risk-warning
'
:
{
'
&
.risk-warning
'
:
{
backgroundColor
:
themeColors
[
'
custom.riskWarning.background
'
],
},
'
.main-contract-header
'
:
{
'
&
.main-contract-header
'
:
{
backgroundColor
:
themeColors
[
'
custom.mainContract.header
'
],
},
'
.main-contract-body
'
:
{
'
&
.main-contract-body
'
:
{
backgroundColor
:
themeColors
[
'
custom.mainContract.body
'
],
},
'
.main-contract-glyph
'
:
{
'
&
.main-contract-glyph
'
:
{
zIndex
:
1
,
background
:
'
url(/static/contract_star.png) no-repeat center center
'
,
backgroundSize
:
'
12px
'
,
...
...
@@ -256,18 +257,18 @@ const CodeEditor = ({ data, remappings, libraries, language, mainFile, contractN
},
[
themeColors
]);
if
(
data
.
length
===
1
)
{
const
sx
=
{
...
container
Sx
,
'
.monaco-editor
'
:
{
const
css
=
{
...
container
Css
,
'
&
.monaco-editor
'
:
{
'
border-radius
'
:
borderRadius
,
},
'
.monaco-editor .overflow-guard
'
:
{
'
&
.monaco-editor .overflow-guard
'
:
{
'
border-radius
'
:
borderRadius
,
},
};
return
(
<
Box
height=
{
`${ EDITOR_HEIGHT }px`
}
width=
"100%"
sx=
{
sx
}
ref=
{
containerNodeRef
}
>
<
Box
height=
{
`${ EDITOR_HEIGHT }px`
}
width=
"100%"
css=
{
css
}
ref=
{
containerNodeRef
}
>
<
ErrorBoundary
renderErrorScreen=
{
renderErrorScreen
}
>
<
MonacoEditor
className=
"editor-container"
...
...
@@ -290,7 +291,7 @@ const CodeEditor = ({ data, remappings, libraries, language, mainFile, contractN
height=
{
`${ EDITOR_HEIGHT + TABS_HEIGHT + BREADCRUMBS_HEIGHT }px`
}
position=
"relative"
ref=
{
containerNodeRef
}
sx=
{
containerSx
}
css=
{
containerCss
}
overflow=
{
{
base
:
'
hidden
'
,
lg
:
'
visible
'
}
}
borderRadius=
"md"
onClick=
{
handleClick
}
...
...
ui/shared/monaco/CodeEditorFileTree.tsx
View file @
27872bfe
import
type
{
Chakra
Props
}
from
'
@chakra-ui/react
'
;
import
{
Box
,
Accordion
,
AccordionButton
,
AccordionItem
,
AccordionPanel
,
chakra
}
from
'
@chakra-ui/react
'
;
import
type
{
AccordionItem
Props
}
from
'
@chakra-ui/react
'
;
import
{
Box
,
chakra
}
from
'
@chakra-ui/react
'
;
import
React
from
'
react
'
;
import
type
{
FileTree
}
from
'
./types
'
;
import
{
AccordionItem
,
AccordionItemContent
,
AccordionItemTrigger
,
AccordionRoot
}
from
'
toolkit/chakra/accordion
'
;
import
IconSvg
from
'
ui/shared/IconSvg
'
;
import
CodeEditorFileIcon
from
'
./CodeEditorFileIcon
'
;
...
...
@@ -20,7 +21,13 @@ interface Props {
}
const
CodeEditorFileTree
=
({
tree
,
level
=
0
,
onItemClick
,
isCollapsed
,
selectedFile
,
mainFile
}:
Props
)
=>
{
const
itemProps
:
ChakraProps
=
{
const
[
value
,
setValue
]
=
React
.
useState
<
Array
<
string
>>
(
isCollapsed
?
[]
:
tree
.
map
((
item
)
=>
item
.
name
));
const
handleValueChange
=
React
.
useCallback
(({
value
}:
{
value
:
Array
<
string
>
})
=>
{
setValue
(
value
);
},
[]);
const
itemProps
:
Partial
<
AccordionItemProps
>
=
{
borderWidth
:
'
0px
'
,
cursor
:
'
pointer
'
,
lineHeight
:
'
22px
'
,
...
...
@@ -31,17 +38,16 @@ const CodeEditorFileTree = ({ tree, level = 0, onItemClick, isCollapsed, selecte
const
themeColors
=
useThemeColors
();
return
(
<
Accordion
allowMultiple
defaultIndex=
{
isCollapsed
?
undefined
:
tree
.
map
((
item
,
index
)
=>
index
)
}
reduceMo
tion
>
<
Accordion
Root
multiple
value=
{
value
}
onValueChange=
{
handleValueChange
}
noAnima
tion
>
{
tree
.
map
((
leaf
,
index
)
=>
{
const
leafName
=
<
chakra
.
span
overflow=
"hidden"
textOverflow=
"ellipsis"
whiteSpace=
"nowrap"
>
{
leaf
.
name
}
</
chakra
.
span
>;
const
isExpanded
=
value
.
includes
(
leaf
.
name
);
if
(
'
children
'
in
leaf
)
{
return
(
<
AccordionItem
key=
{
index
}
{
...
itemProps
}
>
{
({
isExpanded
})
=>
(
<>
<
AccordionButton
<
AccordionItem
key=
{
index
}
value=
{
leaf
.
name
}
{
...
itemProps
}
>
<
AccordionItemTrigger
pr=
"8px"
py=
"0"
pl=
{
`${ 8 + 8 * level }px`
}
...
...
@@ -50,17 +56,25 @@ const CodeEditorFileTree = ({ tree, level = 0, onItemClick, isCollapsed, selecte
lineHeight=
"22px"
h=
"22px"
transitionDuration=
"0"
noIndicator
>
<
Box
className=
"codicon codicon-tree-item-expanded"
transform=
{
isExpanded
?
'
rotate(0deg)
'
:
'
rotate(-90deg)
'
}
transform=
"rotate(-90deg)"
_groupExpanded=
{
{
transform
:
'
rotate(0deg)
'
,
}
}
boxSize=
"16px"
mr=
"2px"
/>
<
IconSvg
name=
{
isExpanded
?
'
monaco/folder-open
'
:
'
monaco/folder
'
}
boxSize=
"16px"
mr=
"4px"
/>
<
IconSvg
name=
{
isExpanded
?
'
monaco/folder-open
'
:
'
monaco/folder
'
}
boxSize=
"16px"
mr=
"4px"
/>
{
leafName
}
</
AccordionButton
>
<
AccordionPanel
p=
"0"
>
</
AccordionItemTrigger
>
<
AccordionItemContent
p=
"0"
>
<
CodeEditorFileTree
tree=
{
leaf
.
children
}
level=
{
level
+
1
}
...
...
@@ -69,9 +83,7 @@ const CodeEditorFileTree = ({ tree, level = 0, onItemClick, isCollapsed, selecte
selectedFile=
{
selectedFile
}
mainFile=
{
mainFile
}
/>
</
AccordionPanel
>
</>
)
}
</
AccordionItemContent
>
</
AccordionItem
>
);
}
...
...
@@ -79,6 +91,7 @@ const CodeEditorFileTree = ({ tree, level = 0, onItemClick, isCollapsed, selecte
return
(
<
AccordionItem
key=
{
index
}
value=
{
leaf
.
name
}
{
...
itemProps
}
pl=
{
`${ 26 + (level * 8) }px`
}
pr=
"8px"
...
...
@@ -106,7 +119,7 @@ const CodeEditorFileTree = ({ tree, level = 0, onItemClick, isCollapsed, selecte
);
})
}
</
Accordion
>
</
Accordion
Root
>
);
};
...
...
ui/shared/monaco/CodeEditorMainFileIndicator.tsx
View file @
27872bfe
import
{
Box
,
chakra
,
Tooltip
}
from
'
@chakra-ui/react
'
;
import
{
Box
,
chakra
}
from
'
@chakra-ui/react
'
;
import
React
from
'
react
'
;
import
{
Tooltip
}
from
'
toolkit/chakra/tooltip
'
;
import
IconSvg
from
'
ui/shared/IconSvg
'
;
interface
Props
{
...
...
@@ -9,7 +10,7 @@ interface Props {
const
CodeEditorMainFileIndicator
=
({
className
}:
Props
)
=>
{
return
(
<
Tooltip
label
=
"The main file containing verified contract"
>
<
Tooltip
content
=
"The main file containing verified contract"
>
<
Box
className=
{
className
}
>
<
IconSvg
name=
"star_filled"
boxSize=
{
3
}
display=
"block"
color=
"green.500"
/>
</
Box
>
...
...
ui/shared/monaco/CodeEditorSearch.tsx
View file @
27872bfe
import
type
{
ChakraProps
}
from
'
@chakra-ui/react
'
;
import
{
Accordion
,
Box
,
Input
,
InputGroup
,
InputRightElement
,
useBoolean
}
from
'
@chakra-ui/react
'
;
import
type
{
HTML
ChakraProps
}
from
'
@chakra-ui/react
'
;
import
{
Box
}
from
'
@chakra-ui/react
'
;
import
type
*
as
monaco
from
'
monaco-editor/esm/vs/editor/editor.api
'
;
import
React
from
'
react
'
;
import
type
{
File
,
Monaco
,
SearchResult
}
from
'
./types
'
;
import
useDebounce
from
'
lib/hooks/useDebounce
'
;
import
{
AccordionRoot
}
from
'
toolkit/chakra/accordion
'
;
import
{
Input
}
from
'
toolkit/chakra/input
'
;
import
{
InputGroup
}
from
'
toolkit/chakra/input-group
'
;
import
CodeEditorSearchSection
from
'
./CodeEditorSearchSection
'
;
import
CoderEditorCollapseButton
from
'
./CoderEditorCollapseButton
'
;
...
...
@@ -24,16 +27,28 @@ interface Props {
const
CodeEditorSearch
=
({
monaco
,
data
,
onFileSelect
,
isInputStuck
,
isActive
,
setActionBarRenderer
,
defaultValue
}:
Props
)
=>
{
const
[
searchTerm
,
changeSearchTerm
]
=
React
.
useState
(
''
);
const
[
searchResults
,
setSearchResults
]
=
React
.
useState
<
Array
<
SearchResult
>>
([]);
const
[
expandedSections
,
setExpandedSections
]
=
React
.
useState
<
Array
<
number
>>
([]);
const
[
isMatchCase
,
setMatchCase
]
=
useBoolean
(
);
const
[
isMatchWholeWord
,
setMatchWholeWord
]
=
useBoolean
(
);
const
[
isMatchRegex
,
setMatchRegex
]
=
useBoolean
(
);
const
[
expandedSections
,
setExpandedSections
]
=
React
.
useState
<
Array
<
string
>>
([]);
const
[
isMatchCase
,
setMatchCase
]
=
React
.
useState
(
false
);
const
[
isMatchWholeWord
,
setMatchWholeWord
]
=
React
.
useState
(
false
);
const
[
isMatchRegex
,
setMatchRegex
]
=
React
.
useState
(
false
);
const
decorations
=
React
.
useRef
<
Record
<
string
,
Array
<
string
>>>
({});
const
themeColors
=
useThemeColors
();
const
debouncedSearchTerm
=
useDebounce
(
searchTerm
,
300
);
const
handleMatchCaseChange
=
React
.
useCallback
(()
=>
{
setMatchCase
((
prev
)
=>
!
prev
);
},
[]);
const
handleMatchWholeWordChange
=
React
.
useCallback
(()
=>
{
setMatchWholeWord
((
prev
)
=>
!
prev
);
},
[]);
const
handleMatchRegexChange
=
React
.
useCallback
(()
=>
{
setMatchRegex
((
prev
)
=>
!
prev
);
},
[]);
React
.
useEffect
(()
=>
{
changeSearchTerm
(
defaultValue
);
},
[
defaultValue
]);
...
...
@@ -73,7 +88,7 @@ const CodeEditorSearch = ({ monaco, data, onFileSelect, isInputStuck, isActive,
},
[
debouncedSearchTerm
,
isMatchCase
,
isMatchRegex
,
isMatchWholeWord
,
monaco
]);
React
.
useEffect
(()
=>
{
setExpandedSections
(
searchResults
.
map
((
item
,
index
)
=>
index
));
setExpandedSections
(
searchResults
.
map
((
item
)
=>
item
.
file_path
));
},
[
searchResults
]);
const
handleSearchTermChange
=
React
.
useCallback
((
event
:
React
.
ChangeEvent
<
HTMLInputElement
>
)
=>
{
...
...
@@ -87,13 +102,13 @@ const CodeEditorSearch = ({ monaco, data, onFileSelect, isInputStuck, isActive,
}
},
[
data
,
onFileSelect
]);
const
handleAccordionStateChange
=
React
.
useCallback
((
newValue
:
Array
<
number
>
)
=>
{
setExpandedSections
(
newV
alue
);
const
handleAccordionStateChange
=
React
.
useCallback
((
{
value
}:
{
value
:
Array
<
string
>
}
)
=>
{
setExpandedSections
(
v
alue
);
},
[]);
const
handleToggleCollapseClick
=
React
.
useCallback
(()
=>
{
if
(
expandedSections
.
length
===
0
)
{
setExpandedSections
(
searchResults
.
map
((
item
,
index
)
=>
index
));
setExpandedSections
(
searchResults
.
map
((
item
)
=>
item
.
file_path
));
}
else
{
setExpandedSections
([]);
}
...
...
@@ -114,13 +129,14 @@ const CodeEditorSearch = ({ monaco, data, onFileSelect, isInputStuck, isActive,
isActive
&&
setActionBarRenderer
(()
=>
renderActionBar
);
},
[
isActive
,
renderActionBar
,
setActionBarRenderer
]);
const
buttonProps
:
ChakraProps
=
{
const
buttonProps
:
HTMLChakraProps
<
'
div
'
>
=
{
boxSize
:
'
20px
'
,
p
:
'
1px
'
,
cursor
:
'
pointer
'
,
borderRadius
:
'
3px
'
,
borderWidth
:
'
1px
'
,
borderColor
:
'
transparent
'
,
color
:
'
global.body.fg
'
,
};
const
searchResultNum
=
(()
=>
{
...
...
@@ -145,8 +161,40 @@ const CodeEditorSearch = ({ monaco, data, onFileSelect, isInputStuck, isActive,
);
})();
const
inputEndElement
=
(
<>
<
Box
{
...
buttonProps
}
className=
"codicon codicon-case-sensitive"
onClick=
{
handleMatchCaseChange
}
bgColor=
{
isMatchCase
?
themeColors
[
'
custom.inputOption.activeBackground
'
]
:
'
transparent
'
}
_hover=
{
{
bgColor
:
isMatchCase
?
themeColors
[
'
custom.inputOption.activeBackground
'
]
:
themeColors
[
'
custom.inputOption.hoverBackground
'
]
}
}
title=
"Match Case"
aria
-
label=
"Match Case"
/>
<
Box
{
...
buttonProps
}
className=
"codicon codicon-whole-word"
bgColor=
{
isMatchWholeWord
?
themeColors
[
'
custom.inputOption.activeBackground
'
]
:
'
transparent
'
}
onClick=
{
handleMatchWholeWordChange
}
_hover=
{
{
bgColor
:
isMatchWholeWord
?
themeColors
[
'
custom.inputOption.activeBackground
'
]
:
themeColors
[
'
custom.inputOption.hoverBackground
'
]
}
}
title=
"Match Whole Word"
aria
-
label=
"Match Whole Word"
/>
<
Box
{
...
buttonProps
}
className=
"codicon codicon-regex"
bgColor=
{
isMatchRegex
?
themeColors
[
'
custom.inputOption.activeBackground
'
]
:
'
transparent
'
}
onClick=
{
handleMatchRegexChange
}
_hover=
{
{
bgColor
:
isMatchRegex
?
themeColors
[
'
custom.inputOption.activeBackground
'
]
:
themeColors
[
'
custom.inputOption.hoverBackground
'
]
}
}
title=
"Use Regular Expression"
aria
-
label=
"Use Regular Expression"
/>
</>
);
return
(
<
Box
>
<>
<
InputGroup
px=
"8px"
position=
"sticky"
...
...
@@ -155,17 +203,20 @@ const CodeEditorSearch = ({ monaco, data, onFileSelect, isInputStuck, isActive,
zIndex=
"2"
bgColor=
{
themeColors
[
'
sideBar.background
'
]
}
pb=
"8px"
boxShadow=
{
isInputStuck
?
'
md
'
:
'
none
'
}
boxShadow=
{
isInputStuck
?
'
0px 6px 3px 0px rgba(0, 0, 0, 0.05)
'
:
'
none
'
}
endElement=
{
inputEndElement
}
endElementProps=
{
{
height
:
'
26px
'
,
pl
:
'
0
'
,
pr
:
'
10px
'
,
columnGap
:
'
2px
'
}
}
endOffset=
"75px"
>
<
Input
size=
"xs"
onChange=
{
handleSearchTermChange
}
value=
{
searchTerm
}
placeholder=
"Search"
variant=
"unstyled"
color=
{
themeColors
[
'
input.foreground
'
]
}
bgColor=
{
themeColors
[
'
input.background
'
]
}
borderRadius=
"none"
height=
"26px"
fontSize=
"13px"
lineHeight=
"20px"
borderWidth=
"1px"
...
...
@@ -178,47 +229,19 @@ const CodeEditorSearch = ({ monaco, data, onFileSelect, isInputStuck, isActive,
borderColor
:
themeColors
.
focusBorder
,
}
}
/>
<
InputRightElement
w=
"auto"
h=
"auto"
right=
"12px"
top=
"3px"
columnGap=
"2px"
>
<
Box
{
...
buttonProps
}
className=
"codicon codicon-case-sensitive"
onClick=
{
setMatchCase
.
toggle
}
bgColor=
{
isMatchCase
?
themeColors
[
'
custom.inputOption.activeBackground
'
]
:
'
transparent
'
}
_hover=
{
{
bgColor
:
isMatchCase
?
themeColors
[
'
custom.inputOption.activeBackground
'
]
:
themeColors
[
'
custom.inputOption.hoverBackground
'
]
}
}
title=
"Match Case"
aria
-
label=
"Match Case"
/>
<
Box
{
...
buttonProps
}
className=
"codicon codicon-whole-word"
bgColor=
{
isMatchWholeWord
?
themeColors
[
'
custom.inputOption.activeBackground
'
]
:
'
transparent
'
}
onClick=
{
setMatchWholeWord
.
toggle
}
_hover=
{
{
bgColor
:
isMatchWholeWord
?
themeColors
[
'
custom.inputOption.activeBackground
'
]
:
themeColors
[
'
custom.inputOption.hoverBackground
'
]
}
}
title=
"Match Whole Word"
aria
-
label=
"Match Whole Word"
/>
<
Box
{
...
buttonProps
}
className=
"codicon codicon-regex"
bgColor=
{
isMatchRegex
?
themeColors
[
'
custom.inputOption.activeBackground
'
]
:
'
transparent
'
}
onClick=
{
setMatchRegex
.
toggle
}
_hover=
{
{
bgColor
:
isMatchRegex
?
themeColors
[
'
custom.inputOption.activeBackground
'
]
:
themeColors
[
'
custom.inputOption.hoverBackground
'
]
}
}
title=
"Use Regular Expression"
aria
-
label=
"Use Regular Expression"
/>
</
InputRightElement
>
</
InputGroup
>
{
searchResultNum
}
<
Accordion
<
Accordion
Root
key=
{
debouncedSearchTerm
}
allowM
ultiple
index
=
{
expandedSections
}
onChange=
{
handleAccordionStateChange
}
reduceMo
tion
m
ultiple
value
=
{
expandedSections
}
on
Value
Change=
{
handleAccordionStateChange
}
noAnima
tion
>
{
searchResults
.
map
((
item
)
=>
<
CodeEditorSearchSection
key=
{
item
.
file_path
}
data=
{
item
}
onItemClick=
{
handleResultItemClick
}
/>)
}
</
Accordion
>
</
Box
>
</
Accordion
Root
>
</>
);
};
...
...
ui/shared/monaco/CodeEditorSearchSection.tsx
View file @
27872bfe
import
{
AccordionButton
,
AccordionItem
,
AccordionPanel
,
Box
}
from
'
@chakra-ui/react
'
;
import
{
Box
}
from
'
@chakra-ui/react
'
;
import
React
from
'
react
'
;
import
type
{
SearchResult
}
from
'
./types
'
;
import
{
AccordionItem
,
AccordionItemContent
,
AccordionItemTrigger
}
from
'
toolkit/chakra/accordion
'
;
import
CodeEditorFileIcon
from
'
./CodeEditorFileIcon
'
;
import
CodeEditorSearchResultItem
from
'
./CodeEditorSearchResultItem
'
;
import
getFileName
from
'
./utils/getFileName
'
;
...
...
@@ -26,10 +28,8 @@ const CodeEditorSearchSection = ({ data, onItemClick }: Props) => {
const
themeColors
=
useThemeColors
();
return
(
<
AccordionItem
borderWidth=
"0px"
_last=
{
{
borderBottomWidth
:
'
0px
'
}
}
>
{
({
isExpanded
})
=>
(
<>
<
AccordionButton
<
AccordionItem
value=
{
data
.
file_path
}
borderWidth=
"0px"
_last=
{
{
borderBottomWidth
:
'
0px
'
}
}
>
<
AccordionItemTrigger
py=
{
0
}
px=
{
2
}
_hover=
{
{
bgColor
:
themeColors
[
'
custom.list.hoverBackground
'
]
}
}
...
...
@@ -37,10 +37,15 @@ const CodeEditorSearchSection = ({ data, onItemClick }: Props) => {
transitionDuration=
"0"
lineHeight=
"22px"
alignItems=
"center"
noIndicator
>
<
Box
className=
"codicon codicon-tree-item-expanded"
transform=
{
isExpanded
?
'
rotate(0deg)
'
:
'
rotate(-90deg)
'
}
transform=
"rotate(-90deg)"
_groupExpanded=
{
{
transform
:
'
rotate(0deg)
'
,
}
}
// transform={ isExpanded ? 'rotate(0deg)' : 'rotate(-90deg)' }
width=
"20px"
height=
"22px"
py=
"3px"
...
...
@@ -64,8 +69,8 @@ const CodeEditorSearchSection = ({ data, onItemClick }: Props) => {
>
{
data
.
matches
.
length
}
</
Box
>
</
AccordionButton
>
<
AccordionPanel
p=
{
0
}
>
</
AccordionItemTrigger
>
<
AccordionItemContent
p=
{
0
}
>
{
data
.
matches
.
map
((
match
)
=>
(
<
CodeEditorSearchResultItem
key=
{
data
.
file_path
+
'
_
'
+
match
.
startLineNumber
+
'
_
'
+
match
.
startColumn
}
...
...
@@ -75,9 +80,7 @@ const CodeEditorSearchSection = ({ data, onItemClick }: Props) => {
/>
),
)
}
</
AccordionPanel
>
</>
)
}
</
AccordionItemContent
>
</
AccordionItem
>
);
};
...
...
ui/shared/monaco/CodeEditorSideBar.tsx
View file @
27872bfe
import
type
{
HTMLChakraProps
}
from
'
@chakra-ui/react
'
;
import
{
Box
,
Tab
,
TabList
,
TabPanel
,
TabPanels
,
Tabs
,
useBoolean
}
from
'
@chakra-ui/react
'
;
import
{
Box
}
from
'
@chakra-ui/react
'
;
import
{
throttle
}
from
'
es-toolkit
'
;
import
type
*
as
monaco
from
'
monaco-editor/esm/vs/editor/editor.api
'
;
import
React
from
'
react
'
;
...
...
@@ -7,6 +6,8 @@ import React from 'react';
import
type
{
File
,
Monaco
}
from
'
./types
'
;
import
{
shift
,
cmd
}
from
'
lib/html-entities
'
;
import
type
{
TabsTriggerProps
}
from
'
toolkit/chakra/tabs
'
;
import
{
TabsContent
,
TabsList
,
TabsRoot
,
TabsTrigger
}
from
'
toolkit/chakra/tabs
'
;
import
CodeEditorFileExplorer
from
'
./CodeEditorFileExplorer
'
;
import
CodeEditorSearch
from
'
./CodeEditorSearch
'
;
...
...
@@ -26,14 +27,14 @@ export const CONTAINER_WIDTH = 250;
const
CodeEditorSideBar
=
({
onFileSelect
,
data
,
monaco
,
editor
,
selectedFile
,
mainFile
}:
Props
)
=>
{
const
[
isStuck
,
setIsStuck
]
=
React
.
useState
(
false
);
const
[
isDrawerOpen
,
setIsDrawerOpen
]
=
useBoolean
(
false
);
const
[
tabIndex
,
setTabIndex
]
=
React
.
useState
(
0
);
const
[
isDrawerOpen
,
setIsDrawerOpen
]
=
React
.
useState
(
false
);
const
[
activeTab
,
setActiveTab
]
=
React
.
useState
(
'
explorer
'
);
const
[
searchValue
,
setSearchValue
]
=
React
.
useState
(
''
);
const
[
actionBarRenderer
,
setActionBarRenderer
]
=
React
.
useState
<
()
=>
React
.
JSX
.
Element
>
();
const
themeColors
=
useThemeColors
();
const
tabProps
:
HTMLChakraProps
<
'
button
'
>
=
{
const
tabProps
:
Partial
<
TabsTriggerProps
>
=
{
fontFamily
:
'
heading
'
,
textTransform
:
'
uppercase
'
,
fontSize
:
'
11px
'
,
...
...
@@ -52,10 +53,18 @@ const CodeEditorSideBar = ({ onFileSelect, data, monaco, editor, selectedFile, m
},
100
));
const
handleFileSelect
=
React
.
useCallback
((
index
:
number
,
lineNumber
?:
number
)
=>
{
isDrawerOpen
&&
setIsDrawerOpen
.
off
(
);
isDrawerOpen
&&
setIsDrawerOpen
(
false
);
onFileSelect
(
index
,
lineNumber
);
},
[
isDrawerOpen
,
onFileSelect
,
setIsDrawerOpen
]);
const
handleSideBarButtonClick
=
React
.
useCallback
(()
=>
{
setIsDrawerOpen
((
prev
)
=>
!
prev
);
},
[]);
const
handleTabChange
=
React
.
useCallback
(({
value
}:
{
value
:
string
})
=>
{
setActiveTab
(
value
);
},
[]);
React
.
useEffect
(()
=>
{
if
(
editor
&&
monaco
)
{
editor
.
addAction
({
...
...
@@ -67,7 +76,7 @@ const CodeEditorSideBar = ({ onFileSelect, data, monaco, editor, selectedFile, m
contextMenuGroupId
:
'
navigation
'
,
contextMenuOrder
:
1.5
,
run
:
function
()
{
set
TabIndex
(
0
);
set
ActiveTab
(
'
explorer
'
);
},
});
...
...
@@ -80,7 +89,7 @@ const CodeEditorSideBar = ({ onFileSelect, data, monaco, editor, selectedFile, m
contextMenuGroupId
:
'
navigation
'
,
contextMenuOrder
:
1.6
,
run
:
function
(
editor
)
{
set
TabIndex
(
1
);
set
ActiveTab
(
'
search
'
);
const
selection
=
editor
.
getSelection
();
const
selectedValue
=
selection
?
editor
.
getModel
()?.
getValueInRange
(
selection
)
:
''
;
setSearchValue
(
selectedValue
||
''
);
...
...
@@ -111,8 +120,8 @@ const CodeEditorSideBar = ({ onFileSelect, data, monaco, editor, selectedFile, m
borderTopRightRadius=
"md"
borderBottomRightRadius=
"md"
>
<
Tabs
isLazy
lazyBehavior=
"keepMounted"
variant=
"unstyled"
size=
"13px"
index=
{
tabIndex
}
onChange=
{
setTabIndex
}
>
<
TabList
<
Tabs
Root
unmountOnExit=
{
false
}
variant=
"unstyled"
size=
"free"
value=
{
activeTab
}
onValueChange=
{
handleTabChange
}
>
<
Tab
s
List
columnGap=
{
3
}
position=
"sticky"
top=
{
0
}
...
...
@@ -120,37 +129,37 @@ const CodeEditorSideBar = ({ onFileSelect, data, monaco, editor, selectedFile, m
bgColor=
{
themeColors
[
'
sideBar.background
'
]
}
zIndex=
"1"
px=
{
2
}
h=
"35px"
alignItems=
"center"
boxShadow=
{
isStuck
?
'
md
'
:
'
none
'
}
borderTopRightRadius=
"md"
>
<
Tab
{
...
tabProps
}
title=
{
`File explorer (${ shift + cmd }E)`
}
>
Explorer
</
Tab
>
<
Tab
{
...
tabProps
}
title=
{
`Search in files (${ shift + cmd }F)`
}
>
Search
</
Tab
>
<
Tab
sTrigger
value=
"explorer"
{
...
tabProps
}
title=
{
`File explorer (${ shift + cmd }E)`
}
>
Explorer
</
TabsTrigger
>
<
Tab
sTrigger
value=
"search"
{
...
tabProps
}
title=
{
`Search in files (${ shift + cmd }F)`
}
>
Search
</
TabsTrigger
>
{
actionBarRenderer
?.()
}
</
TabList
>
<
TabPanels
>
<
TabPanel
p=
{
0
}
>
</
TabsList
>
<
TabsContent
value=
"explorer"
p=
{
0
}
>
<
CodeEditorFileExplorer
data=
{
data
}
onFileSelect=
{
handleFileSelect
}
selectedFile=
{
selectedFile
}
mainFile=
{
mainFile
}
isActive=
{
tabIndex
===
0
}
isActive=
{
activeTab
===
'
explorer
'
}
setActionBarRenderer=
{
setActionBarRenderer
}
/>
</
TabPanel
>
<
TabPanel
p=
{
0
}
>
</
TabsContent
>
<
TabsContent
value=
"search"
p=
{
0
}
>
<
CodeEditorSearch
data=
{
data
}
onFileSelect=
{
handleFileSelect
}
monaco=
{
monaco
}
isInputStuck=
{
isStuck
}
isActive=
{
tabIndex
===
1
}
isActive=
{
activeTab
===
'
search
'
}
setActionBarRenderer=
{
setActionBarRenderer
}
defaultValue=
{
searchValue
}
/>
</
TabPanel
>
</
TabPanels
>
</
Tabs
>
</
TabsContent
>
</
TabsRoot
>
</
Box
>
<
Box
boxSize=
"24px"
...
...
@@ -163,7 +172,7 @@ const CodeEditorSideBar = ({ onFileSelect, data, monaco, editor, selectedFile, m
borderTopLeftRadius=
"4px"
borderBottomLeftRadius=
"4px"
boxShadow=
"md"
onClick=
{
setIsDrawerOpen
.
toggle
}
onClick=
{
handleSideBarButtonClick
}
zIndex=
"1"
transitionProperty=
"right"
transitionDuration=
"normal"
...
...
ui/shared/monaco/CodeEditorTab.tsx
View file @
27872bfe
...
...
@@ -48,7 +48,7 @@ const CodeEditorTab = ({ isActive, isMainFile, path, onClick, onClose, isCloseDi
cursor=
"pointer"
onClick=
{
handleClick
}
_hover=
{
{
'
.codicon-close
'
:
{
'
&
.codicon-close
'
:
{
visibility
:
'
visible
'
,
},
}
}
...
...
ui/shared/monaco/utils/themes.ts
View file @
27872bfe
export
const
light
=
{
import
type
*
as
monaco
from
'
monaco-editor/esm/vs/editor/editor.api
'
;
export
const
light
:
monaco
.
editor
.
IStandaloneThemeData
=
{
base
:
'
vs
'
as
const
,
inherit
:
true
,
rules
:
[
...
...
@@ -44,7 +46,7 @@ export const light = {
}
as
const
,
};
export
const
dark
=
{
export
const
dark
:
monaco
.
editor
.
IStandaloneThemeData
=
{
base
:
'
vs-dark
'
as
const
,
inherit
:
true
,
rules
:
[
...
...
ui/shared/monaco/utils/useThemeColors.ts
View file @
27872bfe
import
{
useColorModeValue
}
from
'
@chakra-ui/react
'
;
import
{
useColorModeValue
}
from
'
toolkit/chakra/color-mode
'
;
import
*
as
themes
from
'
./themes
'
;
...
...
ui/showcases/Tabs.tsx
View file @
27872bfe
...
...
@@ -41,6 +41,18 @@ const TabsShowcase = () => {
<
TabsContent
value=
"tab2"
>
Second tab content
</
TabsContent
>
</
TabsRoot
>
</
Sample
>
<
Sample
label=
"variant: segmented"
>
<
TabsRoot
defaultValue=
"tab1"
variant=
"segmented"
size=
"sm"
>
<
TabsList
>
<
TabsTrigger
value=
"tab1"
>
First tab
</
TabsTrigger
>
<
TabsTrigger
value=
"tab2"
>
Second tab
</
TabsTrigger
>
<
TabsTrigger
value=
"tab3"
>
Third tab
</
TabsTrigger
>
</
TabsList
>
<
TabsContent
value=
"tab1"
>
First tab content
</
TabsContent
>
<
TabsContent
value=
"tab2"
>
Second tab content
</
TabsContent
>
<
TabsContent
value=
"tab3"
>
Third tab content
</
TabsContent
>
</
TabsRoot
>
</
Sample
>
</
SamplesStack
>
</
Section
>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment