diff --git a/editor/src/components/canvas/design-panel-root.tsx b/editor/src/components/canvas/design-panel-root.tsx index 41526aecc865..d25c868343c7 100644 --- a/editor/src/components/canvas/design-panel-root.tsx +++ b/editor/src/components/canvas/design-panel-root.tsx @@ -21,7 +21,7 @@ import { import { ConsoleAndErrorsPane } from '../code-editor/console-and-errors-pane' import { CanvasStrategyInspector } from './canvas-strategies/canvas-strategy-inspector' import { getQueryParam } from '../../common/env-vars' -import { when } from '../../utils/react-conditionals' +import { unless, when } from '../../utils/react-conditionals' import { InsertMenuPane } from '../navigator/insert-menu-pane' import { VariablesMenuPane } from '../navigator/variables-menu-pane' import { useDispatch } from '../editor/store/dispatch-context' @@ -38,6 +38,7 @@ import { MultiplayerWrapper } from '../../utils/multiplayer-wrapper' import { CommentSection } from '../inspector/sections/comment-section' import { isFeatureEnabled } from '../../utils/feature-switches' import { CommentsPane } from '../inspector/comments-pane' +import { useIsViewer } from '../editor/store/project-server-state-hooks' interface NumberSize { width: number @@ -229,6 +230,8 @@ export const RightPane = React.memo((props) => { onClickTab(RightMenuTab.Settings) }, [onClickTab]) + const isViewer = useIsViewer() + if (!isRightMenuExpanded) { return null } @@ -253,16 +256,21 @@ export const RightPane = React.memo((props) => { selected={selectedTab === RightMenuTab.Inspector} onClick={onClickInspectorTab} /> - - + {unless( + isViewer, + <> + + + , + )} {when( isFeatureEnabled('Commenting'), { } }, [updateMyPresence]) + const [roomId, setRoomId] = React.useState(room.id) + React.useEffect(() => { // when the room changes, reset - setTimeout(() => dispatch([switchEditorMode(EditorModes.selectMode(null, false, 'none'))]), 0) - }, [room.id, dispatch]) + // (unfortunately there's not a subscription event for room id changes :() + if (roomId !== room.id) { + setRoomId(room.id) + dispatch([switchEditorMode(EditorModes.selectMode(null, false, 'none'))]) + } + }, [room.id, roomId, dispatch]) if (!isLoggedIn(loginState)) { return null diff --git a/editor/src/components/editor/canvas-toolbar.tsx b/editor/src/components/editor/canvas-toolbar.tsx index 4e07b903d0e1..30a21778b228 100644 --- a/editor/src/components/editor/canvas-toolbar.tsx +++ b/editor/src/components/editor/canvas-toolbar.tsx @@ -37,14 +37,12 @@ import { } from './insert-callbacks' import { useDispatch } from './store/dispatch-context' import { Substores, useEditorState, useRefEditorState } from './store/store-hook' -import { togglePanel } from './actions/action-creators' import { defaultTransparentViewElement } from './defaults' import { generateUidWithExistingComponents } from '../../core/model/element-template-utils' import { useToolbarMode } from './canvas-toolbar-states' -import { when } from '../../utils/react-conditionals' +import { unless, when } from '../../utils/react-conditionals' import { StrategyIndicator } from '../canvas/controls/select-mode/strategy-indicator' import { toggleAbsolutePositioningCommands } from '../inspector/inspector-common' -import { NO_OP } from '../../core/shared/utils' import { createFilter } from 'react-select' import WindowedSelect from 'react-windowed-select' import { InspectorInputEmotionStyle } from '../../uuiui/inputs/base-input' @@ -70,6 +68,7 @@ import { } from '../canvas/ui/floating-insert-menu' import { isFeatureEnabled } from '../../utils/feature-switches' import { floatingInsertMenuStateSwap } from './store/editor-state' +import { useIsViewer } from './store/project-server-state-hooks' export const InsertMenuButtonTestId = 'insert-menu-button' export const PlayModeButtonTestId = 'canvas-toolbar-play-mode' @@ -410,6 +409,8 @@ export const CanvasToolbar = React.memo(() => { 'TopMenu loggedIn', ) + const isViewer = useIsViewer() + return ( { style={{ width: 36 }} /> - - - - - - + {unless( + isViewer, + <> + + + + + + + , + )} { ...handleKeyDown( event, editorStoreRef.current.editor, + editorStoreRef.current.projectServerState, metadataRef, navigatorTargetsRef, namesByKey, @@ -345,6 +356,17 @@ export const EditorComponentInner = React.memo((props: EditorProps) => { [dispatch], ) + useSelectorWithCallback( + Substores.projectServerState, + (store) => store.projectServerState.isMyProject, + (isMyProject) => { + if (isProjectViewer(isMyProject)) { + dispatch([EditorActions.switchEditorMode(EditorModes.commentMode(null, 'not-dragging'))]) + } + }, + 'EditorComponentInner viewer mode', + ) + return ( <> diff --git a/editor/src/components/editor/global-shortcuts.tsx b/editor/src/components/editor/global-shortcuts.tsx index e70ded724182..c0af7acf84d1 100644 --- a/editor/src/components/editor/global-shortcuts.tsx +++ b/editor/src/components/editor/global-shortcuts.tsx @@ -91,7 +91,7 @@ import { TOGGLE_FOCUSED_OMNIBOX_TAB, FOCUS_CLASS_NAME_INPUT, INSERT_DIV_SHORTCUT, - OPEN_EYEDROPPPER as OPEN_EYEDROPPER, + OPEN_EYEDROPPER, TEXT_EDIT_MODE, TOGGLE_TEXT_BOLD, TOGGLE_TEXT_ITALIC, @@ -146,6 +146,7 @@ import type { ElementPathTrees } from '../../core/shared/element-path-tree' import { createPasteToReplacePostActionActions } from '../canvas/canvas-strategies/post-action-options/post-action-options' import { wrapInDivStrategy } from './wrap-in-callbacks' import { isFeatureEnabled } from '../../utils/feature-switches' +import { isProjectViewerFromState, type ProjectServerState } from './store/project-server-state' function updateKeysPressed( keysPressed: KeysPressed, @@ -354,6 +355,7 @@ export function preventBrowserShortcuts(editor: EditorState, event: KeyboardEven export function handleKeyDown( event: KeyboardEvent, editor: EditorState, + projectServerState: ProjectServerState, metadataRef: { current: ElementInstanceMetadataMap }, navigatorTargetsRef: { current: Array }, namesByKey: ShortcutNamesByKey, @@ -362,6 +364,8 @@ export function handleKeyDown( // Stop the browser from firing things like save dialogs. preventBrowserShortcuts(editor, event) + const isViewer = isProjectViewerFromState(projectServerState) + // Ensure that any key presses are appropriately recorded. const key = Keyboard.keyCharacterForCode(event.keyCode) const editorTargeted = editorIsTarget(event, editor) @@ -605,6 +609,9 @@ export function handleKeyDown( return [EditorActions.toggleHidden()] }, [INSERT_IMAGE_SHORTCUT]: () => { + if (isViewer) { + return [] + } if (isSelectMode(editor.mode) || isInsertMode(editor.mode)) { // FIXME: Side effects. insertImage(dispatch) @@ -742,6 +749,9 @@ export function handleKeyDown( } }, [ADD_ELEMENT_SHORTCUT]: () => { + if (isViewer) { + return [] + } if (isSelectMode(editor.mode)) { return [ EditorActions.openFloatingInsertMenu({ @@ -775,6 +785,9 @@ export function handleKeyDown( return [] }, [TEXT_EDIT_MODE]: () => { + if (isViewer) { + return [] + } const newUID = generateUidWithExistingComponents(editor.projectContents) actions.push( @@ -949,6 +962,9 @@ export function handleKeyDown( return [EditorActions.applyCommandsAction(commands)] }, [OPEN_INSERT_MENU]: () => { + if (isViewer) { + return [] + } return [ EditorActions.setPanelVisibility('rightmenu', true), EditorActions.setRightMenuTab(RightMenuTab.Insert), diff --git a/editor/src/components/editor/shortcut-definitions.ts b/editor/src/components/editor/shortcut-definitions.ts index 6e07765668ac..38bde5578ac7 100644 --- a/editor/src/components/editor/shortcut-definitions.ts +++ b/editor/src/components/editor/shortcut-definitions.ts @@ -81,7 +81,7 @@ export const PASTE_TO_REPLACE = 'paste-to-replace' export const PASTE_STYLE_PROPERTIES = 'paste-style-properties' export const COPY_STYLE_PROPERTIES = 'copy-style-properties' -export const OPEN_EYEDROPPPER = 'open-eyedropper' +export const OPEN_EYEDROPPER = 'open-eyedropper' export const CONVERT_TO_FLEX_CONTAINER = 'convert-to-flex-container' export const REMOVE_ABSOLUTE_POSITIONING = 'remove-absolute-positioning' export const RESIZE_TO_FIT = 'resize-to-fit' @@ -218,7 +218,7 @@ const shortcutDetailsWithDefaults: ShortcutDetails = { ), [CONVERT_ELEMENT_SHORTCUT]: shortcut('Convert selected element to...', key('s', [])), [ADD_ELEMENT_SHORTCUT]: shortcut('Add element...', key('a', [])), - [OPEN_EYEDROPPPER]: shortcut('Open the eyedropper', key('c', 'ctrl')), + [OPEN_EYEDROPPER]: shortcut('Open the eyedropper', key('c', 'ctrl')), [TEXT_EDIT_MODE]: shortcut('Activate text edit mode', key('t', [])), [TOGGLE_TEXT_BOLD]: shortcut( 'Toggle font-weight to bold of the currently selected text element.', diff --git a/editor/src/components/editor/store/dispatch-strategies.tsx b/editor/src/components/editor/store/dispatch-strategies.tsx index ea4ed8b30e40..085eea03c5f2 100644 --- a/editor/src/components/editor/store/dispatch-strategies.tsx +++ b/editor/src/components/editor/store/dispatch-strategies.tsx @@ -55,6 +55,7 @@ import { last } from '../../../core/shared/array-utils' import type { BuiltInDependencies } from '../../../core/es-modules/package-manager/built-in-dependencies-list' import { isInsertMode } from '../editor-modes' import { patchedCreateRemixDerivedDataMemo } from './remix-derived-data' +import { isProjectViewerFromState } from './project-server-state' interface HandleStrategiesResult { unpatchedEditorState: EditorState @@ -657,7 +658,7 @@ export function handleStrategies( let unpatchedEditorState: EditorState let patchedEditorState: EditorState let newStrategyState: StrategyState - if (storedState.projectServerState.isMyProject === 'no') { + if (isProjectViewerFromState(storedState.projectServerState)) { unpatchedEditorState = result.unpatchedEditor patchedEditorState = result.unpatchedEditor newStrategyState = result.strategyState diff --git a/editor/src/components/editor/store/project-server-state-hooks.tsx b/editor/src/components/editor/store/project-server-state-hooks.tsx new file mode 100644 index 000000000000..ee95abaafb27 --- /dev/null +++ b/editor/src/components/editor/store/project-server-state-hooks.tsx @@ -0,0 +1,11 @@ +import { isProjectViewerFromState } from './project-server-state' +import { Substores, useEditorState } from './store-hook' + +export function useIsViewer(): boolean { + const isViewer = useEditorState( + Substores.projectServerState, + (store) => isProjectViewerFromState(store.projectServerState), + 'useIsViewer isViewer', + ) + return isViewer +} diff --git a/editor/src/components/editor/store/project-server-state.tsx b/editor/src/components/editor/store/project-server-state.tsx index d908c50fa562..4fdf2416cd98 100644 --- a/editor/src/components/editor/store/project-server-state.tsx +++ b/editor/src/components/editor/store/project-server-state.tsx @@ -25,8 +25,10 @@ export function projectMetadataFromServer( } } +export type IsMyProject = 'yes' | 'no' | 'unknown' + export interface ProjectServerState { - isMyProject: 'yes' | 'no' | 'unknown' + isMyProject: IsMyProject ownerId: string | null projectData: ProjectMetadataFromServer | null forkedFromProjectData: ProjectMetadataFromServer | null @@ -118,3 +120,11 @@ export const ProjectServerStateUpdater = React.memo( return <>{children} }, ) + +export function isProjectViewerFromState(state: ProjectServerState): boolean { + return isProjectViewer(state.isMyProject) +} + +export function isProjectViewer(isMyProject: IsMyProject): boolean { + return isMyProject === 'no' +} diff --git a/editor/src/components/navigator/dependency-list-item.tsx b/editor/src/components/navigator/dependency-list-item.tsx index ce027b95488b..c22219b6b558 100644 --- a/editor/src/components/navigator/dependency-list-item.tsx +++ b/editor/src/components/navigator/dependency-list-item.tsx @@ -14,7 +14,6 @@ import { AlternateColorThemeComponent, } from '../../uuiui' import { MenuProvider, MomentumContextMenu } from '../../uuiui-deps' -import { handleKeyDown } from '../editor/global-shortcuts' import type { DependencyPackageDetails } from '../editor/store/editor-state' import { unless } from '../../utils/react-conditionals'