Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
G
gate
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
openmole
gate
Commits
f8b0d4b5
Commit
f8b0d4b5
authored
May 21, 2015
by
Mathieu leclaire
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'form'
Conflicts: client/src/main/scala/fr/iscpif/client/FlowChart.scala
parents
15e1244c
aae3aec2
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
94 additions
and
108 deletions
+94
-108
Client.scala
client/src/main/scala/fr/iscpif/client/Client.scala
+1
-8
FlowChart.scala
client/src/main/scala/fr/iscpif/client/FlowChart.scala
+64
-64
JsRxTags.scala
client/src/main/scala/fr/iscpif/client/JsRxTags.scala
+1
-1
ScalaTraJSTagsWireRxBuild.scala
project/ScalaTraJSTagsWireRxBuild.scala
+21
-24
build.properties
project/build.properties
+1
-1
build.sbt
project/build.sbt
+6
-0
Servlet.scala
server/src/main/scala/fr/iscpif/app/Servlet.scala
+0
-4
Shared.scala
shared/Shared.scala
+0
-6
No files found.
client/src/main/scala/fr/iscpif/client/Client.scala
View file @
f8b0d4b5
package
client
import
java.util.UUID
import
org.scalajs.dom
import
org.scalajs.dom.HTMLElement
import
scala.concurrent.Future
import
scalatags.JsDom._
import
all._
...
...
@@ -11,13 +8,9 @@ import tags2.section
import
rx._
import
scala.scalajs.js.annotation.JSExport
import
scala.scalajs.concurrent.JSExecutionContext.Implicits.runNow
import
org.scalajs.dom.extensions.Ajax
import
scala.Some
import
shared._
import
upickle._
import
autowire._
import
JsRxTags._
@JSExport
object
Client
{
...
...
@@ -47,7 +40,7 @@ object Post extends autowire.Client[String, upickle.Reader, upickle.Writer] {
override
def
doCall
(
req
:
Request
)
:
Future
[
String
]
=
{
val
url
=
req
.
path
.
mkString
(
"/"
)
dom
.
ext
ensions
.
Ajax
.
post
(
dom
.
ext
.
Ajax
.
post
(
url
=
"http://localhost:8080/"
+
url
,
data
=
upickle
.
write
(
req
.
args
)
).
map
{
...
...
client/src/main/scala/fr/iscpif/client/FlowChart.scala
View file @
f8b0d4b5
package
client
/*
* Copyright (C) 2
1/08
/14 // mathieu.leclaire@openmole.org
* Copyright (C) 2
2/09
/14 // mathieu.leclaire@openmole.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
...
...
@@ -16,17 +16,16 @@ package client
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import
fr.iscpif.scaladget.d3mapping._
import
org.scalajs.dom
import
fr.iscpif.scaladget.mapping._
import
shared.Api
import
org.scalajs.dom
import
scala.scalajs.js
import
js.Dynamic.
{
literal
=>
lit
,
newInstance
=>
jsnew
}
import
js.Dynamic.
{
literal
⇒
lit
}
import
rx._
import
fr.iscpif.scaladget.d3._
import
scala.scalajs.concurrent.JSExecutionContext.Implicits.runNow
import
autowire._
import
js.JSConverters._
trait
GraphElement
<:
EventStates
{
def
literal
:
js.Dynamic
...
...
@@ -57,35 +56,34 @@ class Window(nodes: Array[Task] = Array(), edges: Array[Edge] = Array()) {
val
svg
=
d3
.
select
(
"body"
)
.
append
(
"svg"
)
.
attr
(
"id"
,
"workflow"
)
.
attr
(
"width"
,
"2500px"
)
.
attr
(
"height"
,
"2500px"
)
val
graph
=
new
GraphCreator
(
svg
,
nodes
,
edges
)
}
case
class
Consts
(
selectedClass
:
js.
String
=
"selected"
,
case
class
Consts
(
selectedClass
:
String
=
"selected"
,
circleGClass
:
String
=
"conceptG"
,
graphClass
:
js.String
=
"graph"
,
activeEditId
:
js.String
=
"active-editing"
,
DELETE_KEY
:
js.Number
=
46
,
nodeRadius
:
js.Number
=
50
)
graphClass
:
String
=
"graph"
,
activeEditId
:
String
=
"active-editing"
,
DELETE_KEY
:
Double
=
46
,
nodeRadius
:
Double
=
50
)
class
GraphCreator
(
svgSelection
:
Selection
,
_tasks
:
Array
[
Task
],
_edges
:
Array
[
Edge
])
{
implicit
def
dynamicToString
(
d
:
js.Dynamic
)
:
String
=
d
.
asInstanceOf
[
js.
String
]
implicit
def
dynamicToString
(
d
:
js.Dynamic
)
:
String
=
d
.
asInstanceOf
[
String
]
implicit
def
dynamicToBoolean
(
d
:
js.Dynamic
)
:
Boolean
=
d
.
asInstanceOf
[
js.
Boolean
]
implicit
def
dynamicToBoolean
(
d
:
js.Dynamic
)
:
Boolean
=
d
.
asInstanceOf
[
Boolean
]
// SVG DEFINITIONS //
val
consts
=
new
Consts
val
svgG
=
svgSelection
.
append
(
"g"
).
classed
(
consts
.
graphClass
,
true
)
val
dragLine
=
svgG
.
append
(
"svg:path"
)
.
attr
(
"class"
,
"link dragline hidden"
)
.
attr
(
"d"
,
"M0,0L0,0"
)
...
...
@@ -98,8 +96,8 @@ class GraphCreator(svgSelection: Selection, _tasks: Array[Task], _edges: Array[E
val
dragging
:
Var
[
Boolean
]
=
Var
(
false
)
svgSelection
.
on
(
"mousemove"
,
(
_:
js.Any
,
_:
js.Number
)
=>
mousemove
)
.
on
(
"mouseup.scene"
,
(
_:
js.Any
,
_:
js.Number
)
=>
mouseup
)
.
on
(
"mousemove"
,
(
_:
js.Any
,
_:
Double
)
⇒
mousemove
)
.
on
(
"mouseup.scene"
,
(
_:
js.Any
,
_:
Double
)
⇒
mouseup
)
// define arrow markers for graph links
defs
.
append
(
"svg:marker"
)
...
...
@@ -123,7 +121,6 @@ class GraphCreator(svgSelection: Selection, _tasks: Array[Task], _edges: Array[E
.
append
(
"svg:path"
)
.
attr
(
"d"
,
"M0,-5L10,0L0,5"
)
val
tasks
:
Var
[
Array
[
Var
[
Task
]]]
=
Var
(
Array
())
_tasks
.
map
{
addTask
...
...
@@ -134,25 +131,30 @@ class GraphCreator(svgSelection: Selection, _tasks: Array[Task], _edges: Array[E
addEdge
}
val
svgElement
=
js
.
Dynamic
.
global
.
document
.
getElementById
(
"workflow"
)
// GLOBAL EVENTS //
d3
.
select
(
dom
.
window
)
.
on
(
"keydown"
,
(
_:
js.Any
,
_:
js.Number
)
=>
{
d3
.
event
.
keyCode
match
{
case
consts
.
DELETE_KEY
=>
tasks
().
filter
(
t
=>
t
().
selected
()).
map
{
t
=>
removeTask
(
t
)
}
edges
().
filter
(
e
=>
e
().
selected
()).
map
{
e
=>
removeEdge
(
e
)
}
case
_
=>
}
})
.
on
(
"keydown"
,
(
_:
js.Any
,
_:
Double
)
⇒
{
d3
.
event
.
keyCode
match
{
case
consts
.
DELETE_KEY
⇒
tasks
().
filter
(
t
⇒
t
().
selected
()).
map
{
t
⇒
removeTask
(
t
)
}
edges
().
filter
(
e
⇒
e
().
selected
()).
map
{
e
⇒
removeEdge
(
e
)
}
case
_
⇒
}
})
def
mouseXY
=
d3
.
mouse
(
svgElement
)
def
mousemove
=
{
Seq
(
mouseDownTask
()).
flatten
.
map
{
t
=>
val
x
=
d3
.
event
.
clientX
val
y
=
d3
.
event
.
clientY
Seq
(
mouseDownTask
()).
flatten
.
map
{
t
⇒
val
xy
=
mouseXY
val
x
=
xy
(
0
)
val
y
=
xy
(
1
)
if
(
d3
.
event
.
shiftKey
)
{
dragging
()
=
true
dragLine
.
attr
(
"d"
,
"M"
+
t
.
location
().
_1
+
","
+
t
.
location
().
_2
+
"L"
+
x
+
","
+
y
)
...
...
@@ -165,8 +167,9 @@ class GraphCreator(svgSelection: Selection, _tasks: Array[Task], _edges: Array[E
def
mouseup
=
{
// Hide the drag line
val
xy
=
mouseXY
if
(
d3
.
event
.
shiftKey
&&
!
dragging
())
{
val
(
x
,
y
)
=
(
d3
.
event
.
clientX
,
d3
.
event
.
clientY
)
val
(
x
,
y
)
=
(
xy
(
0
),
xy
(
1
)
)
Post
[
Api
].
uuid
.
call
().
foreach
{
i
=>
addTask
(
i
,
i
,
x
,
y
)
...
...
@@ -179,15 +182,14 @@ class GraphCreator(svgSelection: Selection, _tasks: Array[Task], _edges: Array[E
.
style
(
"marker-end"
,
" "
)
}
// ADD, SELECT AND REMOVE ITEMS //
def
unselectTasks
=
tasks
().
foreach
{
t
=>
t
().
selected
()
=
false
}
def
unselectTasks
=
tasks
().
foreach
{
t
⇒
t
().
selected
()
=
false
}
def
unselectEdges
=
edges
().
foreach
{
e
=>
e
().
selected
()
=
false
}
def
unselectEdges
=
edges
().
foreach
{
e
⇒
e
().
selected
()
=
false
}
def
removeTask
(
t
:
Var
[
Task
])
=
{
tasks
()
=
tasks
()
diff
Array
(
t
)
edges
()
=
edges
().
filterNot
(
e
=>
e
().
source
()
==
t
()
||
e
().
target
()
==
t
())
edges
()
=
edges
().
filterNot
(
e
⇒
e
().
source
()
==
t
()
||
e
().
target
()
==
t
())
}
def
removeEdge
(
e
:
Var
[
Edge
])
=
{
...
...
@@ -200,7 +202,7 @@ class GraphCreator(svgSelection: Selection, _tasks: Array[Task], _edges: Array[E
tasks
()
=
tasks
()
:+
Var
(
task
)
Obs
(
tasks
)
{
val
mysel
=
circleRoot
.
selectAll
(
"g"
).
data
(
tasks
()
,
(
task
:
Var
[
Task
],
n
:
js.Number
)
=>
{
val
mysel
=
circleRoot
.
selectAll
(
"g"
).
data
(
tasks
()
.
toJSArray
,
(
task
:
Var
[
Task
],
n
:
Double
)
⇒
{
task
().
id
.
toString
})
...
...
@@ -209,17 +211,17 @@ class GraphCreator(svgSelection: Selection, _tasks: Array[Task], _edges: Array[E
Rx
{
newG
.
classed
(
consts
.
circleGClass
,
true
)
.
attr
(
"transform"
,
(
task
:
Var
[
Task
])
=>
{
val
loc
=
task
().
location
()
"translate("
+
loc
.
_1
+
","
+
loc
.
_2
+
")"
})
.
attr
(
"transform"
,
(
task
:
Var
[
Task
])
⇒
{
val
loc
=
task
().
location
()
"translate("
+
loc
.
_1
+
","
+
loc
.
_2
+
")"
})
newG
.
classed
(
consts
.
selectedClass
,
(
task
:
Var
[
Task
])
=>
{
newG
.
classed
(
consts
.
selectedClass
,
(
task
:
Var
[
Task
])
⇒
{
task
().
selected
()
})
}
newG
.
on
(
"mousedown"
,
(
t
:
Var
[
Task
],
n
:
js.Number
)
=>
{
newG
.
on
(
"mousedown"
,
(
t
:
Var
[
Task
],
n
:
Double
)
⇒
{
mouseDownTask
()
=
Some
(
t
())
d3
.
event
.
stopPropagation
...
...
@@ -228,7 +230,6 @@ class GraphCreator(svgSelection: Selection, _tasks: Array[Task], _edges: Array[E
unselectEdges
t
().
selected
()
=
!
t
().
selected
()
if
(
d3
.
event
.
shiftKey
)
{
val
x
=
t
().
location
().
_1
val
y
=
t
().
location
().
_2
...
...
@@ -238,27 +239,26 @@ class GraphCreator(svgSelection: Selection, _tasks: Array[Task], _edges: Array[E
.
attr
(
"d"
,
"M"
+
x
+
","
+
y
+
"L"
+
x
+
","
+
y
)
}
})
.
on
(
"mouseup.task"
,
(
t
:
Var
[
Task
],
n
:
js.Number
)
=>
{
Seq
(
mouseDownTask
()).
flatten
.
map
{
mdt
=>
if
(
t
()
!=
mdt
)
{
addEdge
(
mdt
,
t
())
.
on
(
"mouseup.task"
,
(
t
:
Var
[
Task
],
n
:
Double
)
⇒
{
Seq
(
mouseDownTask
()).
flatten
.
map
{
mdt
⇒
if
(
t
()
!=
mdt
)
{
addEdge
(
mdt
,
t
())
}
}
}
}
)
mysel
.
exit
().
remove
()
}
}
def
addEdge
(
source
:
Task
,
target
:
Task
)
:
Unit
=
addEdge
(
new
Edge
(
Var
(
source
),
Var
(
target
)))
def
addEdge
(
edge
:
Edge
)
:
Unit
=
{
edges
()
=
edges
()
:+
Var
(
edge
)
Obs
(
edges
)
{
val
mysel
=
pathRoot
.
selectAll
(
"path"
).
data
(
edges
()
,
(
edge
:
Var
[
Edge
],
n
:
js.Number
)
=>
{
val
mysel
=
pathRoot
.
selectAll
(
"path"
).
data
(
edges
()
.
toJSArray
,
(
edge
:
Var
[
Edge
],
n
:
Double
)
⇒
{
edge
().
source
().
id
+
"+"
+
edge
().
target
().
id
})
...
...
@@ -267,20 +267,20 @@ class GraphCreator(svgSelection: Selection, _tasks: Array[Task], _edges: Array[E
Rx
{
newPath
.
style
(
"marker-end"
,
"url(#end-arrow)"
)
.
classed
(
"link"
,
true
)
.
attr
(
"d"
,
(
edge
:
Var
[
Edge
],
n
:
js.Number
)
=>
{
val
source
=
edge
().
source
().
location
()
val
target
=
edge
().
target
().
location
()
"M"
+
source
.
_1
+
","
+
source
.
_2
+
"L"
+
target
.
_1
+
","
+
target
.
_2
})
.
attr
(
"d"
,
(
edge
:
Var
[
Edge
],
n
:
Double
)
⇒
{
val
source
=
edge
().
source
().
location
()
val
target
=
edge
().
target
().
location
()
"M"
+
source
.
_1
+
","
+
source
.
_2
+
"L"
+
target
.
_1
+
","
+
target
.
_2
})
// update existing paths
newPath
.
style
(
"marker-end"
,
"url(#end-arrow)"
)
.
classed
(
consts
.
selectedClass
,
(
edge
:
Var
[
Edge
],
n
:
Number
)
=>
{
edge
().
selected
()
}
.
classed
(
consts
.
selectedClass
,
(
edge
:
Var
[
Edge
],
n
:
Number
)
⇒
{
edge
().
selected
()
}
)
newPath
.
on
(
"mousedown"
,
(
edge
:
Var
[
Edge
],
n
:
js.Number
)
=>
{
newPath
.
on
(
"mousedown"
,
(
edge
:
Var
[
Edge
],
n
:
Double
)
⇒
{
unselectTasks
unselectEdges
edge
().
selected
()
=
!
edge
().
selected
()
...
...
@@ -291,4 +291,4 @@ class GraphCreator(svgSelection: Selection, _tasks: Array[Task], _edges: Array[E
mysel
.
exit
().
remove
()
}
}
}
\ No newline at end of file
}
client/src/main/scala/fr/iscpif/client/JsRxTags.scala
View file @
f8b0d4b5
...
...
@@ -47,7 +47,7 @@ object JsRxTags {
* the Obs onto the element itself so we have a reference to kill it when
* the element leaves the DOM (e.g. it gets deleted).
*/
implicit
def
rxMod
[
T
<:
dom.HTMLElement
](
r
:
Rx
[
HtmlTag
])
:
Modifier
=
{
implicit
def
rxMod
[
T
<:
dom.
raw.
HTMLElement
](
r
:
Rx
[
HtmlTag
])
:
Modifier
=
{
def
rSafe
=
r
.
toTry
match
{
case
Success
(
v
)
=>
v
.
render
case
Failure
(
e
)
=>
span
(
e
.
toString
,
backgroundColor
:=
"red"
).
render
...
...
project/ScalaTraJSTagsWireRxBuild.scala
View file @
f8b0d4b5
import
sbt._
import
Keys._
import
org.scalatra.sbt._
import
org.scalatra.sbt.PluginKeys._
import
fr.iscpif.jsmanager.JSManagerPlugin._
import
org.scalajs.sbtplugin.ScalaJSPlugin
import
org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._
object
ScalaTraJSTagsWireRxBuild
extends
Build
{
val
Organization
=
"fr.iscpif"
val
Name
=
"ScalaTraJSTagsWireRx"
val
Version
=
"0.1.0-SNAPSHOT"
val
ScalaVersion
=
"2.11.
2
"
val
ScalaVersion
=
"2.11.
6
"
val
ScalatraVersion
=
"2.3.0"
val
Resolvers
=
Seq
(
Resolver
.
sonatypeRepo
(
"snapshots"
),
"Typesafe repository"
at
"http://repo.typesafe.com/typesafe/releases/"
,
"bintray/non"
at
"http://dl.bintray.com/non/maven"
,
Resolver
.
url
(
"scala-js-releases"
,
url
(
"http://dl.bintray.com/content/scala-js/scala-js-releases"
))(
Resolver
.
ivyStylePatterns
))
"Typesafe repository"
at
"http://repo.typesafe.com/typesafe/releases/"
)
lazy
val
shared
=
project
.
in
(
file
(
"./shared"
)).
settings
(
scalaVersion
:=
ScalaVersion
)
.
settings
(
jsManagerSettings
:
_
*
)
)
lazy
val
client
=
Project
(
"client"
,
file
(
"./client"
),
settings
=
Defaults
.
defaultSettings
++
jsManagerSettings
++
Seq
(
settings
=
Seq
(
version
:=
Version
,
scalaVersion
:=
ScalaVersion
,
resolvers
++=
Resolvers
,
libraryDependencies
++=
Seq
(
"com.lihaoyi"
%%%
"autowire"
%
"0.2.3"
,
"com.lihaoyi"
%%%
"upickle"
%
"0.2.5"
,
"com.scalatags"
%%%
"scalatags"
%
"0.4.2"
,
"com.scalarx"
%%%
"scalarx"
%
"0.2.6"
,
"fr.iscpif"
%%%
"scaladget"
%
"0.1.0"
,
"org.scala-lang.modules.scalajs"
%%%
"scalajs-dom"
%
"0.6"
),
//jsCall := "Client().run();",
outputPath
:=
"server/src/main/webapp/"
"com.lihaoyi"
%%%
"autowire"
%
"0.2.5"
,
"com.lihaoyi"
%%%
"upickle"
%
"0.2.7"
,
"com.lihaoyi"
%%%
"scalatags"
%
"0.4.6"
,
"com.lihaoyi"
%%%
"scalarx"
%
"0.2.8"
,
"fr.iscpif"
%%%
"scaladget"
%
"0.5.0-SNAPSHOT"
,
"org.scala-js"
%%%
"scalajs-dom"
%
"0.8.0"
)
)
).
dependsOn
(
shared
)
).
dependsOn
(
shared
)
enablePlugins
(
ScalaJSPlugin
)
lazy
val
server
=
Project
(
"server"
,
file
(
"./server"
),
settings
=
Defaults
.
defaultSettings
++
ScalatraPlugin
.
scalatraWithJRebel
++
Seq
(
settings
=
ScalatraPlugin
.
scalatraWithJRebel
++
Seq
(
organization
:=
Organization
,
name
:=
Name
,
version
:=
Version
,
scalaVersion
:=
ScalaVersion
,
resolvers
++=
Resolvers
,
libraryDependencies
++=
Seq
(
"com.lihaoyi"
%%
"autowire"
%
"0.2.
3
"
,
"com.lihaoyi"
%%
"upickle"
%
"0.2.
5
"
,
"com.
scalatags"
%%
"scalatags"
%
"0.4.2
"
,
"com.lihaoyi"
%%
"autowire"
%
"0.2.
5
"
,
"com.lihaoyi"
%%
"upickle"
%
"0.2.
7
"
,
"com.
lihaoyi"
%%
"scalatags"
%
"0.4.6
"
,
"org.scalatra"
%%
"scalatra"
%
ScalatraVersion
,
"org.scalatra"
%%
"scalatra-specs2"
%
ScalatraVersion
%
"test"
,
"ch.qos.logback"
%
"logback-classic"
%
"1.0.12"
%
"runtime"
,
"org.eclipse.jetty"
%
"jetty-webapp"
%
"8.1.
8.v20121106
"
%
"container"
,
"org.eclipse.jetty"
%
"jetty-webapp"
%
"8.1.
17.v20150415
"
%
"container"
,
"org.eclipse.jetty.orbit"
%
"javax.servlet"
%
"3.0.0.v201112011016"
%
"container;provided;test"
/*artifacts (Artifact("javax.servlet", "jar", "jar"))*/
)
)
...
...
project/build.properties
View file @
f8b0d4b5
sbt.version
=
0.13.
5
sbt.version
=
0.13.
7
project/
plugins
.sbt
→
project/
build
.sbt
View file @
f8b0d4b5
resolvers
++=
Seq
(
Resolver
.
url
(
"scala-js-releases"
,
url
(
"http://dl.bintray.com/content/scala-js/scala-js-releases"
))(
Resolver
.
ivyStylePatterns
))
resolvers
+=
"Typesafe repository"
at
"http://repo.typesafe.com/typesafe/releases/"
addSbtPlugin
(
"
fr.iscpif"
%%
"jsmanager"
%
"0.6.0
"
)
addSbtPlugin
(
"
org.scala-js"
%
"sbt-scalajs"
%
"0.6.3
"
)
addSbtPlugin
(
"org.scalatra.sbt"
%
"scalatra-sbt"
%
"0.3.5"
)
server/src/main/scala/fr/iscpif/app/Servlet.scala
View file @
f8b0d4b5
package
fr.iscpif.app
import
java.util.UUID
import
org.scalatra._
import
scala.concurrent.ExecutionContext.Implicits.global
import
upickle._
...
...
@@ -19,8 +17,6 @@ object AutowireServer extends autowire.Server[String, upickle.Reader, upickle.Wr
}
object
ApiImpl
extends
Api
{
def
hello
(
a
:
Int
)
=
a
*
3
def
caseClass
=
MyCaseClass
(
"Hello !"
)
}
class
Servlet
extends
ScalatraServlet
{
...
...
shared/Shared.scala
View file @
f8b0d4b5
package
shared
import
scala.scalajs.js.annotation.JSExport
@JSExport
case
class
MyCaseClass
(
hello
:
String
)
trait
Api
{
def
hello
(
a
:
Int
)
:
Int
def
caseClass
()
:
MyCaseClass
def
uuid
()
:
String
=
java
.
util
.
UUID
.
randomUUID
.
toString
}
\ 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