cleanup tools/menu/actions
This commit is contained in:
parent
94bec533c4
commit
923f61ac9e
43
src/App.tsx
43
src/App.tsx
|
|
@ -15,45 +15,22 @@ import { Board } from './components/Board';
|
||||||
import { Inbox } from './components/Inbox';
|
import { Inbox } from './components/Inbox';
|
||||||
import { Books } from './components/Books';
|
import { Books } from './components/Books';
|
||||||
import {
|
import {
|
||||||
BindingUtil,
|
|
||||||
Editor,
|
Editor,
|
||||||
IndexKey,
|
|
||||||
TLBaseBinding,
|
|
||||||
TLBaseShape,
|
|
||||||
Tldraw,
|
Tldraw,
|
||||||
TLShapeId,
|
TLShapeId,
|
||||||
} from 'tldraw';
|
} from 'tldraw';
|
||||||
import { components, uiOverrides } from './ui-overrides';
|
import { components, overrides } from './ui-overrides'
|
||||||
import { ChatBoxShape } from './shapes/ChatBoxShapeUtil';
|
import { ChatBoxShape } from './shapes/ChatBoxShapeUtil';
|
||||||
import { VideoChatShape } from './shapes/VideoChatShapeUtil';
|
import { VideoChatShape } from './shapes/VideoChatShapeUtil';
|
||||||
import { ChatBoxTool } from './tools/ChatBoxTool';
|
import { ChatBoxTool } from './tools/ChatBoxTool';
|
||||||
import { VideoChatTool } from './tools/VideoChatTool';
|
import { VideoChatTool } from './tools/VideoChatTool';
|
||||||
|
import { EmbedTool } from './tools/EmbedTool';
|
||||||
|
import { EmbedShape } from './shapes/EmbedShapeUtil';
|
||||||
|
|
||||||
inject();
|
inject();
|
||||||
|
|
||||||
// The container shapes that can contain element shapes
|
const customShapeUtils = [ChatBoxShape, VideoChatShape, EmbedShape];
|
||||||
const CONTAINER_PADDING = 24;
|
const customTools = [ChatBoxTool, VideoChatTool, EmbedTool];
|
||||||
|
|
||||||
type ContainerShape = TLBaseShape<'element', { height: number; width: number }>;
|
|
||||||
|
|
||||||
// ... existing code for ContainerShapeUtil ...
|
|
||||||
|
|
||||||
// The element shapes that can be placed inside the container shapes
|
|
||||||
type ElementShape = TLBaseShape<'element', { color: string }>;
|
|
||||||
|
|
||||||
// ... existing code for ElementShapeUtil ...
|
|
||||||
|
|
||||||
// The binding between the element shapes and the container shapes
|
|
||||||
type LayoutBinding = TLBaseBinding<
|
|
||||||
'layout',
|
|
||||||
{
|
|
||||||
index: IndexKey;
|
|
||||||
placeholder: boolean;
|
|
||||||
}
|
|
||||||
>;
|
|
||||||
|
|
||||||
const customShapeUtils = [ChatBoxShape, VideoChatShape];
|
|
||||||
const customTools = [ChatBoxTool, VideoChatTool];
|
|
||||||
|
|
||||||
// [2]
|
// [2]
|
||||||
export default function InteractiveShapeExample() {
|
export default function InteractiveShapeExample() {
|
||||||
|
|
@ -62,7 +39,7 @@ export default function InteractiveShapeExample() {
|
||||||
<Tldraw
|
<Tldraw
|
||||||
shapeUtils={customShapeUtils}
|
shapeUtils={customShapeUtils}
|
||||||
tools={customTools}
|
tools={customTools}
|
||||||
overrides={uiOverrides}
|
overrides={overrides}
|
||||||
components={components}
|
components={components}
|
||||||
onMount={(editor) => {
|
onMount={(editor) => {
|
||||||
handleInitialShapeLoad(editor);
|
handleInitialShapeLoad(editor);
|
||||||
|
|
@ -134,8 +111,6 @@ function Home() {
|
||||||
const shapes = createShapes(elementsInfo)
|
const shapes = createShapes(elementsInfo)
|
||||||
const [isEditorMounted, setIsEditorMounted] = useState(false);
|
const [isEditorMounted, setIsEditorMounted] = useState(false);
|
||||||
|
|
||||||
//console.log("THIS WORKS SO FAR")
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleEditorDidMount = () => {
|
const handleEditorDidMount = () => {
|
||||||
setIsEditorMounted(true);
|
setIsEditorMounted(true);
|
||||||
|
|
@ -149,10 +124,12 @@ function Home() {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<><Toggle />
|
<>
|
||||||
|
<Toggle />
|
||||||
<div style={{ zIndex: 999999 }} className={`${isCanvasEnabled && isEditorMounted ? 'transparent' : ''}`}>
|
<div style={{ zIndex: 999999 }} className={`${isCanvasEnabled && isEditorMounted ? 'transparent' : ''}`}>
|
||||||
{<Default />}
|
{<Default />}
|
||||||
</div>
|
</div>
|
||||||
{isCanvasEnabled && elementsInfo.length > 0 ? <Canvas shapes={shapes} /> : null}</>
|
{isCanvasEnabled && elementsInfo.length > 0 ? <Canvas shapes={shapes} /> : null}
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -4,11 +4,8 @@ import {
|
||||||
AssetRecordType,
|
AssetRecordType,
|
||||||
getHashForString,
|
getHashForString,
|
||||||
TLBookmarkAsset,
|
TLBookmarkAsset,
|
||||||
TLRecord,
|
|
||||||
Tldraw,
|
Tldraw,
|
||||||
Editor,
|
Editor,
|
||||||
TLFrameShape,
|
|
||||||
TLUiEventSource,
|
|
||||||
} from 'tldraw'
|
} from 'tldraw'
|
||||||
import { useParams } from 'react-router-dom'
|
import { useParams } from 'react-router-dom'
|
||||||
import { ChatBoxTool } from '@/tools/ChatBoxTool'
|
import { ChatBoxTool } from '@/tools/ChatBoxTool'
|
||||||
|
|
@ -16,16 +13,12 @@ import { ChatBoxShape } from '@/shapes/ChatBoxShapeUtil'
|
||||||
import { VideoChatTool } from '@/tools/VideoChatTool'
|
import { VideoChatTool } from '@/tools/VideoChatTool'
|
||||||
import { VideoChatShape } from '@/shapes/VideoChatShapeUtil'
|
import { VideoChatShape } from '@/shapes/VideoChatShapeUtil'
|
||||||
import { multiplayerAssetStore } from '../client/multiplayerAssetStore'
|
import { multiplayerAssetStore } from '../client/multiplayerAssetStore'
|
||||||
import { customSchema } from '../../worker/TldrawDurableObject'
|
|
||||||
import { EmbedShape } from '@/shapes/EmbedShapeUtil'
|
import { EmbedShape } from '@/shapes/EmbedShapeUtil'
|
||||||
import { EmbedTool } from '@/tools/EmbedTool'
|
import { EmbedTool } from '@/tools/EmbedTool'
|
||||||
import { defaultShapeUtils, defaultBindingUtils } from 'tldraw'
|
import { defaultShapeUtils, defaultBindingUtils } from 'tldraw'
|
||||||
|
|
||||||
import React, { useState, useEffect, useCallback } from 'react';
|
import { useState } from 'react';
|
||||||
import { ChatBox } from '@/shapes/ChatBoxShapeUtil';
|
import { components, overrides } from '@/ui-overrides'
|
||||||
import { components, uiOverrides } from '@/ui-overrides'
|
|
||||||
import { useCameraControls } from '@/hooks/useCameraControls'
|
|
||||||
import { zoomToSelection } from '../ui-overrides'
|
|
||||||
|
|
||||||
// Default to production URL if env var isn't available
|
// Default to production URL if env var isn't available
|
||||||
export const WORKER_URL = 'https://jeffemmett-canvas.jeffemmett.workers.dev';
|
export const WORKER_URL = 'https://jeffemmett-canvas.jeffemmett.workers.dev';
|
||||||
|
|
@ -47,7 +40,6 @@ export function Board() {
|
||||||
|
|
||||||
const store = useSync(storeConfig);
|
const store = useSync(storeConfig);
|
||||||
const [editor, setEditor] = useState<Editor | null>(null)
|
const [editor, setEditor] = useState<Editor | null>(null)
|
||||||
const { zoomToFrame, copyFrameLink, copyLocationLink, revertCamera } = useCameraControls(editor)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ position: 'fixed', inset: 0 }}>
|
<div style={{ position: 'fixed', inset: 0 }}>
|
||||||
|
|
@ -56,81 +48,7 @@ export function Board() {
|
||||||
shapeUtils={shapeUtils}
|
shapeUtils={shapeUtils}
|
||||||
tools={tools}
|
tools={tools}
|
||||||
components={components}
|
components={components}
|
||||||
overrides={{
|
overrides={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) {
|
|
||||||
zoomToSelection(editor);
|
|
||||||
editor.setCurrentTool('select');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
readonlyOk: true,
|
|
||||||
},
|
|
||||||
'copyLinkToCurrentView': {
|
|
||||||
id: 'copy-link-to-current-view',
|
|
||||||
label: 'Copy Link to Current View',
|
|
||||||
kbd: 'c',
|
|
||||||
onSelect: () => {
|
|
||||||
const camera = editor.getCamera();
|
|
||||||
const url = new URL(window.location.href);
|
|
||||||
url.searchParams.set('x', camera.x.toString());
|
|
||||||
url.searchParams.set('y', camera.y.toString());
|
|
||||||
url.searchParams.set('zoom', camera.z.toString());
|
|
||||||
navigator.clipboard.writeText(url.toString());
|
|
||||||
editor.setCurrentTool('select');
|
|
||||||
},
|
|
||||||
readonlyOk: true,
|
|
||||||
},
|
|
||||||
'revertCamera': {
|
|
||||||
id: 'revert-camera',
|
|
||||||
label: 'Revert Camera',
|
|
||||||
kbd: 'b',
|
|
||||||
onSelect: () => {
|
|
||||||
revertCamera();
|
|
||||||
editor.setCurrentTool('select');
|
|
||||||
},
|
|
||||||
readonlyOk: true,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
}}
|
|
||||||
onMount={(editor) => {
|
onMount={(editor) => {
|
||||||
setEditor(editor)
|
setEditor(editor)
|
||||||
editor.registerExternalAssetHandler('url', unfurlBookmarkUrl)
|
editor.registerExternalAssetHandler('url', unfurlBookmarkUrl)
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,10 @@ import {
|
||||||
TldrawUiMenuItem,
|
TldrawUiMenuItem,
|
||||||
useEditor,
|
useEditor,
|
||||||
useTools,
|
useTools,
|
||||||
TLShapeId,
|
|
||||||
DefaultContextMenu,
|
DefaultContextMenu,
|
||||||
DefaultContextMenuContent,
|
DefaultContextMenuContent,
|
||||||
TLUiContextMenuProps,
|
TLUiContextMenuProps,
|
||||||
TldrawUiMenuGroup,
|
TldrawUiMenuGroup,
|
||||||
TLShape,
|
|
||||||
} from 'tldraw'
|
} from 'tldraw'
|
||||||
import { CustomMainMenu } from './components/CustomMainMenu'
|
import { CustomMainMenu } from './components/CustomMainMenu'
|
||||||
import { Editor } from 'tldraw'
|
import { Editor } from 'tldraw'
|
||||||
|
|
@ -37,55 +35,6 @@ const storeCameraPosition = (editor: Editor) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const copyFrameLink = async (editor: Editor, frameId: string) => {
|
|
||||||
console.log('Starting copyFrameLink with frameId:', frameId);
|
|
||||||
|
|
||||||
if (!editor.store.getSnapshot()) {
|
|
||||||
console.warn('Store not ready');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const baseUrl = `${window.location.origin}${window.location.pathname}`;
|
|
||||||
console.log('Base URL:', baseUrl);
|
|
||||||
|
|
||||||
const url = new URL(baseUrl);
|
|
||||||
url.searchParams.set('frameId', frameId);
|
|
||||||
|
|
||||||
const frame = editor.getShape(frameId as TLShapeId);
|
|
||||||
console.log('Found frame:', frame);
|
|
||||||
|
|
||||||
if (frame) {
|
|
||||||
const camera = editor.getCamera();
|
|
||||||
console.log('Camera position:', { x: camera.x, y: camera.y, zoom: camera.z });
|
|
||||||
|
|
||||||
url.searchParams.set('x', camera.x.toString());
|
|
||||||
url.searchParams.set('y', camera.y.toString());
|
|
||||||
url.searchParams.set('zoom', camera.z.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
const finalUrl = url.toString();
|
|
||||||
console.log('Final URL to copy:', finalUrl);
|
|
||||||
|
|
||||||
if (navigator.clipboard && window.isSecureContext) {
|
|
||||||
console.log('Using modern clipboard API...');
|
|
||||||
await navigator.clipboard.writeText(finalUrl);
|
|
||||||
console.log('URL copied successfully using clipboard API');
|
|
||||||
} else {
|
|
||||||
console.log('Falling back to legacy clipboard method...');
|
|
||||||
const textArea = document.createElement('textarea');
|
|
||||||
textArea.value = finalUrl;
|
|
||||||
document.body.appendChild(textArea);
|
|
||||||
textArea.select();
|
|
||||||
document.execCommand('copy');
|
|
||||||
document.body.removeChild(textArea);
|
|
||||||
console.log('URL copied successfully using fallback method');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to copy to clipboard:', error);
|
|
||||||
alert('Failed to copy link. Please check clipboard permissions.');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const zoomToSelection = (editor: Editor) => {
|
export const zoomToSelection = (editor: Editor) => {
|
||||||
// Store camera position before zooming
|
// Store camera position before zooming
|
||||||
|
|
@ -154,7 +103,7 @@ export const zoomToSelection = (editor: Editor) => {
|
||||||
const copyLinkToCurrentView = async (editor: Editor) => {
|
const copyLinkToCurrentView = async (editor: Editor) => {
|
||||||
console.log('Starting copyLinkToCurrentView');
|
console.log('Starting copyLinkToCurrentView');
|
||||||
|
|
||||||
if (!editor.store.getSnapshot()) {
|
if (!editor.store.serialize()) {
|
||||||
console.warn('Store not ready');
|
console.warn('Store not ready');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -184,10 +133,16 @@ const copyLinkToCurrentView = async (editor: Editor) => {
|
||||||
const textArea = document.createElement('textarea');
|
const textArea = document.createElement('textarea');
|
||||||
textArea.value = finalUrl;
|
textArea.value = finalUrl;
|
||||||
document.body.appendChild(textArea);
|
document.body.appendChild(textArea);
|
||||||
textArea.select();
|
try {
|
||||||
document.execCommand('copy');
|
await navigator.clipboard.writeText(textArea.value);
|
||||||
|
console.log('URL copied successfully');
|
||||||
|
} catch (err) {
|
||||||
|
// Fallback for older browsers
|
||||||
|
textArea.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
console.log('URL copied using fallback method');
|
||||||
|
}
|
||||||
document.body.removeChild(textArea);
|
document.body.removeChild(textArea);
|
||||||
console.log('URL copied successfully using fallback method');
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to copy to clipboard:', error);
|
console.error('Failed to copy to clipboard:', error);
|
||||||
|
|
@ -226,7 +181,8 @@ const revertCamera = (editor: Editor) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const uiOverrides: TLUiOverrides = {
|
// Export a function that creates the uiOverrides
|
||||||
|
export const overrides: TLUiOverrides = ({
|
||||||
tools(editor, tools) {
|
tools(editor, tools) {
|
||||||
return {
|
return {
|
||||||
...tools,
|
...tools,
|
||||||
|
|
@ -257,74 +213,69 @@ export const uiOverrides: TLUiOverrides = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions(editor, actions) {
|
actions(editor, actions) {
|
||||||
actions['copyFrameLink'] = {
|
return {
|
||||||
id: 'copy-frame-link',
|
...actions,
|
||||||
label: 'Copy Frame Link',
|
'zoomToSelection': {
|
||||||
onSelect: () => {
|
id: 'zoom-to-selection',
|
||||||
const shape = editor.getSelectedShapes()[0]
|
label: 'Zoom to Selection',
|
||||||
if (shape && shape.type === 'frame') {
|
kbd: 'z',
|
||||||
copyFrameLink(editor, shape.id)
|
onSelect: () => {
|
||||||
|
if (editor.getSelectedShapeIds().length > 0) {
|
||||||
|
zoomToSelection(editor);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
readonlyOk: true,
|
||||||
|
},
|
||||||
|
'copyLinkToCurrentView': {
|
||||||
|
id: 'copy-link-to-current-view',
|
||||||
|
label: 'Copy Link to Current View',
|
||||||
|
kbd: 's',
|
||||||
|
onSelect: () => {
|
||||||
|
copyLinkToCurrentView(editor);
|
||||||
|
},
|
||||||
|
readonlyOk: true,
|
||||||
|
},
|
||||||
|
'revertCamera': {
|
||||||
|
id: 'revert-camera',
|
||||||
|
label: 'Revert Camera',
|
||||||
|
kbd: 'b',
|
||||||
|
onSelect: () => {
|
||||||
|
if (cameraHistory.length > 0) {
|
||||||
|
revertCamera(editor);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
readonlyOk: true,
|
||||||
|
},
|
||||||
|
'lockToFrame': {
|
||||||
|
id: 'lock-to-frame',
|
||||||
|
label: 'Lock to Frame',
|
||||||
|
kbd: 'l',
|
||||||
|
onSelect: () => {
|
||||||
|
const selectedShapes = editor.getSelectedShapes()
|
||||||
|
if (selectedShapes.length === 0) return
|
||||||
|
const selectedShape = selectedShapes[0]
|
||||||
|
const isFrame = selectedShape.type === 'frame'
|
||||||
|
const bounds = editor.getShapePageBounds(selectedShape)
|
||||||
|
if (!isFrame || !bounds) return
|
||||||
|
|
||||||
|
editor.zoomToBounds(bounds, {
|
||||||
|
animation: { duration: 300 },
|
||||||
|
targetZoom: 1
|
||||||
|
})
|
||||||
|
editor.updateInstanceState({
|
||||||
|
meta: { ...editor.getInstanceState().meta, lockedFrameId: selectedShape.id }
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
readonlyOk: true,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
actions['zoomToFrame'] = {
|
|
||||||
id: 'zoom-to-frame',
|
|
||||||
label: 'Zoom to Frame',
|
|
||||||
onSelect: () => {
|
|
||||||
const shape = editor.getSelectedShapes()[0]
|
|
||||||
if (shape && shape.type === 'frame') {
|
|
||||||
zoomToSelection(editor)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
readonlyOk: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
actions['copyLinkToCurrentView'] = {
|
|
||||||
id: 'copy-link-to-current-view',
|
|
||||||
label: 'Copy Link to Current View',
|
|
||||||
kbd: 'c',
|
|
||||||
onSelect: () => {
|
|
||||||
console.log('Creating link to current view');
|
|
||||||
copyLinkToCurrentView(editor);
|
|
||||||
},
|
|
||||||
readonlyOk: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
actions['zoomToShape'] = {
|
|
||||||
id: 'zoom-to-shape',
|
|
||||||
label: 'Zoom to Selection',
|
|
||||||
kbd: 'z',
|
|
||||||
onSelect: () => {
|
|
||||||
if (editor.getSelectedShapeIds().length > 0) {
|
|
||||||
console.log('Zooming to selection');
|
|
||||||
zoomToSelection(editor);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
readonlyOk: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
actions['revertCamera'] = {
|
|
||||||
id: 'revert-camera',
|
|
||||||
label: 'Revert Camera',
|
|
||||||
kbd: 'b',
|
|
||||||
onSelect: () => {
|
|
||||||
if (cameraHistory.length > 0) {
|
|
||||||
revertCamera(editor);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
readonlyOk: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
return actions
|
|
||||||
},
|
},
|
||||||
}
|
})
|
||||||
|
|
||||||
export const components: TLComponents = {
|
export const components: TLComponents = {
|
||||||
Toolbar: function Toolbar() {
|
Toolbar: function Toolbar() {
|
||||||
const editor = useEditor()
|
const editor = useEditor()
|
||||||
const tools = useTools()
|
const tools = useTools()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DefaultToolbar>
|
<DefaultToolbar>
|
||||||
<DefaultToolbarContent />
|
<DefaultToolbarContent />
|
||||||
|
|
@ -356,7 +307,7 @@ export const components: TLComponents = {
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
MainMenu: CustomMainMenu,
|
MainMenu: CustomMainMenu,
|
||||||
ContextMenu: function CustomContextMenu({ ...rest }) {
|
ContextMenu: function CustomContextMenu(props: TLUiContextMenuProps) {
|
||||||
const editor = useEditor()
|
const editor = useEditor()
|
||||||
const hasSelection = editor.getSelectedShapeIds().length > 0
|
const hasSelection = editor.getSelectedShapeIds().length > 0
|
||||||
const hasCameraHistory = cameraHistory.length > 0
|
const hasCameraHistory = cameraHistory.length > 0
|
||||||
|
|
@ -364,101 +315,76 @@ export const components: TLComponents = {
|
||||||
const isFrame = selectedShape?.type === 'frame'
|
const isFrame = selectedShape?.type === 'frame'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DefaultContextMenu {...rest}>
|
<DefaultContextMenu {...props}>
|
||||||
<DefaultContextMenuContent />
|
<DefaultContextMenuContent />
|
||||||
|
|
||||||
{/* Camera Controls */}
|
{/* Camera Controls Group */}
|
||||||
<TldrawUiMenuItem
|
<TldrawUiMenuGroup id="camera-controls">
|
||||||
id="zoom-to-selection"
|
<TldrawUiMenuItem
|
||||||
label="Zoom to Selection"
|
id="zoom-to-selection"
|
||||||
icon="zoom-in"
|
label="Zoom to Selection"
|
||||||
kbd="z"
|
icon="zoom-in"
|
||||||
disabled={!hasSelection}
|
kbd="z"
|
||||||
onSelect={() => zoomToSelection(editor)}
|
disabled={!hasSelection}
|
||||||
/>
|
onSelect={() => zoomToSelection(editor)}
|
||||||
<TldrawUiMenuItem
|
/>
|
||||||
id="copy-link-to-current-view"
|
<TldrawUiMenuItem
|
||||||
label="Copy Link to Current View"
|
id="copy-link-to-current-view"
|
||||||
icon="link"
|
label="Copy Link to Current View"
|
||||||
kbd="s"
|
icon="link"
|
||||||
onSelect={() => copyLinkToCurrentView(editor)}
|
kbd="s"
|
||||||
/>
|
onSelect={() => copyLinkToCurrentView(editor)}
|
||||||
<TldrawUiMenuItem
|
/>
|
||||||
id="revert-camera"
|
<TldrawUiMenuItem
|
||||||
label="Revert Camera"
|
id="revert-camera"
|
||||||
icon="undo"
|
label="Revert Camera"
|
||||||
kbd="b"
|
icon="undo"
|
||||||
onSelect={() => {
|
kbd="b"
|
||||||
if (hasCameraHistory) {
|
disabled={!hasCameraHistory}
|
||||||
revertCamera(editor);
|
onSelect={() => revertCamera(editor)}
|
||||||
}
|
/>
|
||||||
}}
|
</TldrawUiMenuGroup>
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Shape Creation Tools */}
|
{/* Creation Tools Group */}
|
||||||
<TldrawUiMenuItem
|
<TldrawUiMenuGroup id="creation-tools">
|
||||||
id="video-chat"
|
<TldrawUiMenuItem
|
||||||
label="Create Video Chat"
|
id="video-chat"
|
||||||
icon="video"
|
label="Create Video Chat"
|
||||||
kbd="v"
|
icon="video"
|
||||||
onSelect={() => {
|
kbd="v"
|
||||||
editor.setCurrentTool('VideoChat');
|
onSelect={() => { editor.setCurrentTool('VideoChat'); }}
|
||||||
}}
|
/>
|
||||||
/>
|
<TldrawUiMenuItem
|
||||||
<TldrawUiMenuItem
|
id="chat-box"
|
||||||
id="chat-box"
|
label="Create Chat Box"
|
||||||
label="Create Chat Box"
|
icon="chat"
|
||||||
icon="chat"
|
kbd="c"
|
||||||
kbd="c"
|
onSelect={() => { editor.setCurrentTool('ChatBox'); }}
|
||||||
onSelect={() => {
|
/>
|
||||||
editor.setCurrentTool('ChatBox');
|
<TldrawUiMenuItem
|
||||||
}}
|
id="embed"
|
||||||
/>
|
label="Create Embed"
|
||||||
<TldrawUiMenuItem
|
icon="embed"
|
||||||
id="embed"
|
kbd="e"
|
||||||
label="Create Embed"
|
onSelect={() => { editor.setCurrentTool('Embed'); }}
|
||||||
icon="embed"
|
/>
|
||||||
kbd="e"
|
</TldrawUiMenuGroup>
|
||||||
onSelect={() => {
|
|
||||||
editor.setCurrentTool('Embed');
|
{/* Frame Controls */}
|
||||||
}}
|
{isFrame && (
|
||||||
/>
|
<TldrawUiMenuGroup id="frame-controls">
|
||||||
|
<TldrawUiMenuItem
|
||||||
|
id="lock-to-frame"
|
||||||
|
label="Lock to Frame"
|
||||||
|
icon="lock"
|
||||||
|
kbd="l"
|
||||||
|
onSelect={() => {
|
||||||
|
console.warn('lock to frame NOT IMPLEMENTED')
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</TldrawUiMenuGroup>
|
||||||
|
)}
|
||||||
</DefaultContextMenu>
|
</DefaultContextMenu>
|
||||||
)
|
)
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleInitialShapeLoad = (editor: Editor) => {
|
|
||||||
const url = new URL(window.location.href);
|
|
||||||
|
|
||||||
// Check for both shapeId and legacy frameId (for backwards compatibility)
|
|
||||||
const shapeId = url.searchParams.get('shapeId') || url.searchParams.get('frameId');
|
|
||||||
const x = url.searchParams.get('x');
|
|
||||||
const y = url.searchParams.get('y');
|
|
||||||
const zoom = url.searchParams.get('zoom');
|
|
||||||
|
|
||||||
if (shapeId) {
|
|
||||||
console.log('Found shapeId in URL:', shapeId);
|
|
||||||
const shape = editor.getShape(shapeId as TLShapeId);
|
|
||||||
|
|
||||||
if (shape) {
|
|
||||||
console.log('Found shape:', shape);
|
|
||||||
if (x && y && zoom) {
|
|
||||||
console.log('Setting camera to:', { x, y, zoom });
|
|
||||||
editor.setCamera({
|
|
||||||
x: parseFloat(x),
|
|
||||||
y: parseFloat(y),
|
|
||||||
z: parseFloat(zoom)
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.log('Zooming to shape bounds');
|
|
||||||
editor.zoomToBounds(editor.getShapeGeometry(shape).bounds, {
|
|
||||||
targetZoom: 1,
|
|
||||||
//padding: 32
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.warn('Shape not found:', shapeId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
Loading…
Reference in New Issue