Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
B
baas-ide
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
JIRA
JIRA
Merge Requests
1
Merge Requests
1
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
guxukai
baas-ide
Commits
a45f79b2
Commit
a45f79b2
authored
Oct 28, 2020
by
ioedeveloper
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Load more
parent
7d558863
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
62 additions
and
32 deletions
+62
-32
ArrayType.js
libs/remix-debug/src/solidity-decoder/types/ArrayType.js
+2
-2
.eslintrc
libs/remix-ui/debugger-ui/.eslintrc
+1
-0
debugger-ui.tsx
libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx
+2
-1
dropdown-panel.tsx
...mix-ui/debugger-ui/src/lib/vm-debugger/dropdown-panel.tsx
+4
-3
solidity-locals.tsx
...ix-ui/debugger-ui/src/lib/vm-debugger/solidity-locals.tsx
+30
-4
dropdown-panel.css
...debugger-ui/src/lib/vm-debugger/styles/dropdown-panel.css
+3
-0
vm-debugger-head.tsx
...x-ui/debugger-ui/src/lib/vm-debugger/vm-debugger-head.tsx
+7
-17
index.ts
libs/remix-ui/debugger-ui/src/types/index.ts
+6
-2
solidityTypeFormatter.ts
libs/remix-ui/debugger-ui/src/utils/solidityTypeFormatter.ts
+2
-0
tree-view-item.tsx
...ix-ui/tree-view/src/lib/tree-view-item/tree-view-item.tsx
+2
-2
index.ts
libs/remix-ui/tree-view/src/types/index.ts
+3
-1
No files found.
libs/remix-debug/src/solidity-decoder/types/ArrayType.js
View file @
a45f79b2
...
@@ -83,13 +83,13 @@ class ArrayType extends RefType {
...
@@ -83,13 +83,13 @@ class ArrayType extends RefType {
if
(
isNaN
(
length
))
{
if
(
isNaN
(
length
))
{
return
{
return
{
value
:
'<decoding failed - length is NaN>'
,
value
:
'<decoding failed - length is NaN>'
,
type
:
this
.
typeName
type
:
'Error'
}
}
}
}
if
(
!
skip
)
skip
=
0
if
(
!
skip
)
skip
=
0
if
(
skip
)
offset
=
offset
+
(
32
*
skip
)
if
(
skip
)
offset
=
offset
+
(
32
*
skip
)
let
limit
=
length
-
skip
let
limit
=
length
-
skip
if
(
limit
>
10
0
)
limit
=
10
0
if
(
limit
>
10
)
limit
=
1
0
for
(
var
k
=
0
;
k
<
limit
;
k
++
)
{
for
(
var
k
=
0
;
k
<
limit
;
k
++
)
{
var
contentOffset
=
offset
var
contentOffset
=
offset
ret
.
push
(
this
.
underlyingType
.
decodeFromMemory
(
contentOffset
,
memory
))
ret
.
push
(
this
.
underlyingType
.
decodeFromMemory
(
contentOffset
,
memory
))
...
...
libs/remix-ui/debugger-ui/.eslintrc
View file @
a45f79b2
{
{
"rules": {
"rules": {
"@typescript-eslint/ban-types": "off",
"no-case-declarations": "off",
"no-case-declarations": "off",
"array-callback-return": "warn",
"array-callback-return": "warn",
"dot-location": ["warn", "property"],
"dot-location": ["warn", "property"],
...
...
libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx
View file @
a45f79b2
...
@@ -248,7 +248,8 @@ const stepManager = {
...
@@ -248,7 +248,8 @@ const stepManager = {
registerEvent
:
state
.
debugger
&&
state
.
debugger
.
step_manager
?
state
.
debugger
.
step_manager
.
event
.
register
.
bind
(
state
.
debugger
.
step_manager
.
event
)
:
null
,
registerEvent
:
state
.
debugger
&&
state
.
debugger
.
step_manager
?
state
.
debugger
.
step_manager
.
event
.
register
.
bind
(
state
.
debugger
.
step_manager
.
event
)
:
null
,
}
}
const
vmDebugger
=
{
const
vmDebugger
=
{
registerEvent
:
state
.
debugger
&&
state
.
debugger
.
vmDebuggerLogic
?
state
.
debugger
.
vmDebuggerLogic
.
event
.
register
.
bind
(
state
.
debugger
.
vmDebuggerLogic
.
event
)
:
null
registerEvent
:
state
.
debugger
&&
state
.
debugger
.
vmDebuggerLogic
?
state
.
debugger
.
vmDebuggerLogic
.
event
.
register
.
bind
(
state
.
debugger
.
vmDebuggerLogic
.
event
)
:
null
,
triggerEvent
:
state
.
debugger
&&
state
.
debugger
.
vmDebuggerLogic
?
state
.
debugger
.
vmDebuggerLogic
.
event
.
trigger
.
bind
(
state
.
debugger
.
vmDebuggerLogic
.
event
)
:
null
}
}
return
(
return
(
...
...
libs/remix-ui/debugger-ui/src/lib/vm-debugger/dropdown-panel.tsx
View file @
a45f79b2
...
@@ -6,7 +6,7 @@ import { default as deepequal } from 'deep-equal'
...
@@ -6,7 +6,7 @@ import { default as deepequal } from 'deep-equal'
import
'./styles/dropdown-panel.css'
import
'./styles/dropdown-panel.css'
export
const
DropdownPanel
=
(
props
:
DropdownPanelProps
)
=>
{
export
const
DropdownPanel
=
(
props
:
DropdownPanelProps
)
=>
{
const
{
dropdownName
,
dropdownMessage
,
calldata
,
header
,
loading
,
extractFunc
,
formatSelfFunc
}
=
props
const
{
dropdownName
,
dropdownMessage
,
calldata
,
header
,
loading
,
extractFunc
,
formatSelfFunc
,
loadMore
}
=
props
const
extractDataDefault
:
ExtractFunc
=
(
item
,
parent
?)
=>
{
const
extractDataDefault
:
ExtractFunc
=
(
item
,
parent
?)
=>
{
const
ret
:
ExtractData
=
{}
const
ret
:
ExtractData
=
{}
...
@@ -157,14 +157,15 @@ export const DropdownPanel = (props: DropdownPanelProps) => {
...
@@ -157,14 +157,15 @@ export const DropdownPanel = (props: DropdownPanelProps) => {
if
(
children
&&
children
.
length
>
0
)
{
if
(
children
&&
children
.
length
>
0
)
{
return
(
return
(
<
TreeViewItem
id=
{
`treeViewItem${key}`
}
key=
{
keyPath
}
label=
{
formatSelfFunc
?
formatSelfFunc
(
key
,
data
)
:
formatSelfDefault
(
key
,
data
)
}
handle
Click=
{
()
=>
handleExpand
(
keyPath
)
}
expand=
{
state
.
expandPath
.
includes
(
keyPath
)
}
>
<
TreeViewItem
id=
{
`treeViewItem${key}`
}
key=
{
keyPath
}
label=
{
formatSelfFunc
?
formatSelfFunc
(
key
,
data
)
:
formatSelfDefault
(
key
,
data
)
}
on
Click=
{
()
=>
handleExpand
(
keyPath
)
}
expand=
{
state
.
expandPath
.
includes
(
keyPath
)
}
>
<
TreeView
id=
{
`treeView${key}`
}
key=
{
keyPath
}
>
<
TreeView
id=
{
`treeView${key}`
}
key=
{
keyPath
}
>
{
children
}
{
children
}
{
data
.
hasNext
&&
<
TreeViewItem
id=
{
`treeViewLoadMore`
}
className=
"cursor_pointer"
label=
"Load more"
onClick=
{
()
=>
{
loadMore
(
data
.
cursor
)
}
}
/>
}
</
TreeView
>
</
TreeView
>
</
TreeViewItem
>
</
TreeViewItem
>
)
)
}
else
{
}
else
{
return
<
TreeViewItem
id=
{
key
.
toString
()
}
key=
{
keyPath
}
label=
{
formatSelfFunc
?
formatSelfFunc
(
key
,
data
)
:
formatSelfDefault
(
key
,
data
)
}
handle
Click=
{
()
=>
handleExpand
(
keyPath
)
}
expand=
{
state
.
expandPath
.
includes
(
keyPath
)
}
/>
return
<
TreeViewItem
id=
{
key
.
toString
()
}
key=
{
keyPath
}
label=
{
formatSelfFunc
?
formatSelfFunc
(
key
,
data
)
:
formatSelfDefault
(
key
,
data
)
}
on
Click=
{
()
=>
handleExpand
(
keyPath
)
}
expand=
{
state
.
expandPath
.
includes
(
keyPath
)
}
/>
}
}
}
}
...
...
libs/remix-ui/debugger-ui/src/lib/vm-debugger/solidity-locals.tsx
View file @
a45f79b2
...
@@ -2,15 +2,18 @@ import React, { useState, useEffect } from 'react'
...
@@ -2,15 +2,18 @@ import React, { useState, useEffect } from 'react'
import
DropdownPanel
from
'./dropdown-panel'
import
DropdownPanel
from
'./dropdown-panel'
import
{
extractData
}
from
'../../utils/solidityTypeFormatter'
import
{
extractData
}
from
'../../utils/solidityTypeFormatter'
import
{
ExtractData
}
from
'../../types'
import
{
ExtractData
}
from
'../../types'
import
{
default
as
deepequal
}
from
'deep-equal'
export
const
SolidityLocals
=
({
data
,
message
})
=>
{
export
const
SolidityLocals
=
({
data
,
updatedData
,
message
,
triggerEvent
})
=>
{
const
[
calldata
,
setCalldata
]
=
useState
(
null
)
const
[
calldata
,
setCalldata
]
=
useState
(
null
)
useEffect
(()
=>
{
useEffect
(()
=>
{
if
(
!
deepequal
(
calldata
,
data
))
setCalldata
(
data
)
data
&&
setCalldata
(
data
)
},
[
data
])
},
[
data
])
useEffect
(()
=>
{
updatedData
&&
mergeLocals
(
updatedData
,
calldata
)
},
[
updatedData
])
const
formatSelf
=
(
key
:
string
,
data
:
ExtractData
)
=>
{
const
formatSelf
=
(
key
:
string
,
data
:
ExtractData
)
=>
{
let
color
=
'var(--primary)'
let
color
=
'var(--primary)'
if
(
data
.
isArray
||
data
.
isStruct
||
data
.
isMapping
)
{
if
(
data
.
isArray
||
data
.
isStruct
||
data
.
isMapping
)
{
...
@@ -43,9 +46,32 @@ export const SolidityLocals = ({ data, message }) => {
...
@@ -43,9 +46,32 @@ export const SolidityLocals = ({ data, message }) => {
)
)
}
}
const
mergeLocals
=
(
locals1
,
locals2
)
=>
{
Object
.
keys
(
locals2
).
map
(
item
=>
{
if
(
locals2
[
item
].
cursor
&&
(
parseInt
(
locals2
[
item
].
cursor
)
<
parseInt
(
locals1
[
item
].
cursor
)))
{
locals2
[
item
]
=
{
...
locals1
[
item
],
value
:
[...
locals2
[
item
].
value
,
...
locals1
[
item
].
value
]
}
}
})
setCalldata
(()
=>
locals2
)
}
const
loadMore
=
(
cursor
)
=>
{
triggerEvent
(
'solidityLocalsLoadMore'
,
[
cursor
])
}
return
(
return
(
<
div
id=
'soliditylocals'
data
-
id=
"solidityLocals"
>
<
div
id=
'soliditylocals'
data
-
id=
"solidityLocals"
>
<
DropdownPanel
dropdownName=
'Solidity Locals'
calldata=
{
calldata
||
{}
}
extractFunc=
{
extractData
}
formatSelfFunc=
{
formatSelf
}
/>
<
DropdownPanel
dropdownName=
'Solidity Locals'
dropdownMessage=
{
message
}
calldata=
{
calldata
||
{}
}
extractFunc=
{
extractData
}
formatSelfFunc=
{
formatSelf
}
loadMore=
{
loadMore
}
/>
</
div
>
</
div
>
)
)
}
}
...
...
libs/remix-ui/debugger-ui/src/lib/vm-debugger/styles/dropdown-panel.css
View file @
a45f79b2
...
@@ -33,6 +33,9 @@
...
@@ -33,6 +33,9 @@
margin-top
:
4px
;
margin-top
:
4px
;
animation
:
spin
2s
linear
infinite
;
animation
:
spin
2s
linear
infinite
;
}
}
.cursor_pointer
{
cursor
:
pointer
;
}
@-moz-keyframes
spin
{
@-moz-keyframes
spin
{
to
{
-moz-transform
:
rotate
(
359deg
);
}
to
{
-moz-transform
:
rotate
(
359deg
);
}
}
}
...
...
libs/remix-ui/debugger-ui/src/lib/vm-debugger/vm-debugger-head.tsx
View file @
a45f79b2
...
@@ -5,12 +5,7 @@ import StepDetail from './step-detail'
...
@@ -5,12 +5,7 @@ import StepDetail from './step-detail'
import
SolidityState
from
'./solidity-state'
import
SolidityState
from
'./solidity-state'
import
SolidityLocals
from
'./solidity-locals'
import
SolidityLocals
from
'./solidity-locals'
export
const
VmDebuggerHead
=
({
vmDebugger
:
{
registerEvent
}
})
=>
{
export
const
VmDebuggerHead
=
({
vmDebugger
:
{
registerEvent
,
triggerEvent
}
})
=>
{
const
[
asm
,
setAsm
]
=
useState
({
code
:
null
,
address
:
null
,
index
:
null
})
const
[
functionPanel
,
setFunctionPanel
]
=
useState
(
null
)
const
[
functionPanel
,
setFunctionPanel
]
=
useState
(
null
)
const
[
stepDetail
,
setStepDetail
]
=
useState
({
const
[
stepDetail
,
setStepDetail
]
=
useState
({
'vm trace step'
:
'-'
,
'vm trace step'
:
'-'
,
...
@@ -28,18 +23,9 @@ export const VmDebuggerHead = ({ vmDebugger: { registerEvent } }) => {
...
@@ -28,18 +23,9 @@ export const VmDebuggerHead = ({ vmDebugger: { registerEvent } }) => {
calldata
:
null
,
calldata
:
null
,
message
:
null
,
message
:
null
,
})
})
const
[
updatedSolidityLocals
,
setUpdatedSolidityLocals
]
=
useState
(
null
)
useEffect
(()
=>
{
useEffect
(()
=>
{
registerEvent
&&
registerEvent
(
'codeManagerChanged'
,
(
code
,
address
,
index
)
=>
{
setAsm
(()
=>
{
return
{
code
,
address
,
index
}
})
})
registerEvent
&&
registerEvent
(
'traceUnloaded'
,
()
=>
{
setAsm
(()
=>
{
return
{
code
:
[],
address
:
''
,
index
:
-
1
}
})
})
registerEvent
&&
registerEvent
(
'functionsStackUpdate'
,
(
stack
)
=>
{
registerEvent
&&
registerEvent
(
'functionsStackUpdate'
,
(
stack
)
=>
{
if
(
stack
===
null
||
stack
.
length
===
0
)
return
if
(
stack
===
null
||
stack
.
length
===
0
)
return
const
functions
=
[]
const
functions
=
[]
...
@@ -100,6 +86,7 @@ export const VmDebuggerHead = ({ vmDebugger: { registerEvent } }) => {
...
@@ -100,6 +86,7 @@ export const VmDebuggerHead = ({ vmDebugger: { registerEvent } }) => {
})
})
})
})
registerEvent
&&
registerEvent
(
'solidityLocals'
,
(
calldata
)
=>
{
registerEvent
&&
registerEvent
(
'solidityLocals'
,
(
calldata
)
=>
{
console
.
log
(
'solidityLocals: '
,
calldata
)
setSolidityLocals
(()
=>
{
setSolidityLocals
(()
=>
{
return
{
...
solidityLocals
,
calldata
}
return
{
...
solidityLocals
,
calldata
}
})
})
...
@@ -109,6 +96,9 @@ export const VmDebuggerHead = ({ vmDebugger: { registerEvent } }) => {
...
@@ -109,6 +96,9 @@ export const VmDebuggerHead = ({ vmDebugger: { registerEvent } }) => {
return
{
...
solidityLocals
,
message
}
return
{
...
solidityLocals
,
message
}
})
})
})
})
registerEvent
&&
registerEvent
(
'solidityLocalsLoadMoreCompleted'
,
(
updatedCalldata
)
=>
{
setUpdatedSolidityLocals
(()
=>
updatedCalldata
)
})
},
[
registerEvent
])
},
[
registerEvent
])
return
(
return
(
...
@@ -116,7 +106,7 @@ export const VmDebuggerHead = ({ vmDebugger: { registerEvent } }) => {
...
@@ -116,7 +106,7 @@ export const VmDebuggerHead = ({ vmDebugger: { registerEvent } }) => {
<
div
className=
"d-flex flex-column"
>
<
div
className=
"d-flex flex-column"
>
<
div
className=
"w-100"
>
<
div
className=
"w-100"
>
<
FunctionPanel
data=
{
functionPanel
}
/>
<
FunctionPanel
data=
{
functionPanel
}
/>
<
SolidityLocals
data=
{
solidityLocals
.
calldata
}
message=
{
solidityLocals
.
message
}
/>
<
SolidityLocals
data=
{
solidityLocals
.
calldata
}
updatedData=
{
updatedSolidityLocals
}
message=
{
solidityLocals
.
message
}
triggerEvent=
{
triggerEvent
}
/>
<
SolidityState
calldata=
{
solidityState
.
calldata
}
message=
{
solidityState
.
message
}
/>
<
SolidityState
calldata=
{
solidityState
.
calldata
}
message=
{
solidityState
.
message
}
/>
</
div
>
</
div
>
<
div
className=
"w-100"
><
CodeListView
registerEvent=
{
registerEvent
}
/></
div
>
<
div
className=
"w-100"
><
CodeListView
registerEvent=
{
registerEvent
}
/></
div
>
...
...
libs/remix-ui/debugger-ui/src/types/index.ts
View file @
a45f79b2
...
@@ -7,7 +7,9 @@ export interface ExtractData {
...
@@ -7,7 +7,9 @@ export interface ExtractData {
isStruct
?:
boolean
,
isStruct
?:
boolean
,
isMapping
?:
boolean
,
isMapping
?:
boolean
,
type
?:
string
,
type
?:
string
,
isProperty
?:
boolean
isProperty
?:
boolean
,
hasNext
?:
boolean
,
cursor
?:
number
}
}
export
type
ExtractFunc
=
(
json
:
any
,
parent
?:
any
)
=>
ExtractData
export
type
ExtractFunc
=
(
json
:
any
,
parent
?:
any
)
=>
ExtractData
...
@@ -21,7 +23,8 @@ export interface DropdownPanelProps {
...
@@ -21,7 +23,8 @@ export interface DropdownPanelProps {
header
?:
string
,
header
?:
string
,
loading
?:
boolean
loading
?:
boolean
extractFunc
?:
ExtractFunc
,
extractFunc
?:
ExtractFunc
,
formatSelfFunc
?:
FormatSelfFunc
formatSelfFunc
?:
FormatSelfFunc
,
loadMore
?:
Function
}
}
export
type
FormatSelfFunc
=
(
key
:
string
|
number
,
data
:
ExtractData
)
=>
JSX
.
Element
export
type
FormatSelfFunc
=
(
key
:
string
|
number
,
data
:
ExtractData
)
=>
JSX
.
Element
\ No newline at end of file
libs/remix-ui/debugger-ui/src/utils/solidityTypeFormatter.ts
View file @
a45f79b2
...
@@ -21,6 +21,8 @@ export function extractData (item, parent): ExtractData {
...
@@ -21,6 +21,8 @@ export function extractData (item, parent): ExtractData {
})
})
ret
.
isArray
=
true
ret
.
isArray
=
true
ret
.
self
=
parent
.
isArray
?
''
:
item
.
type
ret
.
self
=
parent
.
isArray
?
''
:
item
.
type
ret
.
cursor
=
item
.
cursor
ret
.
hasNext
=
item
.
hasNext
}
else
if
(
item
.
type
.
indexOf
(
'struct'
)
===
0
)
{
}
else
if
(
item
.
type
.
indexOf
(
'struct'
)
===
0
)
{
ret
.
children
=
Object
.
keys
((
item
.
value
||
{})).
map
(
function
(
key
)
{
ret
.
children
=
Object
.
keys
((
item
.
value
||
{})).
map
(
function
(
key
)
{
return
{
key
:
key
,
value
:
item
.
value
[
key
]}
return
{
key
:
key
,
value
:
item
.
value
[
key
]}
...
...
libs/remix-ui/tree-view/src/lib/tree-view-item/tree-view-item.tsx
View file @
a45f79b2
...
@@ -4,7 +4,7 @@ import { TreeViewItemProps } from '../../types'
...
@@ -4,7 +4,7 @@ import { TreeViewItemProps } from '../../types'
import
'./tree-view-item.css'
import
'./tree-view-item.css'
export
const
TreeViewItem
=
(
props
:
TreeViewItemProps
)
=>
{
export
const
TreeViewItem
=
(
props
:
TreeViewItemProps
)
=>
{
const
{
id
,
children
,
label
,
expand
,
handleClick
,
...
otherProps
}
=
props
const
{
id
,
children
,
label
,
expand
,
...
otherProps
}
=
props
const
[
isExpanded
,
setIsExpanded
]
=
useState
(
false
)
const
[
isExpanded
,
setIsExpanded
]
=
useState
(
false
)
useEffect
(()
=>
{
useEffect
(()
=>
{
...
@@ -12,7 +12,7 @@ export const TreeViewItem = (props: TreeViewItemProps) => {
...
@@ -12,7 +12,7 @@ export const TreeViewItem = (props: TreeViewItemProps) => {
},
[
expand
])
},
[
expand
])
return
(
return
(
<
li
key=
{
`treeViewLi${id}`
}
data
-
id=
{
`treeViewLi${id}`
}
className=
'li_tv'
onClick=
{
handleClick
}
{
...
otherProps
}
>
<
li
key=
{
`treeViewLi${id}`
}
data
-
id=
{
`treeViewLi${id}`
}
className=
'li_tv'
{
...
otherProps
}
>
<
div
key=
{
`treeViewDiv${id}`
}
data
-
id=
{
`treeViewDiv${id}`
}
className=
'd-flex flex-row align-items-center'
onClick=
{
()
=>
setIsExpanded
(
!
isExpanded
)
}
>
<
div
key=
{
`treeViewDiv${id}`
}
data
-
id=
{
`treeViewDiv${id}`
}
className=
'd-flex flex-row align-items-center'
onClick=
{
()
=>
setIsExpanded
(
!
isExpanded
)
}
>
<
div
className=
{
isExpanded
?
'px-1 fas fa-caret-down caret caret_tv'
:
'px-1 fas fa-caret-right caret caret_tv'
}
style=
{
{
visibility
:
children
?
'visible'
:
'hidden'
}
}
></
div
>
<
div
className=
{
isExpanded
?
'px-1 fas fa-caret-down caret caret_tv'
:
'px-1 fas fa-caret-right caret caret_tv'
}
style=
{
{
visibility
:
children
?
'visible'
:
'hidden'
}
}
></
div
>
<
span
className=
'w-100 pl-1'
>
<
span
className=
'w-100 pl-1'
>
...
...
libs/remix-ui/tree-view/src/types/index.ts
View file @
a45f79b2
...
@@ -8,5 +8,6 @@ export interface TreeViewItemProps {
...
@@ -8,5 +8,6 @@ export interface TreeViewItemProps {
id
:
string
,
id
:
string
,
label
:
string
|
number
|
React
.
ReactNode
,
label
:
string
|
number
|
React
.
ReactNode
,
expand
?:
boolean
,
expand
?:
boolean
,
handleClick
?:
(
e
)
=>
void
onClick
?:
VoidFunction
,
className
?:
string
}
}
\ No newline at end of file
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