Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
M
Molstar
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Package registry
Container registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
GitLab community forum
Contribute to GitLab
Provide feedback
Terms and privacy
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
Michal Malý
Molstar
Commits
05b5554e
Commit
05b5554e
authored
Apr 17, 2018
by
Alexander Rose
Browse files
Options
Downloads
Patches
Plain Diff
wip, seperating inputs from controls
parent
d4670e0e
No related branches found
No related tags found
No related merge requests found
Changes
3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
src/mol-gl/controls/trackball.ts
+23
-99
23 additions, 99 deletions
src/mol-gl/controls/trackball.ts
src/mol-util/input/input-observer.ts
+20
-70
20 additions, 70 deletions
src/mol-util/input/input-observer.ts
src/mol-util/input/touch-pinch.ts
+0
-188
0 additions, 188 deletions
src/mol-util/input/touch-pinch.ts
with
43 additions
and
357 deletions
src/mol-gl/controls/trackball.ts
+
23
−
99
View file @
05b5554e
...
...
@@ -11,7 +11,7 @@
import
{
Quat
,
Vec2
,
Vec3
,
EPSILON
}
from
'
mol-math/linear-algebra
'
;
import
{
cameraLookAt
,
Viewport
}
from
'
../camera/util
'
;
import
InputObserver
,
{
DragInput
,
WheelInput
,
Mouse
ButtonsFlag
,
PinchInput
}
from
'
mol-util/input/input-observer
'
;
import
InputObserver
,
{
DragInput
,
WheelInput
,
ButtonsFlag
,
PinchInput
}
from
'
mol-util/input/input-observer
'
;
export
const
DefaultTrackballControlsProps
=
{
noScroll
:
true
,
...
...
@@ -79,8 +79,8 @@ namespace TrackballControls {
const
_zoomStart
=
Vec2
.
zero
()
const
_zoomEnd
=
Vec2
.
zero
()
//
let _touchZoomDistanceStart = 0
//
let _touchZoomDistanceEnd = 0
let
_touchZoomDistanceStart
=
0
let
_touchZoomDistanceEnd
=
0
const
_panStart
=
Vec2
.
zero
()
const
_panEnd
=
Vec2
.
zero
()
...
...
@@ -156,25 +156,7 @@ namespace TrackballControls {
Vec2
.
copy
(
_movePrev
,
_moveCurr
)
}
function
zoomCamera
()
{
// if (_state === STATE.TOUCH_ZOOM_PAN) {
// const factor = _touchZoomDistanceStart / _touchZoomDistanceEnd
// _touchZoomDistanceStart = _touchZoomDistanceEnd;
// Vec3.scale(_eye, _eye, factor)
// } else {
// const factor = 1.0 + ( _zoomEnd[1] - _zoomStart[1] ) * zoomSpeed
// if (factor !== 1.0 && factor > 0.0) {
// Vec3.scale(_eye, _eye, factor)
// }
// if (staticMoving) {
// Vec2.copy(_zoomStart, _zoomEnd)
// } else {
// _zoomStart[1] += ( _zoomEnd[1] - _zoomStart[1] ) * dynamicDampingFactor
// }
// }
const
factor
=
1.0
+
(
_zoomEnd
[
1
]
-
_zoomStart
[
1
])
*
zoomSpeed
if
(
factor
!==
1.0
&&
factor
>
0.0
)
{
Vec3
.
scale
(
_eye
,
_eye
,
factor
)
...
...
@@ -260,26 +242,26 @@ namespace TrackballControls {
// listeners
function
onDrag
({
pageX
,
pageY
,
buttons
,
modifiers
,
s
tart
ed
}:
DragInput
)
{
if
(
s
tart
ed
)
{
if
(
buttons
===
Mouse
ButtonsFlag
.
Primary
)
{
function
onDrag
({
pageX
,
pageY
,
buttons
,
modifiers
,
isS
tart
}:
DragInput
)
{
if
(
isS
tart
)
{
if
(
buttons
===
ButtonsFlag
.
Primary
)
{
Vec2
.
copy
(
_moveCurr
,
getMouseOnCircle
(
pageX
,
pageY
))
Vec2
.
copy
(
_movePrev
,
_moveCurr
)
}
else
if
(
buttons
===
Mouse
ButtonsFlag
.
Auxilary
)
{
}
else
if
(
buttons
===
ButtonsFlag
.
Auxilary
)
{
Vec2
.
copy
(
_zoomStart
,
getMouseOnScreen
(
pageX
,
pageY
))
Vec2
.
copy
(
_zoomEnd
,
_zoomStart
)
}
else
if
(
buttons
===
Mouse
ButtonsFlag
.
Secondary
)
{
}
else
if
(
buttons
===
ButtonsFlag
.
Secondary
)
{
Vec2
.
copy
(
_panStart
,
getMouseOnScreen
(
pageX
,
pageY
))
Vec2
.
copy
(
_panEnd
,
_panStart
)
}
}
if
(
buttons
===
Mouse
ButtonsFlag
.
Primary
)
{
if
(
buttons
===
ButtonsFlag
.
Primary
)
{
Vec2
.
copy
(
_movePrev
,
_moveCurr
)
Vec2
.
copy
(
_moveCurr
,
getMouseOnCircle
(
pageX
,
pageY
))
}
else
if
(
buttons
===
Mouse
ButtonsFlag
.
Auxilary
)
{
}
else
if
(
buttons
===
ButtonsFlag
.
Auxilary
)
{
Vec2
.
copy
(
_zoomEnd
,
getMouseOnScreen
(
pageX
,
pageY
))
}
else
if
(
buttons
===
Mouse
ButtonsFlag
.
Secondary
)
{
}
else
if
(
buttons
===
ButtonsFlag
.
Secondary
)
{
Vec2
.
copy
(
_panEnd
,
getMouseOnScreen
(
pageX
,
pageY
))
}
}
...
...
@@ -288,81 +270,23 @@ namespace TrackballControls {
_zoomStart
[
1
]
-=
dy
}
function
onPinch
({
d
elta
}:
PinchInput
)
{
console
.
log
(
delta
)
_zoomStart
[
1
]
-=
delta
function
onPinch
({
d
istance
,
isStart
}:
PinchInput
)
{
if
(
isStart
)
{
_touchZoomDistanceStart
=
distance
}
_touchZoomDistanceEnd
=
distance
// function touchstart(event: TouchEvent ) {
// switch ( event.touches.length ) {
// case 1:
// // _state = STATE.TOUCH_ROTATE;
// Vec2.copy(_moveCurr, getMouseOnCircle(event.touches[0].pageX, event.touches[0].pageY))
// Vec2.copy(_movePrev, _moveCurr)
// break;
// default: // 2 or more
// // _state = STATE.TOUCH_ZOOM_PAN;
// const dx = event.touches[0].pageX - event.touches[1].pageX;
// const dy = event.touches[0].pageY - event.touches[1].pageY;
// _touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt(dx * dx + dy * dy);
// const x = ( event.touches[0].pageX + event.touches[1].pageX) / 2;
// const y = ( event.touches[0].pageY + event.touches[1].pageY) / 2;
// Vec2.copy(_panStart, getMouseOnScreen(x, y))
// Vec2.copy(_panEnd, _panStart)
// break;
// }
// }
// function touchmove(event: TouchEvent) {
// event.preventDefault();
// event.stopPropagation();
// switch ( event.touches.length ) {
// case 1:
// Vec2.copy(_movePrev, _moveCurr)
// Vec2.copy(_moveCurr, getMouseOnCircle(event.touches[0].pageX, event.touches[0].pageY))
// break;
// default: // 2 or more
// const dx = event.touches[0].pageX - event.touches[1].pageX;
// const dy = event.touches[0].pageY - event.touches[1].pageY;
// _touchZoomDistanceEnd = Math.sqrt(dx * dx + dy * dy);
// const x = (event.touches[0].pageX + event.touches[1].pageX) / 2;
// const y = (event.touches[0].pageY + event.touches[1].pageY) / 2;
// Vec2.copy(_panEnd, getMouseOnScreen(x, y))
// break;
// }
// }
// function touchend(event: TouchEvent) {
// switch ( event.touches.length ) {
// case 0:
// // _state = STATE.NONE;
// break;
// case 1:
// // _state = STATE.TOUCH_ROTATE;
// Vec2.copy(_moveCurr, getMouseOnCircle(event.touches[0].pageX, event.touches[0].pageY))
// Vec2.copy(_movePrev, _moveCurr)
// break;
// }
// }
const
factor
=
(
_touchZoomDistanceStart
/
_touchZoomDistanceEnd
)
*
zoomSpeed
_touchZoomDistanceStart
=
_touchZoomDistanceEnd
;
Vec3
.
scale
(
_eye
,
_eye
,
factor
)
}
function
dispose
()
{
if
(
disposed
)
return
disposed
=
true
input
.
dispose
()
// element.removeEventListener( 'touchstart', touchstart as any, false );
// element.removeEventListener( 'touchend', touchend as any, false );
// element.removeEventListener( 'touchmove', touchmove as any, false );
}
// element.addEventListener( 'touchstart', touchstart as any, false );
// element.addEventListener( 'touchend', touchend as any, false );
// element.addEventListener( 'touchmove', touchmove as any, false );
// force an update at start
update
();
...
...
This diff is collapsed.
Click to expand it.
src/mol-util/input/input-observer.ts
+
20
−
70
View file @
05b5554e
...
...
@@ -9,7 +9,6 @@ import { Subject } from 'rxjs';
import
{
Vec2
}
from
'
mol-math/linear-algebra
'
;
import
toPixels
from
'
../to-pixels
'
import
TouchPinch
from
'
./touch-pinch
'
function
getButtons
(
event
:
MouseEvent
|
Touch
)
{
if
(
typeof
event
===
'
object
'
)
{
...
...
@@ -51,7 +50,7 @@ export type ModifiersKeys = {
meta
:
boolean
}
export
const
enum
Mouse
ButtonsFlag
{
export
const
enum
ButtonsFlag
{
/** No button or un-initialized */
None
=
0x0
,
/** Primary button (usually left) */
...
...
@@ -78,7 +77,7 @@ export type DragInput = {
dy
:
number
,
pageX
:
number
,
pageY
:
number
,
s
tart
ed
:
boolean
isS
tart
:
boolean
}
&
BaseInput
export
type
WheelInput
=
{
...
...
@@ -96,7 +95,8 @@ export type ClickInput = {
export
type
PinchInput
=
{
delta
:
number
,
distance
:
number
distance
:
number
,
isStart
:
boolean
}
const
enum
DraggingState
{
...
...
@@ -142,8 +142,6 @@ namespace InputObserver {
meta
:
false
}
// const touchPinch = TouchPinch.create(element)
let
dragging
:
DraggingState
=
DraggingState
.
Stopped
let
disposed
=
false
let
buttons
=
0
...
...
@@ -183,10 +181,6 @@ namespace InputObserver {
element
.
addEventListener
(
'
touchmove
'
,
onTouchMove
as
any
,
false
)
element
.
addEventListener
(
'
touchend
'
,
onTouchEnd
as
any
,
false
)
// touchPinch.place.subscribe(onPinchPlace)
// touchPinch.lift.subscribe(onPinchLift)
// touchPinch.change.subscribe(onPinchChange)
element
.
addEventListener
(
'
blur
'
,
handleBlur
)
element
.
addEventListener
(
'
keyup
'
,
handleMods
as
EventListener
)
element
.
addEventListener
(
'
keydown
'
,
handleMods
as
EventListener
)
...
...
@@ -197,8 +191,6 @@ namespace InputObserver {
if
(
disposed
)
return
disposed
=
true
// touchPinch.dispose()
element
.
removeEventListener
(
'
contextmenu
'
,
onContextMenu
,
false
)
element
.
removeEventListener
(
'
wheel
'
,
onMouseWheel
,
false
)
...
...
@@ -260,79 +252,43 @@ namespace InputObserver {
function
onTouchStart
(
ev
:
TouchEvent
)
{
preventDefault
(
ev
)
console
.
log
(
'
onTouchStart
'
,
ev
)
if
(
ev
.
touches
.
length
===
1
)
{
buttons
=
Mouse
ButtonsFlag
.
Primary
buttons
=
ButtonsFlag
.
Primary
onPointerDown
(
ev
.
touches
[
0
])
}
else
if
(
ev
.
touches
.
length
>=
2
)
{
buttons
=
Mouse
ButtonsFlag
.
Secondary
buttons
=
ButtonsFlag
.
Secondary
onPointerDown
(
getCenterTouch
(
ev
))
let
lastTouchDistance
=
getTouchDistance
(
ev
)
pinch
.
next
({
distance
:
lastTouchDistance
,
delta
:
0
})
pinch
.
next
({
distance
:
lastTouchDistance
,
delta
:
0
,
isStart
:
true
})
}
}
function
onTouchEnd
(
ev
:
TouchEvent
)
{
preventDefault
(
ev
)
console
.
log
(
'
onTouchEnd
'
,
ev
)
}
function
onTouchMove
(
ev
:
TouchEvent
)
{
preventDefault
(
ev
)
if
(
ev
.
touches
.
length
===
1
)
{
buttons
=
Mouse
ButtonsFlag
.
Primary
buttons
=
ButtonsFlag
.
Primary
onPointerMove
(
ev
.
touches
[
0
])
}
else
if
(
ev
.
touches
.
length
>=
2
)
{
buttons
=
MouseButtonsFlag
.
Secondary
onPointerDown
(
getCenterTouch
(
ev
))
const
touchDistance
=
getTouchDistance
(
ev
)
pinch
.
next
({
delta
:
lastTouchDistance
-
touchDistance
,
distance
:
touchDistance
})
if
(
lastTouchDistance
-
touchDistance
<
4
)
{
buttons
=
ButtonsFlag
.
Secondary
onPointerMove
(
getCenterTouch
(
ev
))
}
else
{
pinch
.
next
({
delta
:
lastTouchDistance
-
touchDistance
,
distance
:
touchDistance
,
isStart
:
false
})
}
lastTouchDistance
=
touchDistance
}
// if (dragging === DraggingState.Stopped || isPinching()) return
// // find currently active finger
// for (let i = 0; i < ev.changedTouches.length; i++) {
// const changed = ev.changedTouches[i]
// const idx = touchPinch.indexOfTouch(changed)
// if (idx !== -1) {
// onInputMove(changed)
// break
// }
// }
}
// function onPinchPlace ({ newTouch, oldTouch }: { newTouch?: Touch, oldTouch?: Touch }) {
// dragging = isPinching() ? DraggingState.Stopped : DraggingState.Started
// if (dragging === DraggingState.Started) {
// const firstFinger = oldTouch || newTouch
// if (firstFinger) onInputDown(firstFinger)
// }
// }
// function onPinchLift ({ removed, otherTouch }: { removed?: Touch, otherTouch?: Touch }) {
// // if either finger is down, consider it dragging
// const sum = touchPinch.fingers.reduce((sum, item) => sum + (item ? 1 : 0), 0)
// dragging = sum >= 1 ? DraggingState.Moving : DraggingState.Stopped
// if (dragging && otherTouch) {
// eventOffset(mouseStart, otherTouch, element)
// }
// }
// function isPinching () {
// return touchPinch.pinching
// }
// function onPinchChange ({ currentDistance, lastDistance }: { currentDistance: number, lastDistance: number }) {
// pinch.next({ delta: currentDistance - lastDistance })
// }
function
onMouseDown
(
ev
:
MouseEvent
)
{
preventDefault
(
ev
)
...
...
@@ -370,27 +326,21 @@ namespace InputObserver {
const
{
pageX
,
pageY
}
=
ev
const
[
x
,
y
]
=
pointerEnd
console
.
log
(
'
click
'
,
{
x
,
y
,
pageX
,
pageY
,
buttons
,
modifiers
})
click
.
next
({
x
,
y
,
pageX
,
pageY
,
buttons
,
modifiers
})
}
}
function
onPointerMove
(
ev
:
PointerEvent
)
{
eventOffset
(
pointerEnd
,
ev
)
// if (pinch && isPinching()) {
// Vec2.copy(pointerStart, pointerEnd)
// return
// }
if
(
dragging
===
DraggingState
.
Stopped
)
return
Vec2
.
div
(
pointerDelta
,
Vec2
.
sub
(
pointerDelta
,
pointerEnd
,
pointerStart
),
getClientSize
(
rectSize
))
const
s
tart
ed
=
dragging
===
DraggingState
.
Started
const
isS
tart
=
dragging
===
DraggingState
.
Started
const
{
pageX
,
pageY
}
=
ev
const
[
x
,
y
]
=
pointerEnd
const
[
dx
,
dy
]
=
pointerDelta
// console.log({ x, y, dx, dy, pageX, pageY, buttons, modifiers, started })
drag
.
next
({
x
,
y
,
dx
,
dy
,
pageX
,
pageY
,
buttons
,
modifiers
,
started
})
drag
.
next
({
x
,
y
,
dx
,
dy
,
pageX
,
pageY
,
buttons
,
modifiers
,
isStart
})
Vec2
.
copy
(
pointerStart
,
pointerEnd
)
dragging
=
DraggingState
.
Moving
...
...
This diff is collapsed.
Click to expand it.
src/mol-util/input/touch-pinch.ts
deleted
100644 → 0
+
0
−
188
View file @
d4670e0e
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
/*
* This code has been modified (use TypeScript, RxJS) from https://github.com/Jam3/touch-pinch,
* copyright (c) 2014 Matt DesLauriers. MIT License
*/
import
{
Subject
}
from
'
rxjs
'
;
import
{
Vec2
}
from
'
mol-math/linear-algebra
'
;
import
{
eventOffset
}
from
'
./event-offset
'
interface
Finger
{
position
:
Vec2
,
touch
?:
Touch
}
function
Finger
():
Finger
{
return
{
position
:
Vec2
.
zero
(),
touch
:
undefined
}
}
interface
TouchPinch
{
pinching
:
boolean
fingers
:
(
Finger
|
undefined
)[]
indexOfTouch
:
(
touch
:
Touch
)
=>
number
start
:
Subject
<
number
>
end
:
Subject
<
void
>
place
:
Subject
<
{
newTouch
?:
Touch
,
oldTouch
?:
Touch
}
>
change
:
Subject
<
{
currentDistance
:
number
,
lastDistance
:
number
}
>
lift
:
Subject
<
{
removed
:
Touch
,
otherTouch
?:
Touch
}
>
dispose
:
()
=>
void
}
namespace
TouchPinch
{
export
function
create
(
target
:
Element
):
TouchPinch
{
const
fingers
:
(
Finger
|
undefined
)[]
=
[]
let
activeCount
=
0
let
lastDistance
=
0
let
ended
=
false
let
disposed
=
false
const
start
=
new
Subject
<
number
>
()
const
end
=
new
Subject
<
void
>
()
const
place
=
new
Subject
<
{
newTouch
?:
Touch
,
oldTouch
?:
Touch
}
>
()
const
change
=
new
Subject
<
{
currentDistance
:
number
,
lastDistance
:
number
}
>
()
const
lift
=
new
Subject
<
{
removed
:
Touch
,
otherTouch
?:
Touch
}
>
()
target
.
addEventListener
(
'
touchstart
'
,
onTouchStart
as
any
,
false
)
target
.
addEventListener
(
'
touchmove
'
,
onTouchMove
as
any
,
false
)
target
.
addEventListener
(
'
touchend
'
,
onTouchRemoved
as
any
,
false
)
target
.
addEventListener
(
'
touchcancel
'
,
onTouchRemoved
as
any
,
false
)
return
{
get
pinching
()
{
return
activeCount
===
2
},
fingers
,
indexOfTouch
,
start
,
end
,
place
,
change
,
lift
,
dispose
}
function
indexOfTouch
(
touch
:
Touch
)
{
const
id
=
touch
.
identifier
for
(
let
i
=
0
;
i
<
fingers
.
length
;
i
++
)
{
const
finger
=
fingers
[
i
]
if
(
finger
&&
finger
.
touch
&&
finger
.
touch
.
identifier
===
id
)
{
return
i
}
}
return
-
1
}
function
dispose
()
{
if
(
disposed
)
return
disposed
=
true
activeCount
=
0
fingers
[
0
]
=
undefined
fingers
[
1
]
=
undefined
lastDistance
=
0
ended
=
false
target
.
removeEventListener
(
'
touchstart
'
,
onTouchStart
as
any
,
false
)
target
.
removeEventListener
(
'
touchmove
'
,
onTouchMove
as
any
,
false
)
target
.
removeEventListener
(
'
touchend
'
,
onTouchRemoved
as
any
,
false
)
target
.
removeEventListener
(
'
touchcancel
'
,
onTouchRemoved
as
any
,
false
)
}
function
onTouchStart
(
ev
:
TouchEvent
)
{
for
(
let
i
=
0
;
i
<
ev
.
changedTouches
.
length
;
i
++
)
{
const
newTouch
=
ev
.
changedTouches
[
i
]
const
idx
=
indexOfTouch
(
newTouch
)
if
(
idx
===
-
1
&&
activeCount
<
2
)
{
const
first
=
activeCount
===
0
// newest and previous finger (previous may be undefined)
const
newIndex
=
fingers
[
0
]
?
1
:
0
const
oldIndex
=
fingers
[
0
]
?
0
:
1
const
newFinger
=
Finger
()
// add to stack
fingers
[
newIndex
]
=
newFinger
activeCount
++
// update touch event & position
newFinger
.
touch
=
newTouch
eventOffset
(
newFinger
.
position
,
newTouch
,
target
)
const
finger
=
fingers
[
oldIndex
]
const
oldTouch
=
finger
?
finger
.
touch
:
undefined
place
.
next
({
newTouch
,
oldTouch
})
if
(
!
first
)
{
const
initialDistance
=
computeDistance
()
ended
=
false
start
.
next
(
initialDistance
)
lastDistance
=
initialDistance
}
}
}
}
function
onTouchMove
(
ev
:
TouchEvent
)
{
let
changed
=
false
for
(
let
i
=
0
;
i
<
ev
.
changedTouches
.
length
;
i
++
)
{
const
movedTouch
=
ev
.
changedTouches
[
i
]
const
idx
=
indexOfTouch
(
movedTouch
)
if
(
idx
!==
-
1
)
{
const
finger
=
fingers
[
idx
]
if
(
finger
)
{
changed
=
true
finger
.
touch
=
movedTouch
// avoid caching touches
eventOffset
(
finger
.
position
,
movedTouch
,
target
)
}
}
}
if
(
activeCount
===
2
&&
changed
)
{
const
currentDistance
=
computeDistance
()
change
.
next
({
currentDistance
,
lastDistance
})
lastDistance
=
currentDistance
}
}
function
onTouchRemoved
(
ev
:
TouchEvent
)
{
for
(
let
i
=
0
;
i
<
ev
.
changedTouches
.
length
;
i
++
)
{
const
removed
=
ev
.
changedTouches
[
i
]
const
idx
=
indexOfTouch
(
removed
)
if
(
idx
!==
-
1
)
{
fingers
[
idx
]
=
undefined
activeCount
--
const
otherIdx
=
idx
===
0
?
1
:
0
const
finger
=
fingers
[
otherIdx
]
if
(
finger
)
{
const
otherTouch
=
finger
?
finger
.
touch
:
undefined
lift
.
next
({
removed
,
otherTouch
})
}
}
}
if
(
!
ended
&&
activeCount
!==
2
)
{
ended
=
true
end
.
next
()
}
}
function
computeDistance
()
{
const
[
f1
,
f2
]
=
fingers
return
(
f1
&&
f2
)
?
Vec2
.
distance
(
f1
.
position
,
f2
.
position
)
:
0
}
}
}
export
default
TouchPinch
\ No newline at end of file
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment