Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
Genesys PGR
Genesys Backend
Commits
fcf1e123
Commit
fcf1e123
authored
Nov 19, 2013
by
Matija Obreza
Browse files
Country and organization institute maps
parent
fbbb2d71
Changes
11
Hide whitespace changes
Inline
Side-by-side
src/main/java/org/genesys2/server/service/GeoService.java
View file @
fcf1e123
...
...
@@ -21,6 +21,9 @@ import java.util.List;
import
java.util.Locale
;
import
org.genesys2.server.model.impl.Country
;
import
org.genesys2.server.model.impl.FaoInstitute
;
import
com.fasterxml.jackson.databind.node.ArrayNode
;
public
interface
GeoService
{
...
...
@@ -70,4 +73,6 @@ public interface GeoService {
*/
Country
getCurrentCountry
(
String
code3
);
ArrayNode
toJson
(
List
<
FaoInstitute
>
members
);
}
src/main/java/org/genesys2/server/service/impl/GeoServiceImpl.java
View file @
fcf1e123
...
...
@@ -31,6 +31,7 @@ import org.genesys2.geo.sources.CountryInfo;
import
org.genesys2.geo.sources.DavrosCountrySource
;
import
org.genesys2.geo.sources.GeoNamesCountrySource
;
import
org.genesys2.server.model.impl.Country
;
import
org.genesys2.server.model.impl.FaoInstitute
;
import
org.genesys2.server.persistence.domain.CountryRepository
;
import
org.genesys2.server.service.ContentService
;
import
org.genesys2.server.service.GeoService
;
...
...
@@ -40,6 +41,10 @@ import org.springframework.security.access.prepost.PreAuthorize;
import
org.springframework.stereotype.Service
;
import
org.springframework.transaction.annotation.Transactional
;
import
com.fasterxml.jackson.databind.ObjectMapper
;
import
com.fasterxml.jackson.databind.node.ArrayNode
;
import
com.fasterxml.jackson.databind.node.ObjectNode
;
@Service
@Transactional
(
readOnly
=
true
)
public
class
GeoServiceImpl
implements
GeoService
{
...
...
@@ -287,4 +292,23 @@ public class GeoServiceImpl implements GeoService {
countryRepository
.
save
(
country
);
return
country
;
}
@Override
public
ArrayNode
toJson
(
List
<
FaoInstitute
>
members
)
{
// Generate JSON
ObjectMapper
mapper
=
new
ObjectMapper
();
ArrayNode
jsonArray
=
mapper
.
createArrayNode
();
for
(
FaoInstitute
inst
:
members
)
{
if
(
inst
.
getLatitude
()
!=
null
)
{
ObjectNode
instNode
=
mapper
.
createObjectNode
();
instNode
.
put
(
"lat"
,
inst
.
getLatitude
());
instNode
.
put
(
"lng"
,
inst
.
getLongitude
());
instNode
.
put
(
"alt"
,
inst
.
getAltitude
());
instNode
.
put
(
"title"
,
inst
.
getFullName
());
instNode
.
put
(
"code"
,
inst
.
getCode
());
jsonArray
.
add
(
instNode
);
}
}
return
jsonArray
;
}
}
src/main/java/org/genesys2/server/servlet/controller/CountryController.java
View file @
fcf1e123
...
...
@@ -17,8 +17,10 @@
package
org.genesys2.server.servlet.controller
;
import
java.util.HashMap
;
import
java.util.List
;
import
org.genesys2.server.model.impl.Country
;
import
org.genesys2.server.model.impl.FaoInstitute
;
import
org.genesys2.server.service.ContentService
;
import
org.genesys2.server.service.GenesysService
;
import
org.genesys2.server.service.GeoService
;
...
...
@@ -83,6 +85,18 @@ public class CountryController extends BaseController {
return
"/country/details"
;
}
@RequestMapping
(
"/{country}/map"
)
public
String
map
(
ModelMap
model
,
@PathVariable
(
value
=
"country"
)
String
countryStr
)
{
view
(
model
,
countryStr
);
@SuppressWarnings
(
"unchecked"
)
List
<
FaoInstitute
>
institutes
=
(
List
<
FaoInstitute
>)
model
.
get
(
"faoInstitutes"
);
model
.
addAttribute
(
"jsonInstitutes"
,
geoService
.
toJson
(
institutes
).
toString
());
return
"/country/map"
;
}
@PreAuthorize
(
"hasRole('ADMINISTRATOR')"
)
@RequestMapping
(
"/{country}/edit"
)
public
String
edit
(
ModelMap
model
,
@PathVariable
(
value
=
"country"
)
String
countryStr
)
{
...
...
src/main/java/org/genesys2/server/servlet/controller/OrganizationController.java
View file @
fcf1e123
...
...
@@ -27,6 +27,7 @@ import org.genesys2.server.model.impl.FaoInstitute;
import
org.genesys2.server.model.impl.Organization
;
import
org.genesys2.server.service.ContentService
;
import
org.genesys2.server.service.GenesysService
;
import
org.genesys2.server.service.GeoService
;
import
org.genesys2.server.service.OrganizationService
;
import
org.genesys2.server.service.TaxonomyService
;
import
org.genesys2.spring.ResourceNotFoundException
;
...
...
@@ -42,6 +43,10 @@ import org.springframework.web.bind.annotation.PathVariable;
import
org.springframework.web.bind.annotation.RequestMapping
;
import
org.springframework.web.bind.annotation.RequestParam
;
import
com.fasterxml.jackson.databind.ObjectMapper
;
import
com.fasterxml.jackson.databind.node.ArrayNode
;
import
com.fasterxml.jackson.databind.node.ObjectNode
;
@Controller
@Scope
(
"request"
)
@RequestMapping
(
"/org"
)
...
...
@@ -62,6 +67,10 @@ public class OrganizationController extends BaseController {
@Autowired
private
ContentService
contentService
;
@Autowired
private
GeoService
geoService
;
@RequestMapping
(
"/"
)
public
String
view
(
ModelMap
model
,
@RequestParam
(
value
=
"page"
,
required
=
false
,
defaultValue
=
"1"
)
int
page
)
{
model
.
addAttribute
(
"pagedData"
,
organizationService
.
list
(
new
PageRequest
(
page
-
1
,
50
,
new
Sort
(
"title"
))));
...
...
@@ -75,20 +84,20 @@ public class OrganizationController extends BaseController {
if
(
organization
==
null
)
{
throw
new
ResourceNotFoundException
();
}
List
<
FaoInstitute
>
members
=
organizationService
.
getMembers
(
organization
);
List
<
FaoInstitute
>
members
=
organizationService
.
getMembers
(
organization
);
_logger
.
debug
(
"Has: "
+
members
.
size
());
// Sort members by country
final
Locale
locale
=
getLocale
();
Collections
.
sort
(
members
,
new
Comparator
<
FaoInstitute
>()
{
@Override
public
int
compare
(
FaoInstitute
o1
,
FaoInstitute
o2
)
{
return
o1
.
getCountry
().
getName
(
locale
).
compareTo
(
o2
.
getCountry
().
getName
(
locale
));
}
});
model
.
addAttribute
(
"organization"
,
organization
);
model
.
addAttribute
(
"members"
,
members
);
...
...
@@ -110,6 +119,25 @@ public class OrganizationController extends BaseController {
return
"/organization/edit"
;
}
/**
* View map of member institutes
*
* @param model
* @param slug
* @return
*/
@RequestMapping
(
"/{slug}/map"
)
public
String
map
(
ModelMap
model
,
@PathVariable
(
value
=
"slug"
)
String
slug
)
{
view
(
model
,
slug
);
@SuppressWarnings
(
"unchecked"
)
List
<
FaoInstitute
>
members
=
(
List
<
FaoInstitute
>)
model
.
get
(
"members"
);
model
.
addAttribute
(
"jsonInstitutes"
,
geoService
.
toJson
(
members
).
toString
());
return
"/organization/map"
;
}
@PreAuthorize
(
"hasRole('ADMINISTRATOR')"
)
@RequestMapping
(
value
=
"/{slug}/update"
)
public
String
update
(
ModelMap
model
,
@PathVariable
(
value
=
"slug"
)
String
slug
,
@RequestParam
(
value
=
"slug"
)
String
newSlug
,
...
...
@@ -123,7 +151,7 @@ public class OrganizationController extends BaseController {
}
else
{
organization
=
organizationService
.
update
(
organization
.
getId
(),
newSlug
,
title
);
}
organizationService
.
updateBlurp
(
organization
,
blurp
,
getLocale
());
return
"redirect:/org/"
+
organization
.
getSlug
();
...
...
src/main/resources/content/language.properties
View file @
fcf1e123
...
...
@@ -286,3 +286,4 @@ oauth-client.active-tokens=List of tokens issued
maps.loading-map
=
Loading map...
maps.view-map
=
View map
src/main/webapp/WEB-INF/jsp/country/details.jsp
View file @
fcf1e123
...
...
@@ -21,7 +21,11 @@
</c:if>
</div>
</c:if>
<div
class=
"page-header"
>
<a
href=
"
<c:url
value=
"/geo/${country.code3.toLowerCase()}/map"
/>
"
><spring:message
code=
"maps.view-map"
/></a>
</div>
<div
class=
"jumbotron"
>
<spring:message
code=
"country.stat.countByOrigin"
arguments=
"
${
countByOrigin
}
"
/>
<c:if
test=
"
${
countByOrigin
gt
0
}
"
>
...
...
@@ -48,6 +52,7 @@
<a
target=
"_blank"
rel=
"nofollow"
href=
"
<c:out
value=
"
${
country
.
wikiLink
}
"
/>
"
><c:out
value=
"
${
country
.
wikiLink
}
"
/></a>
</div>
</c:if>
<%-- <h3>
<spring:message code="country.statistics" />
</h3>
...
...
src/main/webapp/WEB-INF/jsp/country/map.jsp
0 → 100644
View file @
fcf1e123
<!DOCTYPE html>
<%@include
file=
"/WEB-INF/jsp/init.jsp"
%>
<html>
<head>
<title><spring:message
code=
"country.page.profile.title"
arguments=
"
${
country
.
getName
(
pageContext
.
response
.
locale
)
}
"
argumentSeparator=
"|"
/></title>
</head>
<body>
<h1>
<c:out
value=
"
${
country
.
getName
(
pageContext
.
response
.
locale
)
}
"
/>
<img
class=
"country-flag bigger"
src=
"${cdnFlagsUrl}/${country.code3.toUpperCase()}.png"
/>
</h1>
<div
class=
"page-header"
>
<a
href=
"
<c:url
value=
"/geo/${country.code3.toLowerCase()}"
/>
"
><c:out
value=
"
${
country
.
getName
(
pageContext
.
response
.
locale
)
}
"
/></a>
</div>
<c:if
test=
"
${
jsonInstitutes
ne
null
}
"
>
<div
class=
"row"
style=
""
>
<div
class=
"col-sm-12"
>
<div
id=
"map"
class=
"gis-map gis-map-square"
><spring:message
code=
"maps.loading-map"
/></div>
</div>
</div>
<script
type=
"text/javascript"
>
jQuery
(
document
).
ready
(
function
()
{
GoogleMaps
.
map
(
"
${pageContext.response.locale.language}
"
,
$
(
"
#map
"
),
{
maxZoom
:
8
,
center
:
new
GoogleMaps
.
LatLng
(
0
,
0
)
},
function
(
el
,
map
)
{
// info window
var
infowindow
=
new
google
.
maps
.
InfoWindow
({
content
:
""
,
maxWidth
:
500
});
// markers
var
jsonInstitutes
=
$
{
jsonInstitutes
};
jsonInstitutes
.
forEach
(
function
(
inst
)
{
var
m
=
new
google
.
maps
.
Marker
({
position
:
new
google
.
maps
.
LatLng
(
inst
.
lat
,
inst
.
lng
),
title
:
inst
.
title
,
map
:
map
});
google
.
maps
.
event
.
addListener
(
m
,
"
click
"
,
function
()
{
infowindow
.
close
();
infowindow
.
setContent
(
"
<a href='
<c:url
value=
"/wiews/"
/>
"
+
inst
.
code
.
toLowerCase
()
+
"
'>
"
+
inst
.
title
+
"
</a>
"
);
infowindow
.
open
(
map
,
m
);
});
});
map
.
fitBounds
(
GoogleMaps
.
boundingBox
(
jsonInstitutes
));
});
});
</script>
</c:if>
</body>
</html>
\ No newline at end of file
src/main/webapp/WEB-INF/jsp/organization/details.jsp
View file @
fcf1e123
...
...
@@ -21,6 +21,7 @@
<div
class=
"page-header"
>
<a
href=
"
<c:url
value=
"/org/${organization.slug}/map"
/>
"
><spring:message
code=
"maps.view-map"
/></a>
<a
href=
"
<c:url
value=
"/org/${organization.slug}/data"
/>
"
><spring:message
code=
"view.accessions"
/></a>
</div>
...
...
@@ -43,29 +44,29 @@
</div>
</div>
<c:remove
var=
"countryName"
/>
</div>
<c:set
value=
""
var=
"countryName"
/>
<ul
class=
"funny-list"
>
<c:forEach
items=
"
${
members
}
"
var=
"faoInstitute"
varStatus=
"status"
>
<c:if
test=
"
${
countryName
ne
faoInstitute
.
country
.
getName
(
pageContext
.
response
.
locale
)
}
"
>
<c:set
var=
"countryName"
value=
"
${
faoInstitute
.
country
.
getName
(
pageContext
.
response
.
locale
)
}
"
/>
<li
class=
"hoofdleter"
id=
"nav-${faoInstitute.country.code3}"
><c:out
value=
"
${
countryName
}
"
/>
<small><a
href=
"#"
><spring:message
code=
"jump-to-top"
/></a></small></li>
</c:if>
<li
class=
"clearfix ${status.count % 2 == 0 ? 'even' : 'odd'}"
><a
class=
"show pull-left"
href=
"
<c:url
value=
"/wiews/${faoInstitute.code.toLowerCase()}"
/>
"
><b><c:out
value=
"
${
faoInstitute
.
code
}
"
/></b>
<c:out
value=
"
${
faoInstitute
.
fullName
}
"
/></a>
<div
class=
"pull-right"
>
<spring:message
code=
"faoInstitute.accessionCount"
arguments=
"
${
faoInstitute
.
accessionCount
}
"
/>
</div></li>
</c:forEach>
</ul>
<c:set
value=
""
var=
"countryName"
/>
<ul
class=
"funny-list"
>
<c:forEach
items=
"
${
members
}
"
var=
"faoInstitute"
varStatus=
"status"
>
<c:if
test=
"
${
countryName
ne
faoInstitute
.
country
.
getName
(
pageContext
.
response
.
locale
)
}
"
>
<c:set
var=
"countryName"
value=
"
${
faoInstitute
.
country
.
getName
(
pageContext
.
response
.
locale
)
}
"
/>
<li
class=
"hoofdleter"
id=
"nav-${faoInstitute.country.code3}"
><c:out
value=
"
${
countryName
}
"
/>
<small><a
href=
"#"
><spring:message
code=
"jump-to-top"
/></a></small></li>
</c:if>
<li
class=
"clearfix ${status.count % 2 == 0 ? 'even' : 'odd'}"
><a
class=
"show pull-left"
href=
"
<c:url
value=
"/wiews/${faoInstitute.code.toLowerCase()}"
/>
"
><b><c:out
value=
"
${
faoInstitute
.
code
}
"
/></b>
<c:out
value=
"
${
faoInstitute
.
fullName
}
"
/></a>
<div
class=
"pull-right"
>
<spring:message
code=
"faoInstitute.accessionCount"
arguments=
"
${
faoInstitute
.
accessionCount
}
"
/>
</div></li>
</c:forEach>
</ul>
<script
type=
"text/javascript"
>
jQuery
(
document
).
ready
(
function
()
{
$
(
"
#countryNavigation
"
).
on
(
"
change
"
,
function
()
{
window
.
location
.
hash
=
"
nav-
"
+
this
.
value
;
});
<script
type=
"text/javascript"
>
jQuery
(
document
).
ready
(
function
()
{
$
(
"
#countryNavigation
"
).
on
(
"
change
"
,
function
()
{
window
.
location
.
hash
=
"
nav-
"
+
this
.
value
;
});
});
</script>
</body>
</html>
\ No newline at end of file
src/main/webapp/WEB-INF/jsp/organization/map.jsp
0 → 100644
View file @
fcf1e123
<!DOCTYPE html>
<%@include
file=
"/WEB-INF/jsp/init.jsp"
%>
<html>
<head>
<title><spring:message
code=
"organization.page.profile.title"
arguments=
"
${
organization
.
slug
}
"
argumentSeparator=
"|"
/></title>
</head>
<body>
<h1>
<c:out
value=
"
${
organization
.
title
}
"
/>
<small><c:out
value=
"
${
organization
.
slug
}
"
/></small>
</h1>
<div
class=
"page-header"
>
<a
href=
"
<c:url
value=
"/org/${organization.slug}"
/>
"
><c:out
value=
"
${
organization
.
slug
}
"
/></a>
<a
href=
"
<c:url
value=
"/org/${organization.slug}/map"
/>
"
><spring:message
code=
"maps.view-map"
/></a>
<a
href=
"
<c:url
value=
"/org/${organization.slug}/data"
/>
"
><spring:message
code=
"view.accessions"
/></a>
</div>
<c:if
test=
"
${
jsonInstitutes
ne
null
}
"
>
<div
class=
"row"
style=
""
>
<div
class=
"col-sm-12"
>
<div
id=
"map"
class=
"gis-map gis-map-square"
><spring:message
code=
"maps.loading-map"
/></div>
</div>
</div>
<script
type=
"text/javascript"
>
jQuery
(
document
).
ready
(
function
()
{
GoogleMaps
.
map
(
"
${pageContext.response.locale.language}
"
,
$
(
"
#map
"
),
{
maxZoom
:
8
,
center
:
new
GoogleMaps
.
LatLng
(
0
,
0
)
},
function
(
el
,
map
)
{
// info window
var
infowindow
=
new
google
.
maps
.
InfoWindow
({
content
:
""
,
maxWidth
:
500
});
// markers
var
jsonInstitutes
=
$
{
jsonInstitutes
};
jsonInstitutes
.
forEach
(
function
(
inst
)
{
var
m
=
new
google
.
maps
.
Marker
({
position
:
new
google
.
maps
.
LatLng
(
inst
.
lat
,
inst
.
lng
),
title
:
inst
.
title
,
map
:
map
});
google
.
maps
.
event
.
addListener
(
m
,
"
click
"
,
function
()
{
infowindow
.
close
();
infowindow
.
setContent
(
"
<a href='
<c:url
value=
"/wiews/"
/>
"
+
inst
.
code
.
toLowerCase
()
+
"
'>
"
+
inst
.
title
+
"
</a>
"
);
infowindow
.
open
(
map
,
m
);
});
});
map
.
fitBounds
(
GoogleMaps
.
boundingBox
(
jsonInstitutes
));
});
});
</script>
</c:if>
</body>
</html>
\ No newline at end of file
src/main/webapp/html/css/custom.css
View file @
fcf1e123
...
...
@@ -25,6 +25,10 @@
margin-top
:
5px
;
width
:
250px
;
}
.gis-map.gis-map-square
{
height
:
100%
;
}
}
@media
(
max-width
:
780px
)
{
...
...
@@ -39,6 +43,9 @@
width
:
250px
;
}
.gis-map.gis-map-square
{
height
:
100%
;
}
}
@media
(
max-width
:
500px
)
{
...
...
@@ -72,6 +79,10 @@
#crops-menu
.funny-list
{
display
:
none
;
}
.gis-map.gis-map-square
{
height
:
100%
;
}
}
a
{
...
...
@@ -542,7 +553,3 @@ tr.acn .sel.picked {
height
:
300px
;
margin-top
:
1em
;
}
.gis-map.gis-map-400
{
height
:
400px
;
}
src/main/webapp/html/js/crophub.js
View file @
fcf1e123
...
...
@@ -24,7 +24,7 @@ GoogleMaps = {
GoogleMaps
=
{
LatLng
:
GoogleMaps
.
LatLng
,
loaded
:
false
,
maps
:
[],
queue
:
[],
loadedMaps
:
[],
googleMaps
:
[],
defaultOptions
:
{
...
...
@@ -34,19 +34,20 @@ GoogleMaps = {
streetViewControl
:
false
},
map
:
function
(
language
,
doms
,
mapOptions
)
{
var
mapOpt
=
{
zoom
:
mapOptions
.
zoom
||
this
.
defaultOptions
.
zoom
,
maxZoom
:
mapOptions
.
maxZoom
,
center
:
mapOptions
.
center
||
this
.
defaultOptions
.
center
,
mapTypeId
:
mapOptions
.
mapTypeId
||
this
.
defaultOptions
.
mapTypeId
,
markerTitle
:
mapOptions
.
markerTitle
||
null
,
streetViewControl
:
mapOptions
.
streetViewControl
||
this
.
defaultOptions
.
streetViewControl
};
map
:
function
(
language
,
doms
,
mapOptions
,
callback
)
{
var
mapOpt
=
$
.
extend
({},
this
.
defaultOptions
,
mapOptions
);
// var mapOpt = {
// zoom: mapOptions.zoom || this.defaultOptions.zoom,
// maxZoom: mapOptions.maxZoom,
// center: mapOptions.center || this.defaultOptions.center,
// mapTypeId: mapOptions.mapTypeId || this.defaultOptions.mapTypeId,
// markerTitle: mapOptions.markerTitle || null,
// streetViewControl: mapOptions.streetViewControl || this.defaultOptions.streetViewControl
// };
doms
.
each
(
function
(
idx
,
el
)
{
if
(
$
.
inArray
(
el
,
this
.
loadedMaps
)
<
0
)
{
el
.
googleMapsOptions
=
mapOpt
;
GoogleMaps
.
maps
.
push
(
el
);
//
el.googleMapsOptions = mapOpt;
GoogleMaps
.
queue
.
push
(
{
element
:
el
,
mapOptions
:
$
.
extend
({},
mapOpt
),
callback
:
callback
}
);
}
});
...
...
@@ -66,23 +67,40 @@ GoogleMaps = {
},
onLoad
:
function
()
{
var
el
=
this
.
maps
.
shift
();
while
(
el
!=
null
)
{
var
mapOptions
=
el
.
googleMapsOptions
;
el
.
googleMapsOptions
=
null
;
var
config
=
this
.
queue
.
shift
();
while
(
config
!=
null
)
{
var
mapOptions
=
config
.
mapOptions
;
var
pos
=
mapOptions
.
center
;
mapOptions
.
center
=
new
google
.
maps
.
LatLng
(
pos
.
lat
,
pos
.
lng
,
pos
.
noWrap
);
var
map
=
new
google
.
maps
.
Map
(
el
,
mapOptions
);
this
.
googleMaps
.
push
(
map
);
var
map
=
new
google
.
maps
.
Map
(
config
.
element
,
mapOptions
);
//
this.googleMaps.push(map);
// debugger;
var
marker
=
new
google
.
maps
.
Marker
({
position
:
mapOptions
.
center
,
map
:
map
,
title
:
mapOptions
.
markerTitle
});
this
.
loadedMaps
.
push
(
el
);
el
=
this
.
maps
.
shift
();
if
(
mapOptions
.
markerTitle
!=
null
)
{
var
marker
=
new
google
.
maps
.
Marker
({
position
:
mapOptions
.
center
,
map
:
map
,
title
:
mapOptions
.
markerTitle
});
}
if
(
config
.
callback
!=
null
)
{
config
.
callback
(
config
.
element
,
map
);
}
this
.
loadedMaps
.
push
(
config
.
element
);
config
=
this
.
queue
.
shift
();
}
},
boundingBox
:
function
(
latLngArray
)
{
var
sw
=
new
GoogleMaps
.
LatLng
(
180
,
180
);
var
ne
=
new
GoogleMaps
.
LatLng
(
-
180
,
-
180
);
latLngArray
.
forEach
(
function
(
latLng
)
{
sw
.
lat
=
Math
.
min
(
sw
.
lat
,
latLng
.
lat
);
sw
.
lng
=
Math
.
min
(
sw
.
lng
,
latLng
.
lng
);
ne
.
lat
=
Math
.
max
(
ne
.
lat
,
latLng
.
lat
);
ne
.
lng
=
Math
.
max
(
ne
.
lng
,
latLng
.
lng
);
});
return
new
google
.
maps
.
LatLngBounds
(
new
google
.
maps
.
LatLng
(
sw
.
lat
,
sw
.
lng
),
new
google
.
maps
.
LatLng
(
ne
.
lat
,
ne
.
lng
));
}
};
...
...
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