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

Store canvas position of scene comments. #4791

Merged
merged 16 commits into from
Jan 25, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
24 changes: 18 additions & 6 deletions editor/liveblocks.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,17 +95,29 @@ export type RoomEvent = ControlChangedRoomEvent

// Optionally, when using Comments, ThreadMetadata represents metadata on
// each thread. Can only contain booleans, strings, and numbers.
export type ThreadMetadata = {
// quote: string;
// time: number;
type: 'canvas'
x: number // x and y is global when sceneId is undefined, and local to the scene when sceneId is not null
export type ThreadMetadata = CanvasThreadMetadata | SceneThreadMetadata

export type SceneThreadMetadata = {
x: number
y: number
sceneId: string
sceneX: number
sceneY: number
remixLocationRoute?: string
resolved: boolean
}

export type CanvasThreadMetadata = {
x: number
y: number
sceneId?: string
remixLocationRoute?: string
resolved: boolean
}

export function isSceneThreadMetadata(metadata: ThreadMetadata): metadata is SceneThreadMetadata {
return metadata.hasOwnProperty('sceneId')
}

export const {
suspense: {
RoomProvider,
Expand Down
48 changes: 7 additions & 41 deletions editor/src/components/canvas/controls/comment-indicator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,18 @@ import type { ThreadMetadata } from '../../../../liveblocks.config'
import { useEditThreadMetadata, useStorage } from '../../../../liveblocks.config'
import {
getCollaboratorById,
getThreadLocationOnCanvas,
useActiveThreads,
useCanvasCommentThreadAndLocation,
useCanvasLocationOfThread,
useMyThreadReadStatus,
} from '../../../core/commenting/comment-hooks'
import type {
CanvasPoint,
CanvasRectangle,
LocalPoint,
MaybeInfinityCanvasRectangle,
} from '../../../core/shared/math-utils'
import type { CanvasPoint } from '../../../core/shared/math-utils'
import {
canvasPoint,
distance,
getLocalPointInNewParentContext,
isNotNullFiniteRectangle,
localPoint,
nullIfInfinity,
offsetPoint,
pointDifference,
windowPoint,
Expand Down Expand Up @@ -369,36 +363,6 @@ const CommentIndicator = React.memo(({ thread }: CommentIndicatorProps) => {
})
CommentIndicator.displayName = 'CommentIndicator'

function canvasPositionOfThread(
sceneGlobalFrame: CanvasRectangle,
locationInScene: LocalPoint,
): CanvasPoint {
return canvasPoint({
x: sceneGlobalFrame.x + locationInScene.x,
y: sceneGlobalFrame.y + locationInScene.y,
})
}

function getThreadOriginalLocationOnCanvas(
thread: ThreadData<ThreadMetadata>,
startingSceneGlobalFrame: MaybeInfinityCanvasRectangle | null,
): CanvasPoint {
const sceneId = thread.metadata.sceneId
if (sceneId == null) {
return canvasPoint({ x: thread.metadata.x, y: thread.metadata.y })
}

const globalFrame = nullIfInfinity(startingSceneGlobalFrame)
if (globalFrame == null) {
throw new Error('Found thread attached to scene with invalid global frame')
}

return canvasPositionOfThread(
globalFrame,
localPoint({ x: thread.metadata.x, y: thread.metadata.y }),
)
}

const COMMENT_DRAG_THRESHOLD = 5 // square px

function useDragging(
Expand Down Expand Up @@ -433,7 +397,7 @@ function useDragging(
scenesRef.current,
)

const originalThreadPosition = getThreadOriginalLocationOnCanvas(
const originalThreadPosition = getThreadLocationOnCanvas(
thread,
maybeStartingSceneUnderPoint?.globalFrame ?? null,
)
Expand Down Expand Up @@ -519,9 +483,11 @@ function useDragging(
editThreadMetadata({
threadId: thread.id,
metadata: {
x: localPointInScene.x,
y: localPointInScene.y,
sceneX: localPointInScene.x,
sceneY: localPointInScene.y,
sceneId: sceneIdToUse,
x: newPositionOnCanvas.x,
y: newPositionOnCanvas.y,
remixLocationRoute: remixRoute != null ? remixRoute.location.pathname : undefined,
},
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export function useCommentModeSelectAndHover(comment: CommentId | null): MouseCa
setHighlightedView(scene.elementPath),
switchEditorMode(
EditorModes.commentMode(
newComment(sceneCommentLocation(sceneIdToUse, offset)),
newComment(sceneCommentLocation(sceneIdToUse, offset, loc.canvasPositionRounded)),
'not-dragging',
),
),
Expand Down
8 changes: 4 additions & 4 deletions editor/src/components/canvas/controls/comment-popup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,6 @@ const CommentThread = React.memo(({ comment }: CommentThreadProps) => {
body,
metadata: {
resolved: false,
type: 'canvas',
x: comment.location.position.x,
y: comment.location.position.y,
},
Expand Down Expand Up @@ -184,10 +183,11 @@ const CommentThread = React.memo(({ comment }: CommentThreadProps) => {
body,
metadata: {
resolved: false,
type: 'canvas',
x: comment.location.offset.x,
y: comment.location.offset.y,
x: comment.location.position.x,
y: comment.location.position.x,
sceneId: sceneId,
sceneX: comment.location.offset.x,
sceneY: comment.location.offset.y,
remixLocationRoute: remixRoute != null ? remixRoute.location.pathname : undefined,
},
})
Expand Down
2 changes: 2 additions & 0 deletions editor/src/components/editor/editor-component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ import { useDataThemeAttributeOnBody } from '../../core/commenting/comment-hooks
import { CollaborationStateUpdater } from './store/collaboration-state'
import { GithubRepositoryCloneFlow } from '../github/github-repository-clone-flow'
import { getPermissions } from './store/permissions'
import { CommentMaintainer } from '../../core/commenting/comment-maintainer'
import { useIsLoggedIn } from '../../core/shared/multiplayer-hooks'

const liveModeToastId = 'play-mode-toast'
Expand Down Expand Up @@ -483,6 +484,7 @@ export const EditorComponentInner = React.memo((props: EditorProps) => {
keyDown={onWindowKeyDown}
keyUp={onWindowKeyUp}
/>
<CommentMaintainer />
</>
)
})
Expand Down
8 changes: 7 additions & 1 deletion editor/src/components/editor/editor-modes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,13 +116,19 @@ export interface SceneCommentLocation {
type: 'scene'
sceneId: string
offset: LocalPoint
position: CanvasPoint
}

export function sceneCommentLocation(sceneId: string, offset: LocalPoint): SceneCommentLocation {
export function sceneCommentLocation(
sceneId: string,
offset: LocalPoint,
position: CanvasPoint,
): SceneCommentLocation {
return {
type: 'scene',
sceneId: sceneId,
offset: offset,
position: position,
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3231,11 +3231,13 @@ export const CanvasCommentLocationKeepDeepEquality: KeepDeepEqualityCall<CanvasC
combine1EqualityCall((loc) => loc.position, CanvasPointKeepDeepEquality, canvasCommentLocation)

export const SceneCommentLocationKeepDeepEquality: KeepDeepEqualityCall<SceneCommentLocation> =
combine2EqualityCalls(
combine3EqualityCalls(
(loc) => loc.sceneId,
StringKeepDeepEquality,
(loc) => loc.offset,
LocalPointKeepDeepEquality,
(loc) => loc.position,
CanvasPointKeepDeepEquality,
sceneCommentLocation,
)

Expand Down
73 changes: 57 additions & 16 deletions editor/src/core/commenting/comment-hooks.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,43 @@
import React from 'react'
import type { User } from '@liveblocks/client'
import { LiveObject, type ThreadData } from '@liveblocks/client'
import type { ConnectionInfo, Presence, ThreadMetadata, UserMeta } from '../../../liveblocks.config'
import type {
ConnectionInfo,
Presence,
SceneThreadMetadata,
ThreadMetadata,
UserMeta,
} from '../../../liveblocks.config'
import {
isSceneThreadMetadata,
useEditThreadMetadata,
useMutation,
useSelf,
useStorage,
useThreads,
} from '../../../liveblocks.config'
import { Substores, useEditorState } from '../../components/editor/store/store-hook'
import { normalizeMultiplayerName, possiblyUniqueColor } from '../shared/multiplayer'
import { normalizeMultiplayerName } from '../shared/multiplayer'
import { isLoggedIn } from '../../common/user'
import type { CommentId, SceneCommentLocation } from '../../components/editor/editor-modes'
import { assertNever } from '../shared/utils'
import type { CanvasPoint } from '../shared/math-utils'
import type {
CanvasPoint,
CanvasRectangle,
LocalPoint,
MaybeInfinityCanvasRectangle,
} from '../shared/math-utils'
import {
canvasPoint,
getCanvasPointWithCanvasOffset,
isNotNullFiniteRectangle,
localPoint,
zeroCanvasPoint,
nullIfInfinity,
} from '../shared/math-utils'
import { MetadataUtils } from '../model/element-metadata-utils'
import { getIdOfScene } from '../../components/canvas/controls/comment-mode/comment-mode-hooks'
import type { ElementPath } from '../shared/project-file-types'
import type { ElementInstanceMetadata } from '../shared/element-template'
import { type ElementInstanceMetadata } from '../shared/element-template'
import * as EP from '../shared/element-path'
import { getCurrentTheme } from '../../components/editor/store/editor-state'
import { useMyUserId } from '../shared/multiplayer-hooks'
Expand Down Expand Up @@ -67,7 +79,7 @@ export function useCanvasCommentThreadAndLocation(comment: CommentId): {
})

if (scene == null || !isNotNullFiniteRectangle(scene.globalFrame)) {
return getCanvasPointWithCanvasOffset(zeroCanvasPoint, comment.location.offset)
return comment.location.position
}
return getCanvasPointWithCanvasOffset(scene.globalFrame, comment.location.offset)
default:
Expand All @@ -78,18 +90,20 @@ export function useCanvasCommentThreadAndLocation(comment: CommentId): {
if (thread == null) {
return null
}
if (thread.metadata.sceneId == null) {
return canvasPoint(thread.metadata)

const { metadata } = thread
if (!isSceneThreadMetadata(metadata)) {
return canvasPoint(metadata)
}
const scene = scenes.find((s) => getIdOfScene(s) === thread.metadata.sceneId)
const scene = scenes.find((s) => getIdOfScene(s) === metadata.sceneId)

if (scene == null) {
return canvasPoint(thread.metadata)
}
if (!isNotNullFiniteRectangle(scene.globalFrame)) {
return canvasPoint(thread.metadata)
return canvasPoint(metadata)
}
return getCanvasPointWithCanvasOffset(scene.globalFrame, localPoint(thread.metadata))
return getCanvasPointWithCanvasOffset(scene.globalFrame, positionInScene(metadata))

default:
assertNever(comment)
Expand Down Expand Up @@ -261,16 +275,17 @@ export function useCanvasLocationOfThread(thread: ThreadData<ThreadMetadata>): {
} {
const scenes = useScenesWithId()

if (thread.metadata.sceneId == null) {
return { location: canvasPoint(thread.metadata), scene: null }
const { metadata } = thread
if (!isSceneThreadMetadata(metadata)) {
return { location: canvasPoint(metadata), scene: null }
}
const scene = scenes.find((s) => getIdOfScene(s) === thread.metadata.sceneId)
const scene = scenes.find((s) => getIdOfScene(s) === metadata.sceneId)

if (scene == null || !isNotNullFiniteRectangle(scene.globalFrame)) {
return { location: canvasPoint(thread.metadata), scene: null }
return { location: canvasPoint(metadata), scene: null }
}
return {
location: getCanvasPointWithCanvasOffset(scene.globalFrame, localPoint(thread.metadata)),
location: getCanvasPointWithCanvasOffset(scene.globalFrame, positionInScene(metadata)),
scene: scene.elementPath,
}
}
Expand Down Expand Up @@ -440,3 +455,29 @@ export function useCanComment() {

return isFeatureEnabled('Multiplayer') && canComment
}

export function getThreadLocationOnCanvas(
thread: ThreadData<ThreadMetadata>,
startingSceneGlobalFrame: MaybeInfinityCanvasRectangle | null,
): CanvasPoint {
const globalFrame = nullIfInfinity(startingSceneGlobalFrame)
if (!isSceneThreadMetadata(thread.metadata) || globalFrame == null) {
return canvasPoint(thread.metadata)
}

return canvasPositionOfThread(globalFrame, positionInScene(thread.metadata))
}

function canvasPositionOfThread(
sceneGlobalFrame: CanvasRectangle,
locationInScene: LocalPoint,
): CanvasPoint {
return canvasPoint({
x: sceneGlobalFrame.x + locationInScene.x,
y: sceneGlobalFrame.y + locationInScene.y,
})
}

export function positionInScene(metadata: SceneThreadMetadata): LocalPoint {
return localPoint({ x: metadata.sceneX, y: metadata.sceneY })
}
Loading
Loading