Skip to content

Commit

Permalink
Fix comment indicators in follow mode (#4781)
Browse files Browse the repository at this point in the history
* disable pan on follow mode

* canPan

* use current style

* smooth out cursors

* use canvas wrapper

* fix click

* update expect

---------

Co-authored-by: Federico Ruggi <[email protected]>
  • Loading branch information
ruggi and ruggishop authored Jan 23, 2024
1 parent 8ab79f3 commit 5a20c6f
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 113 deletions.
191 changes: 81 additions & 110 deletions editor/src/components/canvas/controls/comment-indicator.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
/** @jsxRuntime classic */
/** @jsx jsx */
import type { Interpolation } from '@emotion/react'
import { jsx } from '@emotion/react'
import type { ThreadData } from '@liveblocks/client'
import { Comment } from '@liveblocks/react-comments'
import { AnimatePresence, motion, useAnimate } from 'framer-motion'
import type { CSSProperties } from 'react'
import React from 'react'
import type { ThreadMetadata } from '../../../../liveblocks.config'
import { useEditThreadMetadata, useStorage } from '../../../../liveblocks.config'
Expand All @@ -20,7 +17,6 @@ import type {
CanvasRectangle,
LocalPoint,
MaybeInfinityCanvasRectangle,
WindowPoint,
} from '../../../core/shared/math-utils'
import {
canvasPoint,
Expand All @@ -44,7 +40,6 @@ import { optionalMap } from '../../../core/shared/optional-utils'
import type { CommentWrapperProps } from '../../../utils/multiplayer-wrapper'
import { MultiplayerWrapper, baseMultiplayerAvatarStyle } from '../../../utils/multiplayer-wrapper'
import { when } from '../../../utils/react-conditionals'
import type { Theme } from '../../../uuiui'
import { UtopiaStyles, colorTheme } from '../../../uuiui'
import {
setProp_UNSAFE,
Expand All @@ -56,7 +51,7 @@ import { useDispatch } from '../../editor/store/dispatch-context'
import { RightMenuTab } from '../../editor/store/editor-state'
import { Substores, useEditorState, useRefEditorState } from '../../editor/store/store-hook'
import { MultiplayerAvatar } from '../../user-bar'
import { canvasPointToWindowPoint, windowToCanvasCoordinates } from '../dom-lookup'
import { windowToCanvasCoordinates } from '../dom-lookup'
import {
RemixNavigationAtom,
useRemixNavigationContext,
Expand All @@ -68,6 +63,7 @@ import * as EP from '../../../core/shared/element-path'
import { useRefAtom } from '../../editor/hook-utils'
import { emptyComments, jsExpressionValue } from '../../../core/shared/element-template'
import * as PP from '../../../core/shared/property-path'
import { CanvasOffsetWrapper } from './canvas-offset-wrapper'

export const CommentIndicators = React.memo(() => {
const projectId = useEditorState(
Expand Down Expand Up @@ -95,7 +91,7 @@ export const CommentIndicators = React.memo(() => {
CommentIndicators.displayName = 'CommentIndicators'

interface TemporaryCommentIndicatorProps {
position: WindowPoint
position: CanvasPoint
bgColor: string
fgColor: string
avatarUrl: string | null
Expand All @@ -114,25 +110,9 @@ function useCommentBeingComposed(): TemporaryCommentIndicatorProps | null {
'CommentIndicatorsInner commentBeingComposed',
)

const canvasScale = useEditorState(
Substores.canvasOffset,
(store) => store.editor.canvas.scale,
'MultiplayerPresence canvasScale',
)

const canvasOffset = useEditorState(
Substores.canvasOffset,
(store) => store.editor.canvas.roundedCanvasOffset,
'MultiplayerPresence canvasOffset',
)

const { location } = useCanvasCommentThreadAndLocation(
commentBeingComposed ?? { type: 'existing', threadId: 'dummy-thread-id' }, // this is as a placeholder for nulls
)
const position = React.useMemo(
() => (location == null ? null : canvasPointToWindowPoint(location, canvasScale, canvasOffset)),
[location, canvasScale, canvasOffset],
)

const collabs = useStorage((storage) => storage.collaborators)

Expand All @@ -155,12 +135,12 @@ function useCommentBeingComposed(): TemporaryCommentIndicatorProps | null {
}
}, [collabs, myUserId])

if (position == null) {
if (location == null) {
return null
}

return {
position: position,
position: location,
bgColor: collaboratorInfo.color.background,
fgColor: collaboratorInfo.color.foreground,
avatarUrl: collaboratorInfo.avatar,
Expand All @@ -173,7 +153,7 @@ const CommentIndicatorsInner = React.memo(() => {
const temporaryIndicatorData = useCommentBeingComposed()

return (
<React.Fragment>
<CanvasOffsetWrapper setScaleToo={true}>
{threads.map((thread) => (
<CommentIndicator key={thread.id} thread={thread} />
))}
Expand All @@ -189,13 +169,13 @@ const CommentIndicatorsInner = React.memo(() => {
isRead={false}
/>
) : null}
</React.Fragment>
</CanvasOffsetWrapper>
)
})
CommentIndicatorsInner.displayName = 'CommentIndicatorInner'

interface CommentIndicatorUIProps {
position: WindowPoint
position: CanvasPoint
resolved: boolean
bgColor: string
fgColor: string
Expand All @@ -205,28 +185,32 @@ interface CommentIndicatorUIProps {
isRead: boolean
}

function getIndicatorStyle(
position: WindowPoint,
function useIndicatorStyle(
position: CanvasPoint,
params: {
isRead: boolean
resolved: boolean
isActive: boolean
expanded: boolean
dragging: boolean
},
) {
): CSSProperties {
const { isRead, resolved, isActive, expanded, dragging } = params
const canvasHeight = getCanvasHeight()

const positionAdjust = 3 // px
const canvasScale = useEditorState(
Substores.canvasOffset,
(store) => store.editor.canvas.scale,
'useIndicatorStyle canvasScale',
)

const base: Interpolation<Theme> = {
const style: CSSProperties = {
pointerEvents: dragging ? 'none' : undefined,
cursor: 'auto',
padding: 2,
position: 'fixed',
bottom: canvasHeight - position.y - positionAdjust,
position: 'absolute',
left: position.x,
top: position.y,
// transform: 'translateY(-100%)',
background: isRead ? colorTheme.bg1.value : colorTheme.primary.value,
borderRadius: '24px 24px 24px 0px',
display: 'flex',
Expand All @@ -236,36 +220,31 @@ function getIndicatorStyle(
border: '.4px solid #a3a3a340',
opacity: resolved ? 0.6 : undefined,
zIndex: expanded ? 1 : 'auto',
transform: `translateY(-100%) scale(${1 / canvasScale})`,
transformOrigin: 'bottom left',
}

const whenActive: Interpolation<Theme> = {
border: `1px solid ${colorTheme.primary.value}`,
}

const whenInactive: Interpolation<Theme> = {
':hover': {},
if (isActive) {
style.border = `1px solid ${colorTheme.primary.value}`
}

return {
...base,
...(isActive ? whenActive : whenInactive),
}
return style
}

export const CommentIndicatorUI = React.memo<CommentIndicatorUIProps>((props) => {
const { position, bgColor, fgColor, avatarUrl, avatarInitials, resolved, isActive, isRead } =
props

const style = useIndicatorStyle(position, {
isRead: isRead ?? true,
resolved: resolved,
isActive: isActive,
expanded: true,
dragging: false,
})

return (
<div
css={getIndicatorStyle(position, {
isRead: isRead ?? true,
resolved: resolved,
isActive: isActive,
expanded: true,
dragging: false,
})}
>
<div style={style}>
<MultiplayerAvatar
color={{ background: bgColor, foreground: fgColor }}
picture={avatarUrl}
Expand Down Expand Up @@ -303,7 +282,11 @@ const CommentIndicator = React.memo(({ thread }: CommentIndicatorProps) => {
const [dragging, setDragging] = React.useState(false)
const draggingCallback = React.useCallback((isDragging: boolean) => setDragging(isDragging), [])

const { onMouseDown, didDrag, dragPosition } = useDragging(thread, location, draggingCallback)
const { onMouseDown: onMouseDownDrag, dragPosition } = useDragging(
thread,
location,
draggingCallback,
)

const remixLocationRoute = React.useMemo(() => {
return thread.metadata.remixLocationRoute ?? null
Expand All @@ -315,17 +298,6 @@ const CommentIndicator = React.memo(({ thread }: CommentIndicatorProps) => {
return remixLocationRoute != null && remixLocationRoute !== remixState?.location.pathname
}, [remixLocationRoute, remixState])

const canvasScale = useEditorState(
Substores.canvasOffset,
(store) => store.editor.canvas.scale,
'CommentIndicator canvasScale',
)
const canvasOffset = useEditorState(
Substores.canvasOffset,
(store) => store.editor.canvas.roundedCanvasOffset,
'CommentIndicator canvasOffset',
)

const isActive = useEditorState(
Substores.restOfEditor,
(store) =>
Expand All @@ -340,59 +312,60 @@ const CommentIndicator = React.memo(({ thread }: CommentIndicatorProps) => {
return !isActive && (hovered || dragging)
}, [hovered, isActive, dragging])

const position = React.useMemo(
() => canvasPointToWindowPoint(dragPosition ?? location, canvasScale, canvasOffset),
[location, canvasScale, canvasOffset, dragPosition],
)

const onMouseOut = React.useCallback(() => {
if (dragging) {
return
}
onHoverMouseOut()
}, [dragging, onHoverMouseOut])

const onClick = React.useCallback(() => {
if (didDrag) {
return
}
if (isOnAnotherRoute && remixLocationRoute != null && remixState != null) {
remixState.navigate(remixLocationRoute)
}
cancelHover()
dispatch([
...openCommentThreadActions(thread.id, commentScene),
setRightMenuTab(RightMenuTab.Comments),
])
}, [
dispatch,
thread.id,
remixState,
remixLocationRoute,
isOnAnotherRoute,
commentScene,
didDrag,
cancelHover,
])
const onMouseDown = React.useCallback(
(e: React.MouseEvent) => {
onMouseDownDrag(e, (dragged) => {
if (dragged) {
return
}
if (isOnAnotherRoute && remixLocationRoute != null && remixState != null) {
remixState.navigate(remixLocationRoute)
}
cancelHover()
dispatch([
...openCommentThreadActions(thread.id, commentScene),
setRightMenuTab(RightMenuTab.Comments),
])
})
},
[
dispatch,
thread.id,
remixState,
remixLocationRoute,
isOnAnotherRoute,
commentScene,
cancelHover,
onMouseDownDrag,
],
)

// This is a hack: when the comment is unread, we show a dark background, so we need light foreground colors.
// So we trick the Liveblocks Comment component and lie to it that the theme is dark mode.
const dataThemeProp = isRead ? {} : { 'data-theme': 'dark' }

const style = useIndicatorStyle(dragPosition ?? location, {
isRead: isRead,
resolved: thread.metadata.resolved,
isActive: isActive,
expanded: preview,
dragging: dragging,
})

return (
<div
onMouseOver={onMouseOver}
onMouseOut={onMouseOut}
onMouseDown={onMouseDown}
onClick={onClick}
data-testid='comment-indicator'
css={getIndicatorStyle(position, {
isRead: isRead,
resolved: thread.metadata.resolved,
isActive: isActive,
expanded: preview,
dragging: dragging,
})}
style={style}
>
<CommentIndicatorWrapper
thread={thread}
Expand Down Expand Up @@ -450,7 +423,6 @@ function useDragging(
) {
const editThreadMetadata = useEditThreadMetadata()
const [dragPosition, setDragPosition] = React.useState<CanvasPoint | null>(null)
const [didDrag, setDidDrag] = React.useState(false)

const canvasScaleRef = useRefEditorState((store) => store.editor.canvas.scale)
const canvasOffsetRef = useRefEditorState((store) => store.editor.canvas.roundedCanvasOffset)
Expand All @@ -463,9 +435,7 @@ function useDragging(
const remixSceneRoutesRef = useRefAtom(RemixNavigationAtom)

const onMouseDown = React.useCallback(
(event: React.MouseEvent) => {
setDidDrag(false)

(event: React.MouseEvent, onHandled: (dragged: boolean) => void) => {
const mouseDownPoint = windowPoint({ x: event.clientX, y: event.clientY })
const mouseDownCanvasPoint = windowToCanvasCoordinates(
canvasScaleRef.current,
Expand Down Expand Up @@ -494,7 +464,6 @@ function useDragging(
draggingCallback(true)
dispatch([switchEditorMode(EditorModes.commentMode(null, 'dragging'))])

setDidDrag(true)
const dragVectorWindow = pointDifference(mouseDownPoint, mouseMovePoint)
const dragVectorCanvas = canvasPoint({
x: dragVectorWindow.x / canvasScaleRef.current,
Expand All @@ -508,10 +477,12 @@ function useDragging(
upEvent.stopPropagation()
window.removeEventListener('mousemove', onMouseMove)
window.removeEventListener('mouseup', onMouseUp)
draggingCallback(false)

const mouseUpPoint = windowPoint({ x: upEvent.clientX, y: upEvent.clientY })

draggingCallback(false)
onHandled(draggedPastThreshold)

if (!draggedPastThreshold) {
return
}
Expand Down Expand Up @@ -588,7 +559,7 @@ function useDragging(
],
)

return { onMouseDown, dragPosition, didDrag }
return { onMouseDown, dragPosition }
}

function useHover() {
Expand Down
4 changes: 3 additions & 1 deletion editor/src/templates/editor-canvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,9 @@ function on(
}

if (isDragToPan(canvas.editorState.canvas.interactionSession, canvas.keysPressed['space'])) {
if (event.event === 'MOVE' && event.nativeEvent.buttons === 1) {
const canPan =
!isFollowMode(canvas.mode) && event.event === 'MOVE' && event.nativeEvent.buttons === 1
if (canPan) {
return [
CanvasActions.scrollCanvas(
canvasPoint({
Expand Down
Loading

0 comments on commit 5a20c6f

Please sign in to comment.