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
ecb17934
Commit
ecb17934
authored
Mar 10, 2023
by
tom
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
file tree basic implementation
parent
38b620c6
Changes
10
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
331 additions
and
3 deletions
+331
-3
monaco.ts
lib/csp/policies/monaco.ts
+1
-0
ContractSourceCode.tsx
ui/address/contract/ContractSourceCode.tsx
+6
-3
CodeEditor.tsx
ui/shared/monaco/CodeEditor.tsx
+56
-0
CodeEditorFileExplorer.tsx
ui/shared/monaco/CodeEditorFileExplorer.tsx
+25
-0
CodeEditorFileTree.tsx
ui/shared/monaco/CodeEditorFileTree.tsx
+50
-0
types.ts
ui/shared/monaco/types.ts
+15
-0
composeFileTree.test.ts
ui/shared/monaco/utils/composeFileTree.test.ts
+101
-0
composeFileTree.ts
ui/shared/monaco/utils/composeFileTree.ts
+44
-0
sortFileTree.ts
ui/shared/monaco/utils/sortFileTree.ts
+14
-0
themes.ts
ui/shared/monaco/utils/themes.ts
+19
-0
No files found.
lib/csp/policies/monaco.ts
View file @
ecb17934
...
...
@@ -5,6 +5,7 @@ import { KEY_WORDS } from '../utils';
export
function
monaco
():
CspDev
.
DirectiveDescriptor
{
return
{
'
script-src
'
:
[
// todo_tom try to remove this
KEY_WORDS
.
BLOB
,
'
https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/min/vs/loader.js
'
,
'
https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/min/vs/editor/editor.main.js
'
,
...
...
ui/address/contract/ContractSourceCode.tsx
View file @
ecb17934
...
...
@@ -4,8 +4,8 @@ import React from 'react';
import
type
{
SmartContract
}
from
'
types/api/contract
'
;
import
CodeEditorMonaco
from
'
ui/shared/CodeEditorMonaco
'
;
import
LinkInternal
from
'
ui/shared/LinkInternal
'
;
import
CodeEditor
from
'
ui/shared/monaco/CodeEditor
'
;
interface
Props
{
data
:
string
;
...
...
@@ -36,7 +36,10 @@ const ContractSourceCode = ({ data, hasSol2Yml, address, isViper, filePath, addi
</
Tooltip
>
)
:
null
;
const
code
=
[
{
file_path
:
filePath
||
'
index.sol
'
,
source_code
:
data
},
...(
additionalSource
||
[])
];
const
editorData
=
React
.
useMemo
(()
=>
{
const
defaultName
=
isViper
?
'
index.vy
'
:
'
index.sol
'
;
return
[
{
file_path
:
filePath
||
defaultName
,
source_code
:
data
},
...(
additionalSource
||
[])
];
},
[
additionalSource
,
data
,
filePath
,
isViper
]);
return
(
<
section
>
...
...
@@ -44,7 +47,7 @@ const ContractSourceCode = ({ data, hasSol2Yml, address, isViper, filePath, addi
{
heading
}
{
diagramLink
}
</
Flex
>
<
CodeEditor
Monaco
data=
{
code
}
/>
<
CodeEditor
data=
{
editorData
}
/>
</
section
>
);
};
...
...
ui/shared/
CodeEditorMonaco
.tsx
→
ui/shared/
monaco/CodeEditor
.tsx
View file @
ecb17934
import
{
Box
,
useColorMode
}
from
'
@chakra-ui/react
'
;
import
Editor
from
'
@monaco-editor/react
'
;
import
{
Box
,
useColorMode
,
Flex
}
from
'
@chakra-ui/react
'
;
import
Monaco
Editor
from
'
@monaco-editor/react
'
;
import
type
*
as
monaco
from
'
monaco-editor/esm/vs/editor/editor.api
'
;
import
React
from
'
react
'
;
import
type
{
File
}
from
'
./types
'
;
import
CodeEditorFileExplorer
from
'
./CodeEditorFileExplorer
'
;
import
*
as
themes
from
'
./utils/themes
'
;
export
type
Monaco
=
typeof
monaco
;
interface
Props
{
data
:
Array
<
{
file_path
:
string
;
source_code
:
string
}
>
;
data
:
Array
<
File
>
;
}
const
CodeEditor
Monaco
=
({
data
}:
Props
)
=>
{
const
CodeEditor
=
({
data
}:
Props
)
=>
{
const
instance
=
React
.
useRef
<
Monaco
>
();
const
[
index
]
=
React
.
useState
(
0
);
const
{
colorMode
}
=
useColorMode
();
...
...
@@ -20,40 +27,30 @@ const CodeEditorMonaco = ({ data }: Props) => {
const
handleEditorDidMount
=
React
.
useCallback
((
editor
:
monaco
.
editor
.
IStandaloneCodeEditor
,
monaco
:
Monaco
)
=>
{
instance
.
current
=
monaco
;
monaco
.
editor
.
defineTheme
(
'
blockscout-light
'
,
{
base
:
'
vs
'
,
inherit
:
true
,
rules
:
[],
colors
:
{
'
editor.background
'
:
'
#f5f5f6
'
,
},
});
monaco
.
editor
.
defineTheme
(
'
blockscout-dark
'
,
{
base
:
'
vs-dark
'
,
inherit
:
true
,
rules
:
[],
colors
:
{
'
editor.background
'
:
'
#1a1b1b
'
,
},
});
monaco
.
editor
.
defineTheme
(
'
blockscout-light
'
,
themes
.
light
);
monaco
.
editor
.
defineTheme
(
'
blockscout-dark
'
,
themes
.
dark
);
monaco
.
editor
.
setTheme
(
colorMode
===
'
light
'
?
'
blockscout-light
'
:
'
blockscout-dark
'
);
// componentDidMount
// eslint-disable-next-line react-hooks/exhaustive-deps
},
[
]);
return
(
<
Box
overflow=
"hidden"
borderRadius=
"md"
>
<
Editor
height=
"500px"
language=
"sol"
defaultValue=
{
data
[
0
].
source_code
}
options=
{
{
readOnly
:
true
,
inlayHints
:
{
enabled
:
'
off
'
},
minimap
:
{
enabled
:
false
}
}
}
onMount=
{
handleEditorDidMount
}
/>
</
Box
>
<
Flex
overflow=
"hidden"
borderRadius=
"md"
>
<
Box
flexGrow=
{
1
}
>
<
MonacoEditor
height=
"500px"
language=
"sol"
path=
{
data
[
index
].
file_path
}
defaultValue=
{
data
[
index
].
source_code
}
options=
{
{
readOnly
:
true
,
inlayHints
:
{
enabled
:
'
off
'
},
minimap
:
{
enabled
:
false
}
}
}
onMount=
{
handleEditorDidMount
}
/>
</
Box
>
<
Box
w=
"250px"
flexShrink=
{
0
}
bgColor=
"lightpink"
fontSize=
"sm"
>
<
CodeEditorFileExplorer
data=
{
data
}
/>
</
Box
>
</
Flex
>
);
};
export
default
CodeEditorMonaco
;
export
default
React
.
memo
(
CodeEditor
)
;
ui/shared/monaco/CodeEditorFileExplorer.tsx
0 → 100644
View file @
ecb17934
import
{
Box
}
from
'
@chakra-ui/react
'
;
import
React
from
'
react
'
;
import
type
{
File
}
from
'
./types
'
;
import
CodeEditorFileTree
from
'
./CodeEditorFileTree
'
;
import
composeFileTree
from
'
./utils/composeFileTree
'
;
interface
Props
{
data
:
Array
<
File
>
;
}
const
CodeEditorFileExplorer
=
({
data
}:
Props
)
=>
{
const
tree
=
React
.
useMemo
(()
=>
{
return
composeFileTree
(
data
);
},
[
data
]);
return
(
<
Box
>
<
CodeEditorFileTree
tree=
{
tree
}
/>
</
Box
>
);
};
export
default
React
.
memo
(
CodeEditorFileExplorer
);
ui/shared/monaco/CodeEditorFileTree.tsx
0 → 100644
View file @
ecb17934
import
type
{
ChakraProps
}
from
'
@chakra-ui/react
'
;
import
{
Accordion
,
AccordionButton
,
AccordionIcon
,
AccordionItem
,
AccordionPanel
}
from
'
@chakra-ui/react
'
;
import
React
from
'
react
'
;
import
type
{
FileTree
}
from
'
./types
'
;
interface
Props
{
tree
:
FileTree
;
level
?:
number
;
}
const
CodeEditorFileTree
=
({
tree
,
level
=
0
}:
Props
)
=>
{
const
itemProps
:
ChakraProps
=
{
ml
:
level
?
4
:
0
,
borderWidth
:
'
0px
'
,
_last
:
{
borderBottomWidth
:
'
0px
'
,
},
cursor
:
'
pointer
'
,
};
return
(
<
Accordion
allowMultiple
>
{
tree
.
map
((
leaf
,
index
)
=>
{
if
(
'
children
'
in
leaf
)
{
return
(
<
AccordionItem
key=
{
index
}
{
...
itemProps
}
>
<
AccordionButton
p=
{
0
}
_hover=
{
{
bgColor
:
'
inherit
'
}
}
fontSize=
"sm"
>
<
AccordionIcon
/>
<
span
>
{
leaf
.
name
}
</
span
>
</
AccordionButton
>
<
AccordionPanel
p=
{
0
}
>
<
CodeEditorFileTree
tree=
{
leaf
.
children
}
level=
{
level
+
1
}
/>
</
AccordionPanel
>
</
AccordionItem
>
);
}
return
(
<
AccordionItem
key=
{
index
}
{
...
itemProps
}
>
{
leaf
.
name
}
</
AccordionItem
>
);
})
}
</
Accordion
>
);
};
export
default
React
.
memo
(
CodeEditorFileTree
);
ui/shared/monaco/types.ts
0 → 100644
View file @
ecb17934
export
interface
File
{
file_path
:
string
;
source_code
:
string
;
}
export
interface
FileTreeFile
extends
File
{
name
:
string
;
}
export
interface
FileTreeFolder
{
name
:
string
;
children
:
Array
<
FileTreeFile
|
FileTreeFolder
>
;
}
export
type
FileTree
=
Array
<
FileTreeFile
|
FileTreeFolder
>
;
ui/shared/monaco/utils/composeFileTree.test.ts
0 → 100644
View file @
ecb17934
import
composeFileTree
from
'
./composeFileTree
'
;
const
files
=
[
{
file_path
:
'
index.sol
'
,
source_code
:
'
zero
'
,
},
{
file_path
:
'
contracts/Zeta.eth.sol
'
,
source_code
:
'
one
'
,
},
{
file_path
:
'
/_openzeppelin/contracts/utils/Context.sol
'
,
source_code
:
'
two
'
,
},
{
file_path
:
'
/_openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol
'
,
source_code
:
'
three
'
,
},
{
file_path
:
'
/_openzeppelin/contracts/token/ERC20/IERC20.sol
'
,
source_code
:
'
four
'
,
},
{
file_path
:
'
/_openzeppelin/contracts/token/ERC20/ERC20.sol
'
,
source_code
:
'
five
'
,
},
];
test
(
'
builds correct file tree
'
,
()
=>
{
const
result
=
composeFileTree
(
files
);
expect
(
result
).
toMatchInlineSnapshot
(
`
[
{
"children": [
{
"children": [
{
"children": [
{
"children": [
{
"children": [
{
"file_path": "/_openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol",
"name": "IERC20Metadata.sol",
"source_code": "three",
},
],
"name": "extensions",
},
{
"file_path": "/_openzeppelin/contracts/token/ERC20/ERC20.sol",
"name": "ERC20.sol",
"source_code": "five",
},
{
"file_path": "/_openzeppelin/contracts/token/ERC20/IERC20.sol",
"name": "IERC20.sol",
"source_code": "four",
},
],
"name": "ERC20",
},
],
"name": "token",
},
{
"children": [
{
"file_path": "/_openzeppelin/contracts/utils/Context.sol",
"name": "Context.sol",
"source_code": "two",
},
],
"name": "utils",
},
],
"name": "contracts",
},
],
"name": "_openzeppelin",
},
{
"children": [
{
"file_path": "contracts/Zeta.eth.sol",
"name": "Zeta.eth.sol",
"source_code": "one",
},
],
"name": "contracts",
},
{
"file_path": "index.sol",
"name": "index.sol",
"source_code": "zero",
},
]
`
);
});
ui/shared/monaco/utils/composeFileTree.ts
0 → 100644
View file @
ecb17934
import
type
{
File
,
FileTree
}
from
'
../types
'
;
import
sortFileTree
from
'
./sortFileTree
'
;
const
stripLeadingSlash
=
(
str
:
string
)
=>
{
if
(
str
[
0
]
===
'
.
'
&&
str
[
1
]
===
'
/
'
)
{
return
str
.
slice
(
2
);
}
if
(
str
[
0
]
===
'
/
'
)
{
return
str
.
slice
(
1
);
}
return
str
;
};
export
default
function
composeFileTree
(
files
:
Array
<
File
>
)
{
const
result
:
FileTree
=
[];
type
Level
=
{
result
:
FileTree
;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}
&
Record
<
string
,
any
>
;
const
level
:
Level
=
{
result
};
files
.
forEach
((
file
)
=>
{
const
path
=
stripLeadingSlash
(
file
.
file_path
);
const
segments
=
path
.
split
(
'
/
'
);
segments
.
reduce
((
acc
,
segment
,
currentIndex
,
array
)
=>
{
if
(
!
acc
[
segment
])
{
acc
[
segment
]
=
{
result
:
[]
};
acc
.
result
.
push
({
name
:
segment
,
...(
currentIndex
===
array
.
length
-
1
?
file
:
{
children
:
acc
[
segment
].
result
}),
});
}
acc
.
result
.
sort
(
sortFileTree
);
return
acc
[
segment
];
},
level
);
});
return
result
.
sort
(
sortFileTree
);
}
ui/shared/monaco/utils/sortFileTree.ts
0 → 100644
View file @
ecb17934
import
type
{
FileTree
}
from
'
../types
'
;
import
type
ArrayElement
from
'
types/utils/ArrayElement
'
;
export
default
function
sortFileTree
(
a
:
ArrayElement
<
FileTree
>
,
b
:
ArrayElement
<
FileTree
>
)
{
if
(
'
children
'
in
a
&&
!
(
'
children
'
in
b
))
{
return
-
1
;
}
if
(
'
children
'
in
b
&&
!
(
'
children
'
in
a
))
{
return
1
;
}
return
a
.
name
.
localeCompare
(
b
.
name
);
}
ui/shared/monaco/utils/themes.ts
0 → 100644
View file @
ecb17934
import
type
*
as
monaco
from
'
monaco-editor/esm/vs/editor/editor.api
'
;
export
const
light
:
monaco
.
editor
.
IStandaloneThemeData
=
{
base
:
'
vs
'
,
inherit
:
true
,
rules
:
[],
colors
:
{
'
editor.background
'
:
'
#f5f5f6
'
,
},
};
export
const
dark
:
monaco
.
editor
.
IStandaloneThemeData
=
{
base
:
'
vs-dark
'
,
inherit
:
true
,
rules
:
[],
colors
:
{
'
editor.background
'
:
'
#1a1b1b
'
,
},
};
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