fix board camera controls
This commit is contained in:
parent
6653d19842
commit
4cc9346b83
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -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) => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue