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() { export function Board() {
const { slug } = useParams<{ slug: string }>(); const { slug } = useParams<{ slug: string }>();
const roomId = slug || 'default-room'; const roomId = slug || 'default-room';
const { store } = usePersistentBoard(roomId); const store = usePersistentBoard(roomId);
const [editor, setEditor] = useState<Editor | null>(null) const [editor, setEditor] = useState<Editor | null>(null)
const { zoomToFrame, copyFrameLink, copyLocationLink } = useCameraControls(editor) const { zoomToFrame, copyFrameLink, copyLocationLink, revertCamera } = useCameraControls(editor)
return ( return (
<div style={{ position: 'fixed', inset: 0 }}> <div style={{ position: 'fixed', inset: 0 }}>
<Tldraw <Tldraw
store={store} store={store.store}
shapeUtils={shapeUtils} 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} 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) => { onMount={(editor) => {
setEditor(editor) setEditor(editor)
editor.registerExternalAssetHandler('url', unfurlBookmarkUrl) editor.registerExternalAssetHandler('url', unfurlBookmarkUrl)

View File

@ -2,6 +2,8 @@ import { useEffect } from 'react';
import { Editor, TLFrameShape, TLParentId } from 'tldraw'; import { Editor, TLFrameShape, TLParentId } from 'tldraw';
import { useSearchParams } from 'react-router-dom'; import { useSearchParams } from 'react-router-dom';
const initialCamera = { x: 0, y: 0, z: 1 };
export function useCameraControls(editor: Editor | null) { export function useCameraControls(editor: Editor | null) {
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
@ -85,6 +87,10 @@ export function useCameraControls(editor: Editor | null) {
return { return {
zoomToFrame, zoomToFrame,
copyFrameLink, 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 // Store camera position before zooming
storeCameraPosition(editor); storeCameraPosition(editor);
@ -248,7 +248,7 @@ function CustomContextMenu(props: TLUiContextMenuProps) {
}} }}
/> />
<TldrawUiMenuItem <TldrawUiMenuItem
id="zoom-to-shape" id="zoom-to-selection"
label="Zoom to Selection" label="Zoom to Selection"
icon="zoom-in" icon="zoom-in"
kbd="z" kbd="z"
@ -256,14 +256,14 @@ function CustomContextMenu(props: TLUiContextMenuProps) {
disabled={!hasSelection} disabled={!hasSelection}
onSelect={() => { onSelect={() => {
console.log('Zoom to Selection clicked'); console.log('Zoom to Selection clicked');
zoomToShape(editor); zoomToSelection(editor);
}} }}
/> />
<TldrawUiMenuItem <TldrawUiMenuItem
id="copy-link-to-current-view" id="copy-link-to-current-view"
label="Copy Link to Current View" label="Copy Link to Current View"
icon="link" icon="link"
kbd="c" kbd="s"
readonlyOk readonlyOk
onSelect={() => { onSelect={() => {
console.log('Copy Link to Current View clicked'); console.log('Copy Link to Current View clicked');
@ -278,37 +278,33 @@ function CustomContextMenu(props: TLUiContextMenuProps) {
export const uiOverrides: TLUiOverrides = { export const uiOverrides: TLUiOverrides = {
tools(editor, tools) { tools(editor, tools) {
tools.VideoChat = { return {
id: 'VideoChat', ...tools,
icon: 'color', VideoChat: {
label: 'Video', id: 'VideoChat',
kbd: 'x', icon: 'video',
meta: {}, label: 'Video Chat',
onSelect: () => { kbd: 'v',
editor.setCurrentTool('VideoChat') 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(editor, actions) {
actions['copyFrameLink'] = { actions['copyFrameLink'] = {
@ -329,7 +325,7 @@ export const uiOverrides: TLUiOverrides = {
onSelect: () => { onSelect: () => {
const shape = editor.getSelectedShapes()[0] const shape = editor.getSelectedShapes()[0]
if (shape && shape.type === 'frame') { if (shape && shape.type === 'frame') {
zoomToShape(editor) zoomToSelection(editor)
} }
}, },
readonlyOk: true, readonlyOk: true,
@ -353,7 +349,7 @@ export const uiOverrides: TLUiOverrides = {
onSelect: () => { onSelect: () => {
if (editor.getSelectedShapeIds().length > 0) { if (editor.getSelectedShapeIds().length > 0) {
console.log('Zooming to selection'); console.log('Zooming to selection');
zoomToShape(editor); zoomToSelection(editor);
} }
}, },
readonlyOk: true, readonlyOk: true,
@ -380,30 +376,71 @@ export const components: TLComponents = {
const tools = useTools() const tools = useTools()
return ( return (
<DefaultToolbar> <DefaultToolbar>
<DefaultToolbarContent />
{tools['VideoChat'] && ( {tools['VideoChat'] && (
<TldrawUiMenuItem <TldrawUiMenuItem
{...tools['VideoChat']} {...tools['VideoChat']}
icon="video"
label="Video Chat"
isSelected={tools['VideoChat'].id === editor.getCurrentToolId()} isSelected={tools['VideoChat'].id === editor.getCurrentToolId()}
/> />
)} )}
{tools['ChatBox'] && ( {tools['ChatBox'] && (
<TldrawUiMenuItem <TldrawUiMenuItem
{...tools['ChatBox']} {...tools['ChatBox']}
icon="chat"
label="Chat"
isSelected={tools['ChatBox'].id === editor.getCurrentToolId()} isSelected={tools['ChatBox'].id === editor.getCurrentToolId()}
/> />
)} )}
{tools['Embed'] && ( {tools['Embed'] && (
<TldrawUiMenuItem <TldrawUiMenuItem
{...tools['Embed']} {...tools['Embed']}
icon="embed"
label="Embed"
isSelected={tools['Embed'].id === editor.getCurrentToolId()} isSelected={tools['Embed'].id === editor.getCurrentToolId()}
/> />
)} )}
<DefaultToolbarContent />
</DefaultToolbar> </DefaultToolbar>
) )
}, },
MainMenu: CustomMainMenu, 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) => { const handleInitialShapeLoad = (editor: Editor) => {