From 20b3f804acd47ccce0553ac89f17944f80946da6 Mon Sep 17 00:00:00 2001 From: Balint Gabor <127662+gbalint@users.noreply.github.com> Date: Wed, 6 Dec 2023 15:16:35 +0100 Subject: [PATCH] Connect remix scene to commenting --- .../canvas/controls/comment-indicator.tsx | 48 +++++++------------ .../canvas/controls/comment-popup.tsx | 22 ++++++--- .../remix/utopia-remix-root-component.tsx | 8 ++++ .../inspector/sections/comment-section.tsx | 31 +++++------- editor/src/core/commenting/comment-hooks.tsx | 25 ++++++---- 5 files changed, 68 insertions(+), 66 deletions(-) diff --git a/editor/src/components/canvas/controls/comment-indicator.tsx b/editor/src/components/canvas/controls/comment-indicator.tsx index cacaa915c5e4..e2b25a501cf4 100644 --- a/editor/src/components/canvas/controls/comment-indicator.tsx +++ b/editor/src/components/canvas/controls/comment-indicator.tsx @@ -2,16 +2,11 @@ /** @jsx jsx */ import { jsx } from '@emotion/react' import type { ThreadData } from '@liveblocks/client' -import { useAtom } from 'jotai' import React from 'react' import type { ThreadMetadata } from '../../../../liveblocks.config' import { useEditThreadMetadata, useStorage } from '../../../../liveblocks.config' -import { - useCanvasLocationOfThread, - useIsOnAnotherRemixRoute, - useActiveThreads, -} from '../../../core/commenting/comment-hooks' -import type { CanvasPoint, CanvasVector, WindowPoint } from '../../../core/shared/math-utils' +import { useActiveThreads, useCanvasLocationOfThread } from '../../../core/commenting/comment-hooks' +import type { CanvasPoint } from '../../../core/shared/math-utils' import { canvasPoint, distance, @@ -27,14 +22,13 @@ import { } from '../../../core/shared/multiplayer' import { MultiplayerWrapper } from '../../../utils/multiplayer-wrapper' import { UtopiaStyles } from '../../../uuiui' -import { setHighlightedView, switchEditorMode } from '../../editor/actions/action-creators' -import { EditorModes, existingComment } from '../../editor/editor-modes' +import { switchEditorMode } from '../../editor/actions/action-creators' +import { EditorModes } from '../../editor/editor-modes' import { useDispatch } from '../../editor/store/dispatch-context' import { Substores, useEditorState, useRefEditorState } from '../../editor/store/store-hook' import { AvatarPicture } from '../../user-bar' -import { canvasPointToWindowPoint, windowToCanvasCoordinates } from '../dom-lookup' -import { RemixNavigationAtom } from '../remix/utopia-remix-root-component' -import { stripNulls } from '../../../core/shared/array-utils' +import { canvasPointToWindowPoint } from '../dom-lookup' +import { useRemixNavigationContext } from '../remix/utopia-remix-root-component' const IndicatorSize = 20 @@ -95,35 +89,25 @@ const CommentIndicator = React.memo(({ thread }: CommentIndicatorProps) => { 'CommentIndicator canvasOffset', ) - const [remixNavigationState] = useAtom(RemixNavigationAtom) - const { location, scene: commentScene } = useCanvasLocationOfThread(thread) const remixLocationRoute = thread.metadata.remixLocationRoute ?? null - const isOnAnotherRoute = useIsOnAnotherRemixRoute(remixLocationRoute) + const remixState = useRemixNavigationContext(commentScene) + + const isOnAnotherRoute = + remixLocationRoute != null && remixLocationRoute !== remixState?.location.pathname const onClick = React.useCallback(() => { - if (isOnAnotherRoute && remixLocationRoute != null) { - // TODO: after we have scene identifier in the comment metadata we should only navigate the scene with the comment - Object.keys(remixNavigationState).forEach((scene) => { - const remixState = remixNavigationState[scene] - if (remixState == null) { - return - } - remixState.navigate(remixLocationRoute) - }) + if (isOnAnotherRoute) { + if (remixState == null) { + return + } + remixState.navigate(remixLocationRoute) } dispatch(openCommentThreadActions(thread.id, commentScene)) - }, [ - dispatch, - thread.id, - remixNavigationState, - remixLocationRoute, - isOnAnotherRoute, - commentScene, - ]) + }, [dispatch, thread.id, remixState, remixLocationRoute, isOnAnotherRoute, commentScene]) const { initials, color, avatar } = (() => { const firstComment = thread.comments[0] diff --git a/editor/src/components/canvas/controls/comment-popup.tsx b/editor/src/components/canvas/controls/comment-popup.tsx index a98297ac0d9f..351893f27e3f 100644 --- a/editor/src/components/canvas/controls/comment-popup.tsx +++ b/editor/src/components/canvas/controls/comment-popup.tsx @@ -10,8 +10,8 @@ import { getCollaboratorById, useCanvasCommentThreadAndLocation, useResolveThread, + useScenesWithId, } from '../../../core/commenting/comment-hooks' -import { useRemixPresence } from '../../../core/shared/multiplayer-hooks' import { CommentWrapper, MultiplayerWrapper } from '../../../utils/multiplayer-wrapper' import { switchEditorMode } from '../../editor/actions/action-creators' import type { CommentId } from '../../editor/editor-modes' @@ -26,6 +26,10 @@ import { Substores, useEditorState } from '../../editor/store/store-hook' import { canvasPointToWindowPoint } from '../dom-lookup' import { assertNever } from '../../../core/shared/utils' import { when } from '../../../utils/react-conditionals' +import { useAtom } from 'jotai' +import { RemixNavigationAtom } from '../remix/utopia-remix-root-component' +import { getIdOfScene } from './comment-mode/comment-mode-hooks' +import * as EP from '../../../core/shared/element-path' export const CommentPopup = React.memo(() => { const mode = useEditorState( @@ -66,7 +70,8 @@ const CommentThread = React.memo(({ comment }: CommentThreadProps) => { const createThread = useCreateThread() - const remixPresence = useRemixPresence() + const scenes = useScenesWithId() + const [remixSceneRoutes] = useAtom(RemixNavigationAtom) const onCreateThread = React.useCallback( ({ body }: ComposerSubmitComment, event: React.FormEvent) => { @@ -75,6 +80,7 @@ const CommentThread = React.memo(({ comment }: CommentThreadProps) => { if (!isNewComment(comment)) { return } + // Create a new thread const newThread = (() => { switch (comment.location.type) { @@ -86,10 +92,14 @@ const CommentThread = React.memo(({ comment }: CommentThreadProps) => { type: 'canvas', x: comment.location.position.x, y: comment.location.position.y, - remixLocationRoute: remixPresence?.locationRoute ?? undefined, }, }) case 'scene': + const sceneId = comment.location.sceneId + const scene = scenes.find((s) => getIdOfScene(s) === sceneId) + const remixRoute = + scene != null ? remixSceneRoutes[EP.toString(scene?.elementPath)] : undefined + return createThread({ body, metadata: { @@ -97,8 +107,8 @@ const CommentThread = React.memo(({ comment }: CommentThreadProps) => { type: 'canvas', x: comment.location.offset.x, y: comment.location.offset.y, - sceneId: comment.location.sceneId, - remixLocationRoute: remixPresence?.locationRoute ?? undefined, + sceneId: sceneId, + remixLocationRoute: remixRoute != null ? remixRoute.location.pathname : undefined, }, }) default: @@ -109,7 +119,7 @@ const CommentThread = React.memo(({ comment }: CommentThreadProps) => { switchEditorMode(EditorModes.commentMode(existingComment(newThread.id), 'not-dragging')), ]) }, - [createThread, comment, remixPresence, dispatch], + [createThread, comment, dispatch, remixSceneRoutes, scenes], ) const onCommentDelete = React.useCallback( diff --git a/editor/src/components/canvas/remix/utopia-remix-root-component.tsx b/editor/src/components/canvas/remix/utopia-remix-root-component.tsx index 9da5186e06c7..295b976c98b4 100644 --- a/editor/src/components/canvas/remix/utopia-remix-root-component.tsx +++ b/editor/src/components/canvas/remix/utopia-remix-root-component.tsx @@ -36,6 +36,14 @@ export interface RemixNavigationAtomData { export const ActiveRemixSceneAtom = atom(EP.emptyElementPath) export const RemixNavigationAtom = atom({}) +export function useRemixNavigationContext( + scenePath: ElementPath | null, +): RemixNavigationContext | null { + const [remixNavigationState] = useAtom(RemixNavigationAtom) + const remixContext = scenePath != null ? remixNavigationState[EP.toString(scenePath)] : null + return remixContext ?? null +} + function useGetRouteModules(basePath: ElementPath) { const remixDerivedDataRef = useRefEditorState((store) => store.derived.remixData) const projectContentsRef = useRefEditorState((store) => store.editor.projectContents) diff --git a/editor/src/components/inspector/sections/comment-section.tsx b/editor/src/components/inspector/sections/comment-section.tsx index 5fcf0dad0c4c..dc59d35dfcd1 100644 --- a/editor/src/components/inspector/sections/comment-section.tsx +++ b/editor/src/components/inspector/sections/comment-section.tsx @@ -19,11 +19,9 @@ import { } from '../../editor/actions/action-creators' import { EditorModes, isCommentMode, isExistingComment } from '../../editor/editor-modes' import { CommentWrapper, MultiplayerWrapper } from '../../../utils/multiplayer-wrapper' -import { useAtom } from 'jotai' -import { RemixNavigationAtom } from '../../canvas/remix/utopia-remix-root-component' +import { useRemixNavigationContext } from '../../canvas/remix/utopia-remix-root-component' import { useUnresolvedThreads, - useIsOnAnotherRemixRoute, useResolveThread, useResolvedThreads, useCanvasLocationOfThread, @@ -106,12 +104,9 @@ interface ThreadPreviewProps { const ThreadPreview = React.memo(({ thread }: ThreadPreviewProps) => { const dispatch = useDispatch() const colorTheme = useColorTheme() - const [remixNavigationState] = useAtom(RemixNavigationAtom) const { remixLocationRoute } = thread.metadata - const isOnAnotherRoute = useIsOnAnotherRemixRoute(remixLocationRoute ?? null) - const isSelected = useEditorState( Substores.restOfEditor, (store) => @@ -124,28 +119,28 @@ const ThreadPreview = React.memo(({ thread }: ThreadPreviewProps) => { const { location, scene: commentScene } = useCanvasLocationOfThread(thread) - const onClick = React.useCallback(() => { - const rect = canvasRectangle({ x: location.x, y: location.y, width: 25, height: 25 }) + const remixState = useRemixNavigationContext(commentScene) - if (isOnAnotherRoute && remixLocationRoute != null) { - // TODO: after we have scene identifier in the comment metadata we should only navigate the scene with the comment - Object.keys(remixNavigationState).forEach((scene) => { - const remixState = remixNavigationState[scene] - if (remixState == null) { - return - } - remixState.navigate(remixLocationRoute) - }) + const isOnAnotherRoute = + remixLocationRoute != null && remixLocationRoute !== remixState?.location.pathname + + const onClick = React.useCallback(() => { + if (isOnAnotherRoute) { + if (remixState == null) { + return + } + remixState.navigate(remixLocationRoute) } + const rect = canvasRectangle({ x: location.x, y: location.y, width: 25, height: 25 }) dispatch([ ...openCommentThreadActions(thread.id, commentScene), scrollToPosition(rect, 'to-center'), ]) }, [ dispatch, - remixNavigationState, isOnAnotherRoute, remixLocationRoute, + remixState, location, thread.id, commentScene, diff --git a/editor/src/core/commenting/comment-hooks.tsx b/editor/src/core/commenting/comment-hooks.tsx index 5d7c9ef2cc60..97f91c4234c1 100644 --- a/editor/src/core/commenting/comment-hooks.tsx +++ b/editor/src/core/commenting/comment-hooks.tsx @@ -25,6 +25,7 @@ import { 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' export function useCanvasCommentThreadAndLocation(comment: CommentId): { location: CanvasPoint | null @@ -177,24 +178,28 @@ export function useCollaborators() { return useStorage((store) => store.collaborators) } -export function useIsOnAnotherRemixRoute(remixLocationRoute: string | null) { - const me = useSelf() - - return ( - me.presence.remix?.locationRoute != null && - remixLocationRoute != null && - remixLocationRoute !== me.presence.remix.locationRoute +export function useScenesWithId(): Array { + return useEditorState( + Substores.metadata, + (store) => { + const scenes = MetadataUtils.getScenesMetadata(store.editor.jsxMetadata) + return scenes.filter((s) => getIdOfScene(s) != null) + }, + 'useScenesWithId scenes', ) } -export function useScenesWithId() { +export function useSceneWithId(sceneId: string | null): ElementInstanceMetadata | null { return useEditorState( Substores.metadata, (store) => { + if (sceneId == null) { + return null + } const scenes = MetadataUtils.getScenesMetadata(store.editor.jsxMetadata) - return scenes.filter((s) => getIdOfScene(s) != null) + return scenes.find((s) => getIdOfScene(s) != sceneId) ?? null }, - 'useScenesWithId scenes', + 'useSceneWithId scene', ) }