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() {
|
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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -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) => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue