From 923f61ac9e54d64ca89a9d2ed2b40a38e6eb3fa5 Mon Sep 17 00:00:00 2001 From: Jeff Emmett <46964190+Jeff-Emmett@users.noreply.github.com> Date: Sat, 7 Dec 2024 21:16:44 -0500 Subject: [PATCH] cleanup tools/menu/actions --- src/App.tsx | 43 ++--- src/components/Board.tsx | 88 +--------- src/ui-overrides.tsx | 344 +++++++++++++++------------------------ 3 files changed, 148 insertions(+), 327 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 6427f59..ca3c878 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -15,45 +15,22 @@ import { Board } from './components/Board'; import { Inbox } from './components/Inbox'; import { Books } from './components/Books'; import { - BindingUtil, Editor, - IndexKey, - TLBaseBinding, - TLBaseShape, Tldraw, TLShapeId, } from 'tldraw'; -import { components, uiOverrides } from './ui-overrides'; +import { components, overrides } from './ui-overrides' import { ChatBoxShape } from './shapes/ChatBoxShapeUtil'; import { VideoChatShape } from './shapes/VideoChatShapeUtil'; import { ChatBoxTool } from './tools/ChatBoxTool'; import { VideoChatTool } from './tools/VideoChatTool'; +import { EmbedTool } from './tools/EmbedTool'; +import { EmbedShape } from './shapes/EmbedShapeUtil'; inject(); -// The container shapes that can contain element shapes -const CONTAINER_PADDING = 24; - -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]; +const customShapeUtils = [ChatBoxShape, VideoChatShape, EmbedShape]; +const customTools = [ChatBoxTool, VideoChatTool, EmbedTool]; // [2] export default function InteractiveShapeExample() { @@ -62,7 +39,7 @@ export default function InteractiveShapeExample() { { handleInitialShapeLoad(editor); @@ -134,8 +111,6 @@ function Home() { const shapes = createShapes(elementsInfo) const [isEditorMounted, setIsEditorMounted] = useState(false); - //console.log("THIS WORKS SO FAR") - useEffect(() => { const handleEditorDidMount = () => { setIsEditorMounted(true); @@ -149,10 +124,12 @@ function Home() { }, []); return ( - <> + <> +
{}
- {isCanvasEnabled && elementsInfo.length > 0 ? : null} + {isCanvasEnabled && elementsInfo.length > 0 ? : null} + ) } \ No newline at end of file diff --git a/src/components/Board.tsx b/src/components/Board.tsx index 3dea2ef..4100fae 100644 --- a/src/components/Board.tsx +++ b/src/components/Board.tsx @@ -4,11 +4,8 @@ import { AssetRecordType, getHashForString, TLBookmarkAsset, - TLRecord, Tldraw, Editor, - TLFrameShape, - TLUiEventSource, } from 'tldraw' import { useParams } from 'react-router-dom' import { ChatBoxTool } from '@/tools/ChatBoxTool' @@ -16,16 +13,12 @@ import { ChatBoxShape } from '@/shapes/ChatBoxShapeUtil' import { VideoChatTool } from '@/tools/VideoChatTool' import { VideoChatShape } from '@/shapes/VideoChatShapeUtil' import { multiplayerAssetStore } from '../client/multiplayerAssetStore' -import { customSchema } from '../../worker/TldrawDurableObject' import { EmbedShape } from '@/shapes/EmbedShapeUtil' import { EmbedTool } from '@/tools/EmbedTool' import { defaultShapeUtils, defaultBindingUtils } from 'tldraw' -import React, { useState, useEffect, useCallback } from 'react'; -import { ChatBox } from '@/shapes/ChatBoxShapeUtil'; -import { components, uiOverrides } from '@/ui-overrides' -import { useCameraControls } from '@/hooks/useCameraControls' -import { zoomToSelection } from '../ui-overrides' +import { useState } from 'react'; +import { components, overrides } from '@/ui-overrides' // Default to production URL if env var isn't available export const WORKER_URL = 'https://jeffemmett-canvas.jeffemmett.workers.dev'; @@ -47,7 +40,6 @@ export function Board() { const store = useSync(storeConfig); const [editor, setEditor] = useState(null) - const { zoomToFrame, copyFrameLink, copyLocationLink, revertCamera } = useCameraControls(editor) return (
@@ -56,81 +48,7 @@ export function Board() { shapeUtils={shapeUtils} 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) { - 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, - }, - }), - }} + overrides={overrides} onMount={(editor) => { setEditor(editor) editor.registerExternalAssetHandler('url', unfurlBookmarkUrl) diff --git a/src/ui-overrides.tsx b/src/ui-overrides.tsx index 1dfb822..3184ea5 100644 --- a/src/ui-overrides.tsx +++ b/src/ui-overrides.tsx @@ -6,12 +6,10 @@ import { TldrawUiMenuItem, useEditor, useTools, - TLShapeId, DefaultContextMenu, DefaultContextMenuContent, TLUiContextMenuProps, TldrawUiMenuGroup, - TLShape, } from 'tldraw' import { CustomMainMenu } from './components/CustomMainMenu' 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) => { // Store camera position before zooming @@ -154,7 +103,7 @@ export const zoomToSelection = (editor: Editor) => { const copyLinkToCurrentView = async (editor: Editor) => { console.log('Starting copyLinkToCurrentView'); - if (!editor.store.getSnapshot()) { + if (!editor.store.serialize()) { console.warn('Store not ready'); return; } @@ -184,10 +133,16 @@ const copyLinkToCurrentView = async (editor: Editor) => { const textArea = document.createElement('textarea'); textArea.value = finalUrl; document.body.appendChild(textArea); - textArea.select(); - document.execCommand('copy'); + try { + 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); - console.log('URL copied successfully using fallback method'); } } catch (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) { return { ...tools, @@ -257,74 +213,69 @@ export const uiOverrides: TLUiOverrides = { } }, actions(editor, actions) { - actions['copyFrameLink'] = { - id: 'copy-frame-link', - label: 'Copy Frame Link', - onSelect: () => { - const shape = editor.getSelectedShapes()[0] - if (shape && shape.type === 'frame') { - copyFrameLink(editor, shape.id) + return { + ...actions, + 'zoomToSelection': { + id: 'zoom-to-selection', + label: 'Zoom to Selection', + kbd: 'z', + 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 = { Toolbar: function Toolbar() { const editor = useEditor() const tools = useTools() + return ( @@ -356,7 +307,7 @@ export const components: TLComponents = { ) }, MainMenu: CustomMainMenu, - ContextMenu: function CustomContextMenu({ ...rest }) { + ContextMenu: function CustomContextMenu(props: TLUiContextMenuProps) { const editor = useEditor() const hasSelection = editor.getSelectedShapeIds().length > 0 const hasCameraHistory = cameraHistory.length > 0 @@ -364,101 +315,76 @@ export const components: TLComponents = { const isFrame = selectedShape?.type === 'frame' return ( - + - {/* Camera Controls */} - zoomToSelection(editor)} - /> - copyLinkToCurrentView(editor)} - /> - { - if (hasCameraHistory) { - revertCamera(editor); - } - }} - /> + {/* Camera Controls Group */} + + zoomToSelection(editor)} + /> + copyLinkToCurrentView(editor)} + /> + revertCamera(editor)} + /> + - {/* Shape Creation Tools */} - { - editor.setCurrentTool('VideoChat'); - }} - /> - { - editor.setCurrentTool('ChatBox'); - }} - /> - { - editor.setCurrentTool('Embed'); - }} - /> + {/* Creation Tools Group */} + + { editor.setCurrentTool('VideoChat'); }} + /> + { editor.setCurrentTool('ChatBox'); }} + /> + { editor.setCurrentTool('Embed'); }} + /> + + + {/* Frame Controls */} + {isFrame && ( + + { + console.warn('lock to frame NOT IMPLEMENTED') + }} + /> + + )} ) - }, -} - -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); - } } -}; \ No newline at end of file +} \ No newline at end of file