Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix comment indicators in follow mode #4781

Merged
merged 9 commits into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
190 changes: 81 additions & 109 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,8 +435,7 @@ function useDragging(
const remixSceneRoutesRef = useRefAtom(RemixNavigationAtom)

const onMouseDown = React.useCallback(
(event: React.MouseEvent) => {
setDidDrag(false)
(event: React.MouseEvent, onHandled: (dragged: boolean) => void) => {
draggingCallback(true)

const mouseDownPoint = windowPoint({ x: event.clientX, y: event.clientY })
Expand Down Expand Up @@ -494,7 +465,6 @@ function useDragging(
if (draggedPastThreshold) {
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 +478,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 +560,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
Loading