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 12 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
23 changes: 18 additions & 5 deletions editor/liveblocks.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,17 +95,30 @@ 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;
export type ThreadMetadata = CanvasThreadMetadata | SceneThreadMetadata

export type SceneThreadMetadata = BaseThreadMetadata & {
type: 'scene'
sceneId: string
sceneX: number
sceneY: number
}

export type CanvasThreadMetadata = BaseThreadMetadata & {
type: 'canvas'
x: number // x and y is global when sceneId is undefined, and local to the scene when sceneId is not null
}

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

export function isSceneThreadMetadata(metadata: ThreadMetadata): metadata is SceneThreadMetadata {
return metadata.type === 'scene'
}

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 @@ -384,36 +378,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 @@ -448,7 +412,7 @@ function useDragging(
scenesRef.current,
)

const originalThreadPosition = getThreadOriginalLocationOnCanvas(
const originalThreadPosition = getThreadLocationOnCanvas(
thread,
maybeStartingSceneUnderPoint?.globalFrame ?? null,
)
Expand Down Expand Up @@ -534,9 +498,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
10 changes: 6 additions & 4 deletions editor/src/components/canvas/controls/comment-popup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,8 @@ const CommentThread = React.memo(({ comment }: CommentThreadProps) => {
const newThreadOnCanvas = createThread({
body,
metadata: {
resolved: false,
type: 'canvas',
resolved: false,
x: comment.location.position.x,
y: comment.location.position.y,
},
Expand Down Expand Up @@ -183,11 +183,13 @@ const CommentThread = React.memo(({ comment }: CommentThreadProps) => {
const newThreadOnScene = createThread({
body,
metadata: {
type: 'scene',
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
70 changes: 55 additions & 15 deletions editor/src/core/commenting/comment-hooks.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import React from 'react'
import type { User } from '@liveblocks/client'
import { LiveObject, type ThreadData } from '@liveblocks/client'
import type { Presence, ThreadMetadata, UserMeta } from '../../../liveblocks.config'
import type {
Presence,
SceneThreadMetadata,
ThreadMetadata,
UserMeta,
} from '../../../liveblocks.config'
import {
isSceneThreadMetadata,
useEditThreadMetadata,
useMutation,
useSelf,
Expand All @@ -14,18 +20,23 @@ import { normalizeMultiplayerName, possiblyUniqueColor } from '../shared/multipl
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 @@ -64,7 +75,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 @@ -75,18 +86,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 @@ -209,16 +222,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 @@ -388,3 +402,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