Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Z
zeroing-editor
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
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
劳工
zeroing-editor
Commits
aa38e101
Commit
aa38e101
authored
Jun 17, 2020
by
rockyl
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
修复百分比属性的问题
parent
b01812b1
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
170 additions
and
136 deletions
+170
-136
index.html
public/index.html
+2
-1
NumberInput.vue
src/components/inputs/NumberInput.vue
+32
-28
zh-CN.json
src/locales/zh-CN.json
+6
-2
project.js
src/store/modules/project.js
+35
-42
index.js
src/utils/index.js
+17
-0
Views.vue
src/views/Editor/Views.vue
+73
-60
RuntimeLayer.vue
src/views/Editor/components/RuntimeLayer.vue
+5
-3
No files found.
public/index.html
View file @
aa38e101
...
...
@@ -8,7 +8,8 @@
<title>
烽火台
</title>
<script
src=
"//yun.duiba.com.cn/js-libs/psd.js/3.2.0/psd.min.js"
></script>
<script
src=
"//yun.duiba.com.cn/editor/zeroing/libs/engine.fbc60c6d3cb30e5ab97e82d392d9efeee91b8581.js"
></script>
<!-- <script src="http://0.0.0.0:4002/debug/engine.js"></script>-->
<script
src=
"//yun.duiba.com.cn/editor/zeroing/libs/engine.cfb1f45ea0206b8e50e55213f624e81d67432f81.js"
></script>
<script
src=
"//yun.duiba.com.cn/editor/zeroing/libs/svga.469e6ae1d98c9017953cf9375559c2575b293b59.js"
></script>
</head>
<body>
...
...
src/components/inputs/NumberInput.vue
View file @
aa38e101
...
...
@@ -3,7 +3,8 @@
<el-input-number
:disabled=
"!editable"
:value=
"editValue"
@
change=
"onInput"
controls-position=
"right"
:placeholder=
"defaultValue"
>
</el-input-number>
<span
@
click=
"onClickPercent"
v-if=
"config && config.percent"
style=
"position: absolute; right: 42px; cursor: pointer"
<span
@
click=
"onClickPercent"
v-if=
"config && config.percent"
style=
"position: absolute; right: 42px; cursor: pointer"
:class=
"
{'percent-on': isPercent, 'percent-off': !isPercent}">%
</span>
</div>
</
template
>
...
...
@@ -20,7 +21,7 @@
data
()
{
return
{
isPercent
:
false
,
editValue
:
null
editValue
:
null
}
},
computed
:
{
...
...
@@ -41,32 +42,32 @@
return
getInputDefaultValue
(
this
.
property
);
},
},
mounted
(){
mounted
()
{
this
.
updateValue
()
},
methods
:
{
updateValue
()
{
if
(
this
.
container
.
hasOwnProperty
(
'_'
+
this
.
propertyName
))
{
if
(
this
.
container
[
'_'
+
this
.
propertyName
])
{
if
(
this
.
container
.
hasOwnProperty
(
'_'
+
this
.
propertyName
))
{
if
(
this
.
container
[
'_'
+
this
.
propertyName
])
{
this
.
editValue
=
this
.
container
[
'_'
+
this
.
propertyName
];
}
else
{
}
else
{
this
.
editValue
=
undefined
;
}
}
else
if
(
this
.
propertyName
.
indexOf
(
"_"
)
!=-
1
)
{
if
(
this
.
container
[
this
.
propertyName
])
{
}
else
if
(
this
.
propertyName
.
indexOf
(
"_"
)
!=
-
1
)
{
if
(
this
.
container
[
this
.
propertyName
])
{
this
.
editValue
=
this
.
container
[
this
.
propertyName
]
}
else
{
}
else
{
this
.
editValue
=
undefined
;
}
}
else
{
}
else
{
let
v
=
this
.
value
;
if
(
v
===
undefined
)
{
this
.
isPercent
=
false
;
this
.
editValue
=
this
.
property
.
default
?
this
.
property
.
default
:
undefined
;
this
.
editValue
=
this
.
property
.
default
?
this
.
property
.
default
:
undefined
;
}
else
{
this
.
isPercent
=
typeof
v
===
'string'
;
if
(
typeof
v
===
'string'
)
{
if
(
typeof
v
===
'string'
)
{
v
=
parseInt
(
v
);
}
this
.
editValue
=
v
;
...
...
@@ -75,24 +76,27 @@
}
},
onInput
(
v
)
{
if
(
v
!==
this
.
value
)
{
this
.
$emit
(
'input'
,
v
,
this
.
container
,
this
.
propertyName
,
this
.
value
);
}
//if (v !== this.value) {
let
value
;
if
(
this
.
isPercent
)
{
if
(
!
v
&&
v
!==
0
)
{
value
=
undefined
;
}
else
{
value
=
v
+
'%'
;
}
}
else
{
value
=
parseInt
(
v
);
}
this
.
$emit
(
'input'
,
value
,
this
.
container
,
this
.
propertyName
,
this
.
value
);
//}
},
onClickPercent
()
{
if
(
this
.
isPercent
){
//设置非百分比
this
.
onInput
(
parseInt
(
this
.
value
))
}
else
{
//设置成百分比
if
(
!
this
.
value
&&
this
.
value
!==
0
){
this
.
onInput
(
undefined
)
}
else
{
this
.
onInput
(
this
.
value
+
'%'
)
}
}
this
.
isPercent
=
!
this
.
isPercent
;
this
.
onInput
(
this
.
value
);
},
},
watch
:{
value
(
v
){
watch
:
{
value
(
v
)
{
this
.
updateValue
()
}
}
...
...
@@ -101,4 +105,4 @@
<
style
scoped
>
</
style
>
\ No newline at end of file
</
style
>
src/locales/zh-CN.json
View file @
aa38e101
...
...
@@ -194,8 +194,6 @@
"Save this behavior before"
:
"是否先保存这个行为?"
,
"Failed to import view"
:
"导入视图失败"
,
"Failed to upload file"
:
"上传文件失败"
,
"Single view"
:
"单视图"
,
"Multi views"
:
"多视图"
,
"Import single"
:
"导入单"
,
"Import multi"
:
"导入多"
,
"Import view success"
:
"视图导入成功"
,
...
...
@@ -463,5 +461,11 @@
"top"
:
"顶部裁剪"
,
"center"
:
"上下裁剪"
,
"bottom"
:
"底部裁剪"
},
"importViewMenu"
:
{
"psd_single"
:
"单视图PSD"
,
"psd_multi"
:
"多视图PSD"
,
"view_single"
:
"单视图文件"
,
"view_multi"
:
"多视图文件"
}
}
src/store/modules/project.js
View file @
aa38e101
...
...
@@ -14,7 +14,7 @@ import {
findProcess
,
getMockServeEnabled
,
saveAs
,
traverseViewNode
traverseViewNode
,
zipViewFile
}
from
"../../utils"
import
{
template
}
from
"../../template"
import
{
packAssetsGroups
}
from
"../../utils/assets-pack"
...
...
@@ -28,6 +28,7 @@ import {arrayFind} from "element-ui/src/utils/util";
import
{
uploadFiles
}
from
"./editor"
;
import
events
from
"@/global-events.js"
import
zlib
from
'zlib'
;
const
storeName
=
'project'
;
const
psStoreName
=
'pack-history'
;
...
...
@@ -58,7 +59,7 @@ const defaultOptions = {
const
OPERATE_MAX_LENGTH
=
200
;
// 撤销重做栈最大值
let
nodeUUidCatch
=
null
;
let
nodeUUidCatch
=
null
;
export
const
projectStore
=
{
...
...
@@ -451,6 +452,7 @@ export const projectStore = {
}
}
}
events
.
$emit
(
"viewReset"
)
},
...
...
@@ -779,14 +781,14 @@ export const projectStore = {
commit
(
'updateProjectUpdateTime'
,
{
time
:
state
.
base_time
});
}
getters
.
project
.
data
=
getters
.
project
.
data
.
replace
(
/editScroll/g
,
"scroll"
)
getters
.
project
.
data
=
getters
.
project
.
data
.
replace
(
/editScroll/g
,
"scroll"
)
let
project
=
Object
.
assign
({},
getters
.
project
);
if
(
data
)
{
project
.
data
=
data
;
}
console
.
log
(
"data"
,
getters
.
project
,
project
.
data
)
console
.
log
(
"data"
,
getters
.
project
,
project
.
data
)
let
resp
=
await
projectApi
.
saveOne
(
project
,
remark
);
if
(
resp
.
result
)
{
...
...
@@ -811,11 +813,11 @@ export const projectStore = {
*/
activeComponent
({
state
,
commit
},
data
)
{
// debugger;
console
.
log
(
"resetScrollType"
,
data
)
console
.
log
(
"resetScrollType"
,
data
)
resetScrollType
(
data
.
data
)
let
getTopView
=
node
=>
{
// console.log("node",node)
// console.log("node",node)
if
(
node
.
parent
&&
!
node
.
parent
.
parent
)
{
return
node
;
}
else
{
...
...
@@ -862,10 +864,10 @@ export const projectStore = {
});
//console.log("props",commit,state,props,state.activeComponent)
if
(
nodeUUidCatch
==
state
.
activeComponent
.
uuid
)
{
// events.$emit('canvasKeyVupdate', {props},"update");
if
(
nodeUUidCatch
==
state
.
activeComponent
.
uuid
)
{
// events.$emit('canvasKeyVupdate', {props},"update");
}
nodeUUidCatch
=
state
.
activeComponent
.
uuid
nodeUUidCatch
=
state
.
activeComponent
.
uuid
//console.log("nodeUUidCatch",nodeUUidCatch)
/*if (hasAssetsDep) {
...
...
@@ -931,38 +933,29 @@ export const projectStore = {
scripts
:
_scripts
})
},
async
importPsd
({
commit
},
{
file
,
action
,
mode
})
{
const
result
=
await
toZeroing
(
file
,
{
mode
});
async
importView
({
dispatch
},
{
file
,
action
,
mode
})
{
let
result
;
if
(
action
===
1
||
action
===
2
)
{
//普通视图
result
=
await
zipViewFile
(
file
);
}
else
{
//psd
result
=
await
toZeroing
(
file
,
{
mode
});
}
let
viewFile
=
new
File
([
result
],
'view.json'
);
await
dispatch
(
'uploadView'
,
{
originFile
:
file
,
viewFile
,
action
})
},
async
uploadView
({
commit
},
{
originFile
,
viewFile
,
action
})
{
//console.log(viewFile);
const
{
view
,
assets
}
=
await
editorApi
.
uploadView
(
viewFile
);
switch
(
action
)
{
case
0
:
//单视图
view
.
name
=
file
.
name
.
substring
(
0
,
file
.
name
.
lastIndexOf
(
'.'
));
commit
(
'importView'
,
view
);
break
;
case
1
:
//多视图
for
(
let
subView
of
view
.
children
)
{
commit
(
'importView'
,
subView
)
}
break
if
(
action
%
2
===
1
)
{
//视图文件
view
.
name
=
originFile
.
name
.
substring
(
0
,
originFile
.
name
.
lastIndexOf
(
'.'
));
commit
(
'importView'
,
view
);
}
else
{
//多视图PSD
for
(
let
subView
of
view
.
children
)
{
commit
(
'importView'
,
subView
)
}
}
commit
(
'importAssets'
,
assets
)
},
async
importView
({
commit
},
{
file
,
action
})
{
const
{
view
,
assets
}
=
await
editorApi
.
importView
(
file
);
switch
(
action
)
{
case
0
:
//单视图
view
.
name
=
file
.
name
.
substring
(
0
,
file
.
name
.
lastIndexOf
(
'.'
));
commit
(
'importView'
,
view
);
break
;
case
1
:
//多视图
for
(
let
subView
of
view
.
children
)
{
commit
(
'importView'
,
subView
);
}
break
;
}
commit
(
'importAssets'
,
assets
);
},
exportView
({
state
},
view
)
{
let
zip
=
new
JSZip
();
zip
.
file
(
'view.json'
,
JSON
.
stringify
(
view
));
...
...
@@ -1064,14 +1057,14 @@ function setUUIDForAllChildren(node) {
//重置编辑视图数据为实际视图数据
function
resetScrollType
(
data
)
{
if
(
data
.
type
==
"editScrollList"
)
{
data
.
type
=
"scrollList"
if
(
data
.
type
==
"editScrollList"
)
{
data
.
type
=
"scrollList"
}
if
(
data
.
type
==
"editScrollView"
)
{
data
.
type
=
"scrollView"
if
(
data
.
type
==
"editScrollView"
)
{
data
.
type
=
"scrollView"
}
if
(
data
.
children
)
{
for
(
let
itme
of
data
.
children
)
{
if
(
data
.
children
)
{
for
(
let
itme
of
data
.
children
)
{
resetScrollType
(
itme
)
}
}
...
...
src/utils/index.js
View file @
aa38e101
...
...
@@ -6,6 +6,7 @@ import {Message, Loading} from "element-ui";
import
i18n
from
'../i18n'
import
generateUUID
from
"uuid/v4"
;
import
moment
from
"moment"
;
import
zlib
from
"zlib"
;
/**
* 动态数据图标映射
...
...
@@ -484,3 +485,19 @@ export function dataURLtoBlob(dataUrl) {
}
return
new
Blob
([
u8arr
],
{
type
:
mime
});
}
export
async
function
zipViewFile
(
file
){
let
dataString
=
await
readTextFile
(
file
);
let
buf
=
new
Buffer
(
dataString
);
return
await
new
Promise
((
resolve
,
reject
)
=>
{
zlib
.
gzip
(
buf
,
function
(
err
,
res
)
{
if
(
err
)
{
reject
(
err
);
}
else
{
console
.
log
(
res
.
length
);
resolve
(
res
);
}
})
})
}
src/views/Editor/Views.vue
View file @
aa38e101
...
...
@@ -7,8 +7,7 @@
<el-dropdown
trigger=
"hover"
placement=
"top"
size=
"mini"
@
command=
"onImportCommand"
>
<el-link
icon=
"el-icon-arrow-down"
:underline=
"false"
></el-link>
<el-dropdown-menu
slot=
"dropdown"
>
<el-dropdown-item
command=
"single"
>
{{
$t
(
'Single view'
)
}}
</el-dropdown-item>
<el-dropdown-item
command=
"multi"
>
{{
$t
(
'Multi views'
)
}}
</el-dropdown-item>
<el-dropdown-item
v-for=
"(item, key) in importViewMenu"
:key=
"key"
:command=
"key"
>
{{
item
}}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
...
...
@@ -42,8 +41,9 @@
@
node-drop=
"onNodeDrop"
>
<div
slot-scope=
"
{ node, data }" class="tree-node">
<div
class=
"node-name"
>
<i
v-show=
"Object.keys(iconMap).includes(data.type)"
:class=
"iconMap[data.type]"
style=
"color:#9b82e3"
></i>
<div
class=
"node-name"
>
<i
v-show=
"Object.keys(iconMap).includes(data.type)"
:class=
"iconMap[data.type]"
style=
"color:#9b82e3"
></i>
{{
data
.
name
}}
</div>
...
...
@@ -91,11 +91,12 @@
label
:
'name'
},
expandedKeys
:
[],
nodeFilterPresets
:
this
.
$t
(
'nodeFilterPresets'
),
iconMap
:
{
image
:
'el-icon-picture'
,
label
:
'el-icon-edit-outline'
}
nodeFilterPresets
:
this
.
$t
(
'nodeFilterPresets'
),
importViewMenu
:
this
.
$t
(
'importViewMenu'
),
iconMap
:
{
image
:
'el-icon-picture'
,
label
:
'el-icon-edit-outline'
}
};
},
mounted
()
{
...
...
@@ -128,27 +129,27 @@
},
//重置编辑视图数据为实际视图数据
resetScrollType
(
data
)
{
if
(
data
.
type
==
"editScrollList"
)
{
data
.
type
=
"scrollList"
if
(
data
.
type
==
"editScrollList"
)
{
data
.
type
=
"scrollList"
}
if
(
data
.
type
==
"editScrollView"
)
{
data
.
type
=
"scrollView"
if
(
data
.
type
==
"editScrollView"
)
{
data
.
type
=
"scrollView"
}
if
(
data
.
children
)
{
for
(
let
itme
of
data
.
children
)
{
if
(
data
.
children
)
{
for
(
let
itme
of
data
.
children
)
{
this
.
resetScrollType
(
itme
)
}
}
//console.log("1122334")
},
locateViewNodeActive
(
node
)
{
if
(
this
.
$refs
.
tree
)
{
if
(
this
.
$refs
.
tree
)
{
//console.log("选中")
this
.
$refs
.
tree
.
setCurrentKey
(
node
.
uuid
);
this
.
expandedKeys
=
[
node
.
uuid
];
let
data
=
this
.
$refs
.
tree
.
getCurrentNode
()
let
nodeDom
=
this
.
$refs
.
tree
.
getNode
(
data
)
let
data
=
this
.
$refs
.
tree
.
getCurrentNode
()
let
nodeDom
=
this
.
$refs
.
tree
.
getNode
(
data
)
//this.resetScrollType(this.activeComponent)
this
.
resetScrollType
(
data
)
this
.
resetScrollType
(
nodeDom
.
data
)
...
...
@@ -162,23 +163,23 @@
// nodeDom.data.type="scrollView"
// }
//console.log(data,nodeDom)
this
.
$store
.
dispatch
(
'activeComponent'
,
{
data
,
node
:
nodeDom
});
this
.
$store
.
dispatch
(
'activeComponent'
,
{
data
,
node
:
nodeDom
});
let
styleCatch
=
{
x
:
node
.
x
,
y
:
node
.
y
,
anchorX
:
node
.
anchorX
,
anchorY
:
node
.
anchorY
,
scaleX
:
node
.
scaleX
,
scaleY
:
node
.
scaleY
,
let
styleCatch
=
{
x
:
node
.
x
,
y
:
node
.
y
,
anchorX
:
node
.
anchorX
,
anchorY
:
node
.
anchorY
,
scaleX
:
node
.
scaleX
,
scaleY
:
node
.
scaleY
,
// imageWidth:parseInt(node.imageWidth),
// imageHeight:parseInt(node.imageHeight),
width
:
node
.
_width
,
_width
:
node
.
_width
,
_height
:
node
.
_height
,
height
:
node
.
_height
,
width
:
node
.
_width
,
_width
:
node
.
_width
,
_height
:
node
.
_height
,
height
:
node
.
_height
,
}
this
.
$store
.
dispatch
(
"modifyProperties"
,
styleCatch
);
this
.
$store
.
dispatch
(
"modifyProperties"
,
styleCatch
);
}
},
updateFilter
()
{
...
...
@@ -261,28 +262,28 @@
},
allowDrag
(
draggingNode
)
{
//return draggingNode.parent.parent;
return
true
;
},
allowDrop
(
draggingNode
,
dropNode
,
type
)
{
//
if
(
!
draggingNode
.
parent
.
parent
)
{
//根视图拖动
if
(
!
draggingNode
.
parent
.
parent
)
{
//根视图拖动
return
!
dropNode
.
parent
.
parent
&&
type
!==
'inner'
;
}
else
{
}
else
{
return
dropNode
.
parent
.
parent
||
type
===
'inner'
;
}
},
onNodeDrop
(){
onNodeDrop
()
{
this
.
makeProjectDirty
();
//setTimeout(()=>{
events
.
$emit
(
"canvasViewUpdate"
)
events
.
$emit
(
"canvasViewUpdate"
)
//},200)
},
/**
* 点击左侧视图列表
*/
handleNodeClick
(
data
,
node
)
{
this
.
resetScrollType
(
data
)
this
.
resetScrollType
(
node
.
data
)
...
...
@@ -290,13 +291,13 @@
// console.log(this.getCurrentPath(node))
this
.
$store
.
dispatch
(
'activeComponent'
,
{
data
,
node
});
events
.
$emit
(
'canvasActiveNodeByTree'
,
this
.
getCurrentPath
(
node
));
events
.
$emit
(
'canvasActiveNodeByTree'
,
this
.
getCurrentPath
(
node
));
},
/**
* 获取树的路径,返回格式:0/0/1
*/
getCurrentPath
(
node
)
{
getCurrentPath
(
node
)
{
let
currentPath
if
(
node
&&
node
.
data
)
{
let
nodeParent
=
node
.
parent
...
...
@@ -306,11 +307,11 @@
nodeParent
=
nodeParent
.
parent
}
}
let
path
=
[]
for
(
let
item
of
currentPath
)
{
if
(
item
.
parent
)
{
for
(
let
cNode
in
item
.
parent
.
childNodes
)
{
if
(
item
.
parent
.
childNodes
[
cNode
]
==
item
)
{
let
path
=
[]
for
(
let
item
of
currentPath
)
{
if
(
item
.
parent
)
{
for
(
let
cNode
in
item
.
parent
.
childNodes
)
{
if
(
item
.
parent
.
childNodes
[
cNode
]
==
item
)
{
path
.
push
(
cNode
)
}
}
...
...
@@ -340,7 +341,7 @@
selectFile
(
async
files
=>
{
events
.
$emit
(
'upload-indicator'
,
true
);
try
{
await
this
.
import
Psd
({
await
this
.
import
View
({
file
:
files
[
0
],
action
,
mode
,
...
...
@@ -351,18 +352,30 @@
})
},
onImportCommand
(
importType
)
{
this
.
$refs
.
selectDesignModeDialog
.
show
(
importType
);
},
onSelectDesignMode
(
mode
,
importType
){
let
action
;
let
action
,
needSelectDesign
=
true
;
switch
(
importType
)
{
case
'single'
:
action
=
0
;
break
;
case
'multi'
:
case
'view_single'
:
action
=
1
;
needSelectDesign
=
false
;
break
;
case
'view_multi'
:
action
=
2
;
needSelectDesign
=
false
;
break
;
case
'psd_single'
:
action
=
3
;
break
;
case
'psd_multi'
:
action
=
4
;
break
;
}
if
(
needSelectDesign
){
this
.
$refs
.
selectDesignModeDialog
.
show
(
action
);
}
else
{
this
.
toImport
(
action
);
}
},
onSelectDesignMode
(
mode
,
action
)
{
this
.
toImport
(
action
,
mode
);
},
onMoreMenu
(
command
,
data
,
node
)
{
...
...
@@ -395,20 +408,20 @@
this
.
copyNode
({
node
:
data
,
parentNode
:
node
.
parent
.
data
,
copyState
:
1
copyState
:
1
});
},
()
=>
{
//copyState:1复制行为,2不复制行为
this
.
copyNode
({
node
:
data
,
parentNode
:
node
.
parent
.
data
,
copyState
:
2
copyState
:
2
});
}
)
break
;
case
'paste'
:
this
.
pasteNode
({
...
...
@@ -440,7 +453,7 @@
}
},
...
mapMutations
([
'copyNode'
,
'pasteNode'
,
'deleteNode'
,
'addNode'
,
'makeProjectDirty'
]),
...
mapActions
([
'exportView'
,
'importView'
,
'importPsd'
])
...
mapActions
([
'exportView'
,
'importView'
])
}
};
</
script
>
...
...
@@ -458,4 +471,4 @@
.more-button
{
margin
:
0
3px
;
}
</
style
>
\ No newline at end of file
</
style
>
src/views/Editor/components/RuntimeLayer.vue
View file @
aa38e101
...
...
@@ -69,7 +69,9 @@
},
resize
()
{
this
.
$nextTick
(()
=>
{
engine
.
editorStage
.
resizeStage
();
if
(
engine
.
editorStage
){
engine
.
editorStage
.
resizeStage
();
}
});
},
showView
(
viewConfig
)
{
...
...
@@ -95,7 +97,7 @@
let
view
=
this
.
getNode
(
''
,
true
);
return
node
.
getIndexPath
(
view
);
},
}
}
</
script
>
...
...
@@ -107,4 +109,4 @@
width
:
100%
;
height
:
100%
;
}
</
style
>
\ No newline at end of file
</
style
>
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