fix board camera controls

This commit is contained in:
Jeff Emmett 2024-11-27 12:47:52 +07:00
parent 6653d19842
commit 4cc9346b83
3 changed files with 152 additions and 68 deletions

View File

@ -40,43 +40,84 @@ import { usePersistentBoard } from '@/hooks/usePersistentBoard';
export function Board() {
const { slug } = useParams<{ slug: string }>();
const roomId = slug || 'default-room';
const { store } = usePersistentBoard(roomId);
const store = usePersistentBoard(roomId);
const [editor, setEditor] = useState<Editor | null>(null)
const { zoomToFrame, copyFrameLink, copyLocationLink } = useCameraControls(editor)
const { zoomToFrame, copyFrameLink, copyLocationLink, revertCamera } = useCameraControls(editor)
return (
<div style={{ position: 'fixed', inset: 0 }}>
<Tldraw
store={store}
store={store.store}
shapeUtils={shapeUtils}
overrides={{
...uiOverrides,
tools: (_editor, baseTools) => ({
...baseTools,
frame: {
...baseTools.frame,
contextMenu: (shape: TLFrameShape) => [
{
id: 'copy-frame-link',
label: 'Copy Frame Link',
onSelect: () => copyFrameLink(shape.id),
},
{
id: 'zoom-to-frame',
label: 'Zoom to Frame',
onSelect: () => zoomToFrame(shape.id),
},
{
id: 'copy-location-link',
label: 'Copy Location Link',
onSelect: () => copyLocationLink(),
}
]
},
})
}}
components={components}
tools={tools}
components={components}
overrides={{
tools: (editor, baseTools) => ({
...baseTools,
ChatBox: {
id: 'ChatBox',
icon: 'chat',
label: 'Chat',
kbd: 'c',
readonlyOk: true,
onSelect: () => {
editor.setCurrentTool('ChatBox')
},
},
VideoChat: {
id: 'VideoChat',
icon: 'video',
label: 'Video Chat',
kbd: 'v',
readonlyOk: true,
onSelect: () => {
editor.setCurrentTool('VideoChat')
},
},
Embed: {
id: 'Embed',
icon: 'embed',
label: 'Embed',
kbd: 'e',
readonlyOk: true,
onSelect: () => {
editor.setCurrentTool('Embed')
},
},
}),
actions: (editor, actions) => ({
...actions,
'zoomToShape': {
id: 'zoom-to-shape',
label: 'Zoom to Selection',
kbd: 'z',
onSelect: () => {
if (editor.getSelectedShapeIds().length > 0) {
zoomToFrame(editor.getSelectedShapeIds()[0]);
}
},
readonlyOk: true,
},
'copyLinkToCurrentView': {
id: 'copy-link-to-current-view',
label: 'Copy Link to Current View',
kbd: 'c',
onSelect: () => {
copyLocationLink();
},
readonlyOk: true,
},
'revertCamera': {
id: 'revert-camera',
label: 'Revert Camera',
kbd: 'b',
onSelect: () => {
revertCamera();
},
readonlyOk: true,
},
}),
}}
onMount={(editor) => {
setEditor(editor)
editor.registerExternalAssetHandler('url', unfurlBookmarkUrl)

View File

@ -2,6 +2,8 @@ import { useEffect } from 'react';
import { Editor, TLFrameShape, TLParentId } from 'tldraw';
import { useSearchParams } from 'react-router-dom';
const initialCamera = { x: 0, y: 0, z: 1 };
export function useCameraControls(editor: Editor | null) {
const [searchParams] = useSearchParams();
@ -85,6 +87,10 @@ export function useCameraControls(editor: Editor | null) {
return {
zoomToFrame,
copyFrameLink,
copyLocationLink
copyLocationLink,
revertCamera: () => {
if (!editor) return
editor.setCamera(initialCamera)
}
};
}

View File

@ -87,7 +87,7 @@ const copyFrameLink = async (editor: Editor, frameId: string) => {
}
};
const zoomToShape = (editor: Editor) => {
const zoomToSelection = (editor: Editor) => {
// Store camera position before zooming
storeCameraPosition(editor);
@ -248,7 +248,7 @@ function CustomContextMenu(props: TLUiContextMenuProps) {
}}
/>
<TldrawUiMenuItem
id="zoom-to-shape"
id="zoom-to-selection"
label="Zoom to Selection"
icon="zoom-in"
kbd="z"
@ -256,14 +256,14 @@ function CustomContextMenu(props: TLUiContextMenuProps) {
disabled={!hasSelection}
onSelect={() => {
console.log('Zoom to Selection clicked');
zoomToShape(editor);
zoomToSelection(editor);
}}
/>
<TldrawUiMenuItem
id="copy-link-to-current-view"
label="Copy Link to Current View"
icon="link"
kbd="c"
kbd="s"
readonlyOk
onSelect={() => {
console.log('Copy Link to Current View clicked');
@ -278,37 +278,33 @@ function CustomContextMenu(props: TLUiContextMenuProps) {
export const uiOverrides: TLUiOverrides = {
tools(editor, tools) {
tools.VideoChat = {
id: 'VideoChat',
icon: 'color',
label: 'Video',
kbd: 'x',
meta: {},
onSelect: () => {
editor.setCurrentTool('VideoChat')
return {
...tools,
VideoChat: {
id: 'VideoChat',
icon: 'video',
label: 'Video Chat',
kbd: 'v',
readonlyOk: true,
onSelect: () => editor.setCurrentTool('VideoChat'),
},
ChatBox: {
id: 'ChatBox',
icon: 'chat',
label: 'Chat',
kbd: 'c',
readonlyOk: true,
onSelect: () => editor.setCurrentTool('ChatBox'),
},
Embed: {
id: 'Embed',
icon: 'embed',
label: 'Embed',
kbd: 'e',
readonlyOk: true,
onSelect: () => editor.setCurrentTool('Embed'),
},
}
tools.ChatBox = {
id: 'ChatBox',
icon: 'color',
label: 'Chat',
kbd: 'x',
meta: {},
onSelect: () => {
editor.setCurrentTool('ChatBox')
},
}
tools.Embed = {
id: 'Embed',
icon: 'embed',
label: 'Embed',
kbd: 'e',
meta: {},
onSelect: () => {
editor.setCurrentTool('Embed')
},
}
return tools
},
actions(editor, actions) {
actions['copyFrameLink'] = {
@ -329,7 +325,7 @@ export const uiOverrides: TLUiOverrides = {
onSelect: () => {
const shape = editor.getSelectedShapes()[0]
if (shape && shape.type === 'frame') {
zoomToShape(editor)
zoomToSelection(editor)
}
},
readonlyOk: true,
@ -353,7 +349,7 @@ export const uiOverrides: TLUiOverrides = {
onSelect: () => {
if (editor.getSelectedShapeIds().length > 0) {
console.log('Zooming to selection');
zoomToShape(editor);
zoomToSelection(editor);
}
},
readonlyOk: true,
@ -380,30 +376,71 @@ export const components: TLComponents = {
const tools = useTools()
return (
<DefaultToolbar>
<DefaultToolbarContent />
{tools['VideoChat'] && (
<TldrawUiMenuItem
{...tools['VideoChat']}
icon="video"
label="Video Chat"
isSelected={tools['VideoChat'].id === editor.getCurrentToolId()}
/>
)}
{tools['ChatBox'] && (
<TldrawUiMenuItem
{...tools['ChatBox']}
icon="chat"
label="Chat"
isSelected={tools['ChatBox'].id === editor.getCurrentToolId()}
/>
)}
{tools['Embed'] && (
<TldrawUiMenuItem
{...tools['Embed']}
icon="embed"
label="Embed"
isSelected={tools['Embed'].id === editor.getCurrentToolId()}
/>
)}
<DefaultToolbarContent />
</DefaultToolbar>
)
},
MainMenu: CustomMainMenu,
ContextMenu: CustomContextMenu,
ContextMenu: function CustomContextMenu({ ...rest }) {
const editor = useEditor()
const hasSelection = editor.getSelectedShapeIds().length > 0
const hasCameraHistory = cameraHistory.length > 0
return (
<DefaultContextMenu {...rest}>
<DefaultContextMenuContent />
<TldrawUiMenuGroup id="custom-actions">
<TldrawUiMenuItem
id="zoom-to-selection"
label="Zoom to Selection"
icon="zoom-in"
kbd="z"
disabled={!hasSelection}
onSelect={() => zoomToSelection(editor)}
/>
<TldrawUiMenuItem
id="copy-link-to-current-view"
label="Copy Link to Current View"
icon="link"
kbd="c"
onSelect={() => copyLinkToCurrentView(editor)}
/>
<TldrawUiMenuItem
id="revert-camera"
label="Revert Camera"
icon="undo"
kbd="b"
disabled={!hasCameraHistory}
onSelect={() => revertCamera(editor)}
/>
</TldrawUiMenuGroup>
</DefaultContextMenu>
)
},
}
const handleInitialShapeLoad = (editor: Editor) => {