Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
P
purescript-gargantext
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
1
Merge Requests
1
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
Przemyslaw Kaminski
purescript-gargantext
Commits
8e4975ab
Commit
8e4975ab
authored
Oct 23, 2021
by
arturo
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
>>> continue
parent
6014c01f
Changes
5
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
2081 additions
and
23 deletions
+2081
-23
Phylo.purs
src/Gargantext/Components/Nodes/Corpus/Phylo.purs
+6
-23
Draw.purs
src/Gargantext/Components/PhyloExplorer/Draw.purs
+1
-0
Layout.purs
src/Gargantext/Components/PhyloExplorer/Layout.purs
+403
-0
draw.js
src/Gargantext/Components/PhyloExplorer/draw.js
+1391
-0
layout.js
src/Gargantext/Components/PhyloExplorer/layout.js
+280
-0
No files found.
src/Gargantext/Components/Nodes/Corpus/Phylo.purs
View file @
8e4975ab
...
@@ -12,6 +12,7 @@ import Data.HTTP.Method (Method(..))
...
@@ -12,6 +12,7 @@ import Data.HTTP.Method (Method(..))
import Data.Maybe (Maybe(..))
import Data.Maybe (Maybe(..))
import Effect.Aff (Aff, launchAff_)
import Effect.Aff (Aff, launchAff_)
import Effect.Class (liftEffect)
import Effect.Class (liftEffect)
import Gargantext.Components.PhyloExplorer.Layout (layout)
import Gargantext.Components.PhyloExplorer.Types (PhyloDataset)
import Gargantext.Components.PhyloExplorer.Types (PhyloDataset)
import Gargantext.Sessions (Session)
import Gargantext.Sessions (Session)
import Gargantext.Types (NodeID)
import Gargantext.Types (NodeID)
...
@@ -36,38 +37,20 @@ phyloLayoutCpt = here.component "phyloLayout" cpt where
...
@@ -36,38 +37,20 @@ phyloLayoutCpt = here.component "phyloLayout" cpt where
cpt _ _ = do
cpt _ _ = do
fetchedDataBox <- T.useBox (Nothing :: Maybe PhyloDataset)
fetchedDataBox <- T.useBox (Nothing :: Maybe PhyloDataset)
fetchedData <- T.useLive T.unequal fetchedDataBox
fetchedData
<- T.useLive T.unequal fetchedDataBox
R.useEffectOnce' $ launchAff_ do
R.useEffectOnce' $ launchAff_ do
result <- fetchPhyloJSON
result <- fetchPhyloJSON
liftEffect $ case result of
liftEffect $ case result of
Left err -> log2 "error" err
Left err
-> log2 "error" err
Right res -> T.write_ (Just res) fetchedDataBox
Right res -> T.write_ (Just res) fetchedDataBox
pure $ case fetchedData of
pure case fetchedData of
Nothing -> mempty
Nothing -> mempty
Just fdata ->
Just phyloDataset -> layout { phyloDataset } []
H.div
{ className:"phyloCorpus" }
[ H.text $ show fdata ]
-- ,
-- infoCorpusR
-- ,
-- infoPhyloR
-- ,
-- timelineR
-- ,
-- isolineR
-- ,
-- wordcloudR
-- ,
-- phyloR
fetchPhyloJSON :: Aff (Either String PhyloDataset)
fetchPhyloJSON :: Aff (Either String PhyloDataset)
-- fetchPhyloJSON :: ReadForeign PhyloDataset => Aff Unit
fetchPhyloJSON =
fetchPhyloJSON =
let
let
request = AX.defaultRequest
request = AX.defaultRequest
...
...
src/Gargantext/Components/PhyloExplorer/Draw.purs
0 → 100644
View file @
8e4975ab
module Gargantext.Components.PhyloExplorer.Draw where
src/Gargantext/Components/PhyloExplorer/Layout.purs
0 → 100644
View file @
8e4975ab
module Gargantext.Components.PhyloExplorer.Layout
( layout
) where
import Gargantext.Prelude
import DOM.Simple.Console (log2)
import Data.Array as Array
import Data.Int (fromString)
import Data.Maybe (maybe)
import Data.String as String
import Gargantext.Components.PhyloExplorer.Types (PhyloDataset(..))
import Gargantext.Utils (nbsp)
import Gargantext.Utils.Reactix as R2
import Reactix as R
import Reactix.DOM.HTML as H
import Toestand as T
here :: R2.Here
here = R2.here "Gargantext.Components.PhyloExplorer"
type Props =
( phyloDataset :: PhyloDataset
)
layout :: R2.Component Props
layout = R.createElement layoutCpt
layoutCpt :: R.Component Props
layoutCpt = here.component "layout" cpt where
cpt { phyloDataset: (PhyloDataset phyloDataset)
} _ = do
-- States
let
{ phyloDocs
, phyloBranches
, phyloGroups
, phyloTerms
, phyloPeriods
, phyloFoundations
, phyloSources
} = phyloDataset
nbDocs = parseInt phyloDocs
nbBranches = parseInt phyloBranches
nbGroups = parseInt phyloGroups
nbTerms = parseInt phyloTerms
nbPeriods = parseInt phyloPeriods
nbFoundations = parseInt phyloFoundations
sourcesBox <- T.useBox (mempty :: Array String)
sources <- T.useLive T.unequal sourcesBox
-- Hooks
R.useEffectOnce' $ do
sources' <- pure $ stringArrToArr phyloSources
T.write_ sources' sourcesBox
-- @hightlightSource
let
highlightSource = \_ -> unit
-- Render
pure $
H.div
{ className: "phylo" }
[
-- <!-- row 1 -->
H.div
{ className: "phylo-title font-bold" }
[ H.text "Mèmiescape" ]
,
H.div
{ className: "phylo-folder" }
[
-- <!-- title bar (static mode) -->
H.label
{ id: "phyloName"
, className: "phylo-name"
}
[]
,
-- <!-- folder bar -->
-- H.label
-- { id: "file-label"
-- , for: "file-path"
-- , className: "input-file"
-- }
-- [ H.text "load a phylomemy →" ]
-- ,
-- H.input
-- { id: "file-path"
-- , type: "file"
-- , maxLength: "10"
-- }
-- ,
-- H.label
-- { id: "file-name"
-- , className: "input-name"
-- }
-- []
-- ,
-- H.button
-- { id: "draw"
-- , className: "button draw"
-- }
-- [ H.text "draw" ]
-- ,
-- <!-- source selector -->
R2.select
{ id: "checkSource"
, className: "select-source"
, defaultValue: ""
, on: { change: \_ -> unit }
} $
[
H.option
{ disabled: true
, value: ""
}
[ H.text "select a source ↴" ]
,
H.option
{ value: "unselect" }
[ H.text "unselect source ✕" ]
]
<>
flip Array.mapWithIndex sources
( \idx val ->
H.option
{ value: idx }
[ H.text val ]
)
,
-- <!-- search bar -->
H.label
{ id: "search-label"
, className: "search-label"
}
[ H.text "find a term →" ]
,
H.input
{ id: "search-box"
, type: "text"
, className: "search"
}
,
H.input
{ id: "search-autocomplete"
, text: "text"
, className: "autocomplete"
, disabled: true
, value: ""
}
]
,
-- <!-- row 2 & 3 -->
phyloCorpus {} []
,
phyloCorpusInfo
{ nbDocs, nbFoundations, nbPeriods }
[]
,
-- H.div
-- { id: "phyloHow"
-- , className: "phylo-how"
-- }
-- []
-- ,
phyloPhylo {} []
,
phyloPhyloInfo
{ nbTerms, nbGroups, nbBranches }
[]
,
H.div
{ id: "phyloIsoLine"
, className: "phylo-isoline-info"
}
[
H.div
{ className: "btn-group" }
[
H.button
{ id: "reset"
, className: "button reset"
}
[
H.i
{ className: "fas fa-expand-arrows-alt" }
[]
]
,
H.button
{ id: "label"
, className: "button label"
}
[
H.i
{ className: "fas fa-dot-circle" }
[]
]
,
H.button
{ id: "heading"
, className: "button heading"
}
[
H.i
{ className: "fas fa-sort-alpha-down" }
[]
]
,
H.button
{ id: "export"
, className: "button export"
}
[
H.i
{ className: "fas fa-camera" }
[]
]
]
]
,
-- <!-- row 4 -->
H.div
{ id: "phyloScape"
, className: "phylo-scape"
}
[]
,
H.div
{ id: "phyloTimeline"
, className: "phylo-timeline"
}
[]
,
H.div
{ id: "phyloGraph"
, className: "phylo-graph"
}
[]
,
-- <!-- row 5 -->
H.div
{ className: "phylo-footer font-bold font-small"
}
[ H.text "iscpif // cnrs // 2021" ]
]
parseInt :: String -> Int
parseInt s = maybe 0 identity $ fromString s
stringArrToArr :: String -> Array String
stringArrToArr
= String.replace (String.Pattern "[") (String.Replacement "")
>>> String.replace (String.Pattern "]") (String.Replacement "")
>>> String.split (String.Pattern ",")
>>> Array.filter (\s -> not eq 0 $ String.length s)
--------------------------------------------------------
type PhyloCorpusProps = ()
phyloCorpus :: R2.Component PhyloCorpusProps
phyloCorpus = R.createElement phyloCorpusCpt
phyloCorpusCpt :: R.Component PhyloCorpusProps
phyloCorpusCpt = here.component "phyloCorpus" cpt where
cpt _ _ = do
-- Render
pure $
H.div
{ id: "phyloCorpus"
, className: "phylo-corpus"
}
[ H.text "corpus" ]
---------------------------------------------------------
type PhyloPhyloProps = ()
phyloPhylo :: R2.Component PhyloPhyloProps
phyloPhylo = R.createElement phyloPhyloCpt
phyloPhyloCpt :: R.Component PhyloPhyloProps
phyloPhyloCpt = here.component "phyloPhylo" cpt where
cpt _ _ = do
-- Render
pure $
H.div
{ id: "phyloPhylo"
, className: "phylo-phylo"
}
[ H.text "phylomemy" ]
---------------------------------------------------------
type PhyloCorpusInfoProps =
( nbDocs :: Int
, nbFoundations :: Int
, nbPeriods :: Int
)
phyloCorpusInfo :: R2.Component PhyloCorpusInfoProps
phyloCorpusInfo = R.createElement phyloCorpusInfoCpt
phyloCorpusInfoCpt :: R.Component PhyloCorpusInfoProps
phyloCorpusInfoCpt = here.component "phyloCorpusInfo" cpt where
cpt props _ = do
-- Render
pure $
H.div
{ id: "phyloCorpusInfo"
, className: "phylo-corpus-info"
}
[
H.span
{}
[
H.b {} [ H.text $ show props.nbDocs ]
, H.text $ nbsp 1 <> "docs"
]
,
H.span
{}
[
H.b {} [ H.text $ show props.nbFoundations ]
, H.text $ nbsp 1 <> "foundations"
]
,
H.span
{}
[
H.b {} [ H.text $ show props.nbPeriods ]
, H.text $ nbsp 1 <> "periods"
]
]
---------------------------------------------------------
type PhyloPhyloInfoProps =
( nbTerms :: Int
, nbGroups :: Int
, nbBranches :: Int
)
phyloPhyloInfo :: R2.Component PhyloPhyloInfoProps
phyloPhyloInfo = R.createElement phyloPhyloInfoCpt
phyloPhyloInfoCpt :: R.Component PhyloPhyloInfoProps
phyloPhyloInfoCpt = here.component "phyloPhyloInfo" cpt where
cpt props _ = do
-- Render
pure $
H.div
{ id: "phyloPhyloInfo"
, className: "phylo-phylo-info"
}
[
H.span
{}
[
H.b
{ id: "phyloTerms" }
[ H.text $ show props.nbTerms ]
, H.text $ nbsp 1 <> "terms"
]
,
H.span
{}
[
H.b
{ id: "phyloGroups" }
[ H.text $ show props.nbGroups ]
, H.text $ nbsp 1 <> "groups"
]
,
H.span
{}
[
H.b
{ id: "phyloBranches" }
[ H.text $ show props.nbBranches ]
, H.text $ nbsp 1 <> "branches"
]
]
src/Gargantext/Components/PhyloExplorer/draw.js
0 → 100644
View file @
8e4975ab
This diff is collapsed.
Click to expand it.
src/Gargantext/Components/PhyloExplorer/layout.js
0 → 100644
View file @
8e4975ab
'use strict'
;
function
readJson
(
file
,
callback
)
{
var
raw
=
new
XMLHttpRequest
();
raw
.
overrideMimeType
(
"application/json"
);
raw
.
open
(
"GET"
,
file
,
true
);
raw
.
onreadystatechange
=
function
()
{
if
(
raw
.
readyState
===
4
&&
raw
.
status
==
"200"
)
{
callback
(
raw
.
responseText
);
}
}
raw
.
send
(
null
);
}
function
unhide
(
mode
)
{
document
.
querySelector
(
"#reset"
).
style
.
visibility
=
"visible"
;
document
.
querySelector
(
"#label"
).
style
.
visibility
=
"visible"
;
document
.
querySelector
(
"#heading"
).
style
.
visibility
=
"visible"
;
if
(
mode
!=
"static"
)
{
document
.
querySelector
(
"#export"
).
style
.
visibility
=
"visible"
;
}
}
window
.
addEventListener
(
"load"
,
function
()
{
// read the config
readJson
(
"./config.json"
,
function
(
data1
){
var
conf
=
JSON
.
parse
(
data1
);
// available config modes are "static" or "explorable"
if
(
conf
.
mode
==
"static"
)
{
var
path
=
""
;
var
name
=
""
;
if
(
conf
.
path
==
null
||
conf
.
path
==
""
)
{
path
=
conf
.
defaultPath
;
name
=
conf
.
defaultName
;
}
else
{
path
=
conf
.
path
;
name
=
conf
.
pathName
;
}
document
.
querySelector
(
"#file-label"
).
style
.
display
=
"none"
;
document
.
querySelector
(
"#spin"
).
style
.
visibility
=
"visible"
;
document
.
getElementById
(
"phyloName"
).
innerHTML
=
name
;
document
.
querySelector
(
"#phyloName"
).
style
.
visibility
=
"visible"
;
readJson
(
path
,
function
(
data2
){
phylo
=
JSON
.
parse
(
data2
);
unhide
(
"static"
);
draw
(
phylo
);
})
}
})
})
function
readPhylo
(
file
)
{
var
reader
=
new
FileReader
();
reader
.
onload
=
(
function
(
f
)
{
return
function
(
e
)
{
try
{
json
=
JSON
.
parse
(
e
.
target
.
result
);
unhide
(
"explorable"
);
draw
(
json
)
}
catch
(
error
)
{
console
.
log
(
error
)
}
};
})(
file
);
reader
.
readAsText
(
file
,
"UTF-8"
);
}
// display the Draw button after loading a phylo
document
.
querySelector
(
"#file-path"
).
onchange
=
function
(){
document
.
querySelector
(
"#file-name"
).
textContent
=
(
this
.
files
[
0
].
name
).
substring
(
0
,
15
)
+
"..."
;
// document.querySelector("#file-name").textContent = this.files[0].name;
document
.
querySelector
(
"#draw"
).
style
.
display
=
"inline-block"
;
}
// draw the phylo
document
.
querySelector
(
"#draw"
).
onclick
=
function
()
{
document
.
querySelector
(
"#spin"
).
style
.
visibility
=
"visible"
;
readPhylo
(
document
.
getElementById
(
"file-path"
).
files
[
0
]);
}
function
drawPhyloInfo
(
elemClass
,
docs
,
foundations
,
branches
,
groups
,
terms
,
periods
)
{
ReactDOM
.
render
(
React
.
createElement
(
phyloCorpus
,{}),
document
.
getElementById
(
'phyloCorpus'
));
ReactDOM
.
render
(
React
.
createElement
(
phyloPhylo
,{}),
document
.
getElementById
(
'phyloPhylo'
));
ReactDOM
.
render
(
React
.
createElement
(
phyloHow
,{}),
document
.
getElementById
(
'phyloHow'
));
ReactDOM
.
render
(
React
.
createElement
(
phyloCorpusInfo
,{
nbDocs
:
docs
,
nbFoundations
:
foundations
,
nbPeriods
:
periods
}),
document
.
getElementById
(
'phyloCorpusInfo'
));
ReactDOM
.
render
(
React
.
createElement
(
phyloPhyloInfo
,{
nbTerms
:
terms
,
nbGroups
:
groups
,
nbBranches
:
branches
}),
document
.
getElementById
(
'phyloPhyloInfo'
));
}
function
draw
(
json
)
{
// draw PhyloInfo
window
.
freq
=
{};
window
.
terms
=
{};
window
.
sources
=
[];
window
.
nbDocs
=
parseFloat
(
json
.
phyloDocs
);
window
.
nbBranches
=
parseFloat
(
json
.
phyloBranches
);
window
.
nbGroups
=
parseFloat
(
json
.
phyloGroups
);
window
.
nbTerms
=
parseFloat
(
json
.
phyloTerms
);
window
.
nbPeriods
=
parseFloat
(
json
.
phyloPeriods
);
window
.
nbFoundations
=
parseFloat
(
json
.
phyloFoundations
);
window
.
timeScale
=
json
.
phyloTimeScale
;
if
(
json
.
phyloSources
!=
undefined
)
{
var
sources
=
stringArrToArr
(
json
.
phyloSources
);
var
checkSource
=
document
.
getElementById
(
"checkSource"
);
for
(
var
i
=
0
;
i
<
sources
.
length
;
i
++
)
{
window
.
sources
.
push
({
source
:
sources
[
i
],
index
:
i
});
}
window
.
sources
.
sort
(
function
(
a
,
b
){
if
(
a
.
source
<
b
.
source
)
{
return
-
1
;
}
if
(
a
.
source
>
b
.
source
)
{
return
1
;
}
return
0
;
})
for
(
var
i
=
0
;
i
<
window
.
sources
.
length
;
i
++
)
{
var
option
=
document
.
createElement
(
"option"
);
option
.
text
=
window
.
sources
[
i
].
source
;
option
.
value
=
window
.
sources
[
i
].
index
;
checkSource
.
add
(
option
);
}
}
// original bounding box
bb
=
((
json
.
bb
).
split
(
','
)).
map
(
xy
=>
parseFloat
(
xy
))
drawPhyloInfo
(
""
,
window
.
nbDocs
,
window
.
nbFoundations
,
window
.
nbBranches
,
window
.
nbGroups
,
window
.
nbTerms
,
window
.
nbPeriods
)
// draw PhyloIsoline
var
branches
=
json
.
objects
.
filter
(
node
=>
node
.
nodeType
==
"branch"
).
map
(
function
(
b
){
return
{
x1
:
b
.
branch_x
,
y
:
b
.
branch_y
,
x2
:
parseFloat
(((
b
.
pos
).
split
(
','
))[
0
])
,
label
:
b
.
label
,
bId
:
parseInt
(
b
.
bId
),
gvid
:
parseInt
(
b
.
_gvid
)
}
});
var
periods
=
json
.
objects
.
filter
(
node
=>
node
.
nodeType
==
"period"
).
map
(
function
(
p
){
var
from
=
yearToDate
(
p
.
from
),
to
=
yearToDate
(
p
.
to
);
if
(
p
.
strFrom
!=
undefined
)
{
if
(
window
.
timeScale
==
"epoch"
)
{
from
=
utcStringToDate
(
p
.
strFrom
)
}
else
{
from
=
stringToDate
(
p
.
strFrom
)
}
}
if
(
p
.
strTo
!=
undefined
)
{
if
(
window
.
timeScale
==
"epoch"
)
{
to
=
utcStringToDate
(
p
.
strTo
)
}
else
{
to
=
stringToDate
(
p
.
strTo
)
}
}
return
{
from
:
from
,
to
:
to
,
y
:
parseFloat
(((
p
.
pos
).
split
(
','
))[
1
])}
});
// groups
window
.
weighted
=
false
;
var
groups
=
json
.
objects
.
filter
(
node
=>
node
.
nodeType
==
"group"
).
map
(
function
(
g
){
// console.log(g.weight)
if
((
g
.
weight
!=
undefined
)
&&
(
g
.
weight
!=
"Nothing"
))
window
.
weighted
=
true
;
var
keys
=
(
g
.
foundation
.
slice
(
1
,
g
.
foundation
.
length
-
1
)).
split
(
'|'
)
var
labels
=
(
g
.
lbl
.
slice
(
1
,
g
.
lbl
.
length
-
1
)).
split
(
'|'
)
for
(
var
i
=
0
;
i
<
keys
.
length
;
i
++
)
{
// freq
if
(
!
((
keys
[
i
]).
trim
()
in
window
.
freq
))
{
window
.
freq
[(
keys
[
i
]).
trim
()]
=
0
;
}
else
{
window
.
freq
[(
keys
[
i
]).
trim
()]
+=
1
;
}
// terms
if
(
!
((
keys
[
i
]).
trim
()
in
window
.
terms
))
{
window
.
terms
[(
keys
[
i
]).
trim
()]
=
{
label
:(
labels
[
i
]).
trim
(),
fdt
:(
keys
[
i
]).
trim
()}
}
}
var
from
=
yearToDate
(
g
.
from
),
to
=
yearToDate
(
g
.
to
),
weight
=
0
;
source
=
[];
if
(
g
.
strFrom
!=
undefined
)
{
if
(
window
.
timeScale
==
"epoch"
)
{
from
=
utcStringToDate
(
g
.
strFrom
)
}
else
{
from
=
stringToDate
(
g
.
strFrom
)
}
}
if
(
g
.
strTo
!=
undefined
)
{
if
(
window
.
timeScale
==
"epoch"
)
{
to
=
utcStringToDate
(
g
.
strTo
)
}
else
{
to
=
stringToDate
(
g
.
strTo
)
}
}
if
(
g
.
source
!=
undefined
)
source
=
intArrToArr
(
g
.
source
);
if
(
g
.
weight
!=
undefined
)
weight
=
parseFloat
((
g
.
weight
).
replace
(
"Just "
,
""
));
return
{
from
:
from
,
to
:
to
,
x
:
parseFloat
(((
g
.
pos
).
split
(
','
))[
0
])
,
y
:
parseFloat
(((
g
.
pos
).
split
(
','
))[
1
])
,
bId
:
parseInt
(
g
.
bId
)
,
gId
:
parseInt
(
g
.
_gvid
)
,
size
:
parseInt
(
g
.
support
),
source
:
source
,
weight
:
weight
,
label
:
labels
,
foundation
:
keys
,
role
:
((
g
.
role
.
slice
(
1
,
g
.
role
.
length
-
1
)).
split
(
'|'
)).
map
(
e
=>
parseInt
(
e
.
trim
()))}
});
var
links
=
json
.
edges
.
filter
(
edges
=>
edges
.
edgeType
==
"link"
).
map
(
function
(
l
){
return
{
lId
:
parseInt
(
l
.
_gvid
),
from
:
parseInt
(
l
.
tail
)
,
to
:
parseInt
(
l
.
head
)
,
label
:
l
.
label
}
});
var
aLinks
=
json
.
edges
.
filter
(
edges
=>
edges
.
edgeType
==
"ancestorLink"
).
map
(
function
(
l
){
return
{
lId
:
parseInt
(
l
.
_gvid
),
from
:
parseInt
(
l
.
tail
)
,
to
:
parseInt
(
l
.
head
)
,
label
:
l
.
label
}
});
var
bLinks
=
json
.
edges
.
filter
(
edges
=>
edges
.
edgeType
==
"branchLink"
).
map
(
function
(
l
){
return
{
from
:
parseInt
(
l
.
tail
)
,
to
:
parseInt
(
l
.
head
)
}
});
window
.
terms
=
Object
.
values
(
window
.
terms
)
// draw the phylo
drawPhylo
(
branches
,
periods
,
groups
,
links
,
aLinks
,
bLinks
,
bb
);
}
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