From 0be7e77c1810b3747b4e7e0c92cb5484e25434e0 Mon Sep 17 00:00:00 2001
From: Jeff Emmett <46964190+Jeff-Emmett@users.noreply.github.com>
Date: Wed, 16 Oct 2024 11:20:26 -0400
Subject: [PATCH] big mess of a commit
---
index.html | 2 +-
package.json | 1 +
src/App.tsx | 83 +++++--
src/components/Board.tsx | 125 ++---------
src/components/Canvas.tsx | 2 +-
src/{components => css}/ChatBoxStyles.css | 0
src/physics/simulation.ts | 2 +-
...{ChatBoxShape.tsx => ChatBoxShapeUtil.tsx} | 20 +-
src/shapes/VideoChatShape.tsx | 93 --------
src/shapes/VideoChatShapeUtil.tsx | 79 +++++++
src/snapshot.json | 210 ++++++++++++++++++
src/tools/CardShapeTool.tsx | 20 --
src/tools/ChatBoxTool.ts | 3 +-
src/tools/VideoChatTool.ts | 3 +-
src/ui-overrides.tsx | 48 ++++
src/utils/ContainerShapeUtil.tsx | 0
src/utils/ElementShapeUtil.tsx | 0
src/{shapes => utils}/HTMLShapeUtil.tsx | 0
src/utils/card-shape-migrations.ts | 25 +++
src/utils/card-shape-props.ts | 11 +
src/utils/card-shape-types.ts | 11 +
src/utils/my-interactive-shape-util.tsx | 120 ++++++++++
src/{ => utils}/utils.tsx | 0
ui-overrides.tsx | 67 ------
worker/TldrawDurableObject.ts | 6 +-
25 files changed, 610 insertions(+), 321 deletions(-)
rename src/{components => css}/ChatBoxStyles.css (100%)
rename src/shapes/{ChatBoxShape.tsx => ChatBoxShapeUtil.tsx} (89%)
delete mode 100644 src/shapes/VideoChatShape.tsx
create mode 100644 src/shapes/VideoChatShapeUtil.tsx
create mode 100644 src/snapshot.json
delete mode 100644 src/tools/CardShapeTool.tsx
create mode 100644 src/ui-overrides.tsx
create mode 100644 src/utils/ContainerShapeUtil.tsx
create mode 100644 src/utils/ElementShapeUtil.tsx
rename src/{shapes => utils}/HTMLShapeUtil.tsx (100%)
create mode 100644 src/utils/card-shape-migrations.ts
create mode 100644 src/utils/card-shape-props.ts
create mode 100644 src/utils/card-shape-types.ts
create mode 100644 src/utils/my-interactive-shape-util.tsx
rename src/{ => utils}/utils.tsx (100%)
delete mode 100644 ui-overrides.tsx
diff --git a/index.html b/index.html
index c5c131b..063435a 100644
--- a/index.html
+++ b/index.html
@@ -26,7 +26,7 @@
-
+
diff --git a/package.json b/package.json
index b4f7def..6b0e345 100644
--- a/package.json
+++ b/package.json
@@ -18,6 +18,7 @@
"@dimforge/rapier2d": "^0.11.2",
"@tldraw/sync": "^2.4.6",
"@tldraw/sync-core": "latest",
+ "@tldraw/tldraw": "^3.3.1",
"@tldraw/tlschema": "latest",
"@types/markdown-it": "^14.1.1",
"@vercel/analytics": "^1.2.2",
diff --git a/src/App.tsx b/src/App.tsx
index bb32041..c649b53 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,37 +1,93 @@
import { inject } from '@vercel/analytics';
import "tldraw/tldraw.css";
import "@/css/style.css"
-import React, { useEffect, useState } from "react";
+import { useEffect, useState } from "react";
import ReactDOM from "react-dom/client";
import { Default } from "@/components/Default";
import { Canvas } from "@/components/Canvas";
import { Toggle } from "@/components/Toggle";
import { useCanvas } from "@/hooks/useCanvas"
-import { createShapes } from "@/utils";
+import { createShapes } from "@/utils/utils";
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import { Contact } from "@/components/Contact";
import { Post } from '@/components/Post';
import { Board } from './components/Board';
import { Inbox } from './components/Inbox';
import { Books } from './components/Books';
+import {
+ BindingUtil,
+ IndexKey,
+ TLBaseBinding,
+ TLBaseShape,
+ Tldraw,
+} from 'tldraw';
+import { components, uiOverrides } from './ui-overrides';
+import { ChatBoxShape } from './shapes/ChatBoxShapeUtil';
+import { VideoChatShape } from './shapes/VideoChatShapeUtil';
+import { ChatBoxTool } from './tools/ChatBoxTool';
+import { VideoChatTool } from './tools/VideoChatTool';
+
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];
+
+// [2]
+export default function InteractiveShapeExample() {
+ return (
+
+ {
+ editor.createShape({ type: 'my-interactive-shape', x: 100, y: 100 });
+ }}
+ />
+
+ );
+}
+
+// ... existing code ...
+
ReactDOM.createRoot(document.getElementById("root")!).render( );
function App() {
return (
//
-
-
- } />
- } />
- } />
- } />
- } />
- } />
-
-
+
+
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+
//
);
};
@@ -58,5 +114,6 @@ function Home() {
{ }
- {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 b6d3574..7e82cb3 100644
--- a/src/components/Board.tsx
+++ b/src/components/Board.tsx
@@ -2,46 +2,28 @@ import { useSync } from '@tldraw/sync'
import {
AssetRecordType,
getHashForString,
- TLAssetStore,
TLBookmarkAsset,
Tldraw,
- uniqueId,
} from 'tldraw'
-import { useParams } from 'react-router-dom' // Add this import
+import { useParams } from 'react-router-dom'
import { ChatBoxTool } from '@/tools/ChatBoxTool'
-import { IChatBoxShape, ChatBoxShape } from '@/shapes/ChatBoxShape'
+import { IChatBoxShape, ChatBoxShape } from '@/shapes/ChatBoxShapeUtil'
import { VideoChatTool } from '@/tools/VideoChatTool'
-import { IVideoChatShape, VideoChatShape } from '@/shapes/VideoChatShape'
-import { multiplayerAssetStore } from '../client/multiplayerAssetStore' // Adjusted path if necessary
+import { IVideoChatShape, VideoChatShape } from '@/shapes/VideoChatShapeUtil'
+import { multiplayerAssetStore } from '../client/multiplayerAssetStore'
import { customSchema } from '../../worker/TldrawDurableObject'
-import './ChatBoxStyles.css' // Add a CSS file for styles
-import React, { useState, useEffect, useRef } from 'react'; // Ensure useRef is imported
-import { ChatBox } from '@/shapes/ChatBoxShape'; // Add this import
- import { VideoChat } from '@/shapes/VideoChatShape';
+import React, { useState, useEffect, useRef } from 'react';
+import { ChatBox } from '@/shapes/ChatBoxShapeUtil';
+import { components, uiOverrides } from '@/ui-overrides'
const WORKER_URL = `https://jeffemmett-canvas.jeffemmett.workers.dev`
const shapeUtils = [ChatBoxShape, VideoChatShape]
const tools = [ChatBoxTool, VideoChatTool]; // Array of tools
-// Function to register tools
-const registerTools = (store: any, registeredToolsRef: React.MutableRefObject>) => {
- const typedStore = store as unknown as {
- registerTool: (tool: any) => void;
- hasTool: (id: string) => boolean;
- };
-
- tools.forEach(tool => {
- if (!registeredToolsRef.current.has(tool.id) && typedStore.hasTool && !typedStore.hasTool(tool.id)) {
- typedStore.registerTool(tool); // Register the tool
- registeredToolsRef.current.add(tool.id); // Mark this tool as registered
- }
- });
-};
-
-
export function Board() {
+ console.warn("HELLO FROM BOARD")
const { slug } = useParams<{ slug: string }>(); // Ensure this is inside the Board component
const roomId = slug || 'default-room'; // Declare roomId here
@@ -49,48 +31,12 @@ export function Board() {
uri: `${WORKER_URL}/connect/${roomId}`,
assets: multiplayerAssetStore,
shapeUtils: shapeUtils,
- schema: customSchema
+ schema: customSchema,
});
const [isChatBoxVisible, setChatBoxVisible] = useState(false);
const [userName, setUserName] = useState('');
const [isVideoChatVisible, setVideoChatVisible] = useState(false); // Added state for video chat visibility
- const registeredToolsRef = useRef(new Set()); // Ref to track registered tools
-
- // Call the function to register tools only once
- useEffect(() => {
- registerTools(store, registeredToolsRef);
- }, [store]); // Ensure this effect runs when the store changes
-
-// const videoChatTool = {
-// id: 'videoChatTool', // Ensure this ID is unique
-// // ... other properties of the tool
-// };
-
- //const typedStore = store as unknown as {
- // registerTool: (tool: any) => void;
- // hasTool: (id: string) => boolean; // Ensure hasTool is included
- //};
-
- //if (typedStore.hasTool && !typedStore.hasTool(videoChatTool.id)) { // Check if the tool ID is unique
- // typedStore.registerTool(videoChatTool); // Register the video chat tool
- //} else {
- // console.error(`Tool with id "${videoChatTool.id}" is already registered. Cannot register again.`); // Log an error
- //}
-
-// useEffect(() => {
-// tools.forEach(tool => {
-// const typedStore = store as unknown as {
-// registerTool: (tool: any) => void;
-// hasTool: (id: string) => boolean; // Ensure hasTool is included
-// };
-// if (typedStore.hasTool && !typedStore.hasTool(tool.id)) { // Check if hasTool exists
-// typedStore.registerTool(tool); // Use the typed store
-// } else {
-// console.warn(`Tool with id "${tool.id}" is already registered. Cannot register again.`); // Log an error
-// }
-// });
-// }, [store]); // Run this effect when the store changes
const handleNameChange = (event: React.ChangeEvent) => {
setUserName(event.target.value);
@@ -99,34 +45,13 @@ export function Board() {
return (
{
editor.registerExternalAssetHandler('url', unfurlBookmarkUrl)
- editor.createShape({
- type: 'chatBox',
- x: 0,
- y: 0,
- props: {
- w: 200,
- h: 200,
- roomId: roomId,
- },
- });
- //if (isVideoChatVisible) {
- //editor.registerExternalAssetHandler('url', unfurlBookmarkUrl)
- editor.createShape({
- type: 'videoChat',
- x: 300, // Adjust position as needed
- y: 0,
- props: {
- roomUrl: 'https://whereby.com/default-room', // Default room URL
- w: 640,
- h: 480,
- },
- });
- //}
}}
/>
{isChatBoxVisible && (
@@ -137,12 +62,12 @@ export function Board() {
onChange={handleNameChange}
placeholder="Enter your name"
/>
-
+ w={200} // Set appropriate width
+ h={200} // Set appropriate height
+ />
)}
{isVideoChatVisible && ( // Render the button to join video chat
@@ -154,22 +79,6 @@ export function Board() {
)
}
-// Assuming you have a message structure like this
-interface ChatMessage {
- id: string;
- text: string;
- isUser: boolean; // New property to identify the sender
-}
-
-// Example rendering function for messages
-function renderMessage(message: ChatMessage) {
- return (
-
- {message.text}
-
- )
-}
-
// How does our server handle bookmark unfurling?
async function unfurlBookmarkUrl({ url }: { url: string }): Promise {
const asset: TLBookmarkAsset = {
diff --git a/src/components/Canvas.tsx b/src/components/Canvas.tsx
index bdf7457..34f2a2d 100644
--- a/src/components/Canvas.tsx
+++ b/src/components/Canvas.tsx
@@ -1,6 +1,6 @@
import { Editor, Tldraw, TLShape, TLUiComponents } from "tldraw";
import { SimController } from "@/physics/PhysicsControls";
-import { HTMLShapeUtil } from "@/shapes/HTMLShapeUtil";
+import { HTMLShapeUtil } from "@/utils/HTMLShapeUtil";
const components: TLUiComponents = {
HelpMenu: null,
diff --git a/src/components/ChatBoxStyles.css b/src/css/ChatBoxStyles.css
similarity index 100%
rename from src/components/ChatBoxStyles.css
rename to src/css/ChatBoxStyles.css
diff --git a/src/physics/simulation.ts b/src/physics/simulation.ts
index f657648..2ff1ec7 100644
--- a/src/physics/simulation.ts
+++ b/src/physics/simulation.ts
@@ -66,7 +66,7 @@ export class PhysicsWorld {
this.createGroup(shape as TLGroupShape);
break;
// Add cases for any new shape types here
- case "videoChat":
+ case "VideoChat":
this.createShape (shape as TLGeoShape);
break;
}
diff --git a/src/shapes/ChatBoxShape.tsx b/src/shapes/ChatBoxShapeUtil.tsx
similarity index 89%
rename from src/shapes/ChatBoxShape.tsx
rename to src/shapes/ChatBoxShapeUtil.tsx
index 99a47fe..b385ab6 100644
--- a/src/shapes/ChatBoxShape.tsx
+++ b/src/shapes/ChatBoxShapeUtil.tsx
@@ -1,8 +1,8 @@
import { useEffect, useRef, useState } from "react";
-import { BaseBoxShapeUtil, TLBaseBoxShape, TLBaseShape, TldrawBaseProps } from "tldraw";
+import { BaseBoxShapeUtil, TLBaseShape } from "tldraw";
export type IChatBoxShape = TLBaseShape<
- 'chatBox',
+ 'ChatBox',
{
w: number
h: number
@@ -12,7 +12,7 @@ export type IChatBoxShape = TLBaseShape<
>
export class ChatBoxShape extends BaseBoxShapeUtil {
- static override type = 'chatBox'
+ static override type = 'ChatBox'
getDefaultProps(): IChatBoxShape['props'] {
return {
@@ -29,7 +29,7 @@ export class ChatBoxShape extends BaseBoxShapeUtil {
component(shape: IChatBoxShape) {
return (
-
+
)
}
}
@@ -41,15 +41,11 @@ interface Message {
timestamp: Date;
}
-interface ChatBoxProps {
- roomId: string;
- width: number;
- height: number;
- userName: string; // Add this line
-}
+
+
// Update the ChatBox component to accept userName
-export const ChatBox: React.FC = ({ roomId, width, height, userName }) => {
+export const ChatBox: React.FC = ({ roomId, w, h, userName }) => {
const [messages, setMessages] = useState([]);
const [inputMessage, setInputMessage] = useState("");
const [username, setUsername] = useState(userName);
@@ -98,7 +94,7 @@ export const ChatBox: React.FC = ({ roomId, width, height, userNam
};
return (
-
+
{messages.map((msg) => (
diff --git a/src/shapes/VideoChatShape.tsx b/src/shapes/VideoChatShape.tsx
deleted file mode 100644
index 5062981..0000000
--- a/src/shapes/VideoChatShape.tsx
+++ /dev/null
@@ -1,93 +0,0 @@
-import { BaseBoxShapeUtil, TLBaseShape } from "tldraw";
-import React, { useEffect, useState } from "react"; // Updated import for React
-
-export type IVideoChatShape = TLBaseShape<
- 'videoChat',
- {
- w: number;
- h: number;
- roomUrl: string; // Changed from roomId to roomUrl for Whereby
- userName: string;
- }
->;
-
-export class VideoChatShape extends BaseBoxShapeUtil
{
- static override type = 'videoChat';
-
- getDefaultProps(): IVideoChatShape['props'] {
- return {
- roomUrl: 'https://whereby.com/default-room', // Default Whereby room URL
- w: 640,
- h: 480,
- userName: ''
- };
- }
-
- indicator(shape: IVideoChatShape) {
- return ;
- }
-
- component(shape: IVideoChatShape) {
- return ;
- }
-}
-
-interface VideoChatProps {
- roomUrl: string;
- // Remove width and height as they are not used
- // width: number;
- // height: number;
- // Remove userName as it is not used
- // userName: string;
-}
-
-// VideoChat component using Whereby
-export const VideoChat: React.FC = () => { // Removed roomUrl from props
- // Remove unused destructured props
- // const [roomUrl, setRoomUrl] = useState(initialRoomUrl); // Use initialRoomUrl to avoid duplicate identifier
- // const [roomUrl, setRoomUrl] = useState(roomId); // Use roomId instead
- const [isInRoom, setIsInRoom] = useState(false);
- const [error, setError] = useState("");
- const [isLoading, setIsLoading] = useState(false);
-
- // Automatically show the button on load
- useEffect(() => {
- joinRoom();
- }, []);
-
- const joinRoom = async () => {
- setError("");
- setIsLoading(true);
- try {
- const response = await fetch('/api/get-or-create-room', { method: 'GET' });
- const data: { roomUrl?: string; error?: string } = await response.json(); // Explicitly type 'data'
- if (data.roomUrl) {
- // setRoomUrl(data.roomUrl); // Remove this line
- setIsInRoom(true);
- } else {
- setError(data.error || "Failed to join room. Please try again.");
- }
- } catch (e) {
- console.error("Error joining room:", e);
- setError("An error occurred. Please try again.");
- }
- setIsLoading(false);
- };
-
- const leaveRoom = () => {
- setIsInRoom(false);
- // setRoomUrl(""); // Remove this line
- };
-
- return (
-
- {!isInRoom && ( // Show button if not in room
-
setIsInRoom(true)} className="bg-green-500 text-white px-4 py-2 rounded">
- Join Video Chat
-
- )}
- {/* Render Video Chat UI here when isInRoom is true */}
- {isInRoom &&
Video Chat UI goes here
}
-
- );
-}
\ No newline at end of file
diff --git a/src/shapes/VideoChatShapeUtil.tsx b/src/shapes/VideoChatShapeUtil.tsx
new file mode 100644
index 0000000..b46127c
--- /dev/null
+++ b/src/shapes/VideoChatShapeUtil.tsx
@@ -0,0 +1,79 @@
+import { BaseBoxShapeUtil, TLBaseShape } from "tldraw";
+import { useEffect, useState } from "react";
+
+export type IVideoChatShape = TLBaseShape<
+ 'VideoChat',
+ {
+ w: number;
+ h: number;
+ roomUrl: string; // Changed from roomId to roomUrl for Whereby
+ userName: string;
+ }
+>;
+
+export class VideoChatShape extends BaseBoxShapeUtil {
+ static override type = 'VideoChat';
+
+ getDefaultProps(): IVideoChatShape['props'] {
+ return {
+ roomUrl: 'https://whereby.com/default-room', // Default Whereby room URL
+ w: 640,
+ h: 480,
+ userName: ''
+ };
+ }
+
+ indicator(shape: IVideoChatShape) {
+ return ;
+ }
+
+ component(shape: IVideoChatShape) {
+ // Remove unused destructured props
+ // const [roomUrl, setRoomUrl] = useState(initialRoomUrl); // Use initialRoomUrl to avoid duplicate identifier
+ // const [roomUrl, setRoomUrl] = useState(roomId); // Use roomId instead
+ const [isInRoom, setIsInRoom] = useState(false);
+ const [error, setError] = useState("");
+ const [isLoading, setIsLoading] = useState(false);
+
+ // Automatically show the button on load
+ useEffect(() => {
+ joinRoom();
+ }, []);
+
+ const joinRoom = async () => {
+ setError("");
+ setIsLoading(true);
+ try {
+ const response = await fetch('/api/get-or-create-room', { method: 'GET' });
+ const data: { roomUrl?: string; error?: string } = await response.json(); // Explicitly type 'data'
+ if (data.roomUrl) {
+ // setRoomUrl(data.roomUrl); // Remove this line
+ setIsInRoom(true);
+ } else {
+ setError(data.error || "Failed to join room. Please try again.");
+ }
+ } catch (e) {
+ console.error("Error joining room:", e);
+ setError("An error occurred. Please try again.");
+ }
+ setIsLoading(false);
+ };
+
+ const leaveRoom = () => {
+ setIsInRoom(false);
+ // setRoomUrl(""); // Remove this line
+ };
+
+ return (
+
+ {!isInRoom && ( // Show button if not in room
+
setIsInRoom(true)} className="bg-green-500 text-white px-4 py-2 rounded">
+ Join Video Chat
+
+ )}
+ {/* Render Video Chat UI here when isInRoom is true */}
+ {isInRoom &&
Video Chat UI goes here
}
+
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/snapshot.json b/src/snapshot.json
new file mode 100644
index 0000000..1b80b16
--- /dev/null
+++ b/src/snapshot.json
@@ -0,0 +1,210 @@
+{
+ "store": {
+ "document:document": {
+ "gridSize": 10,
+ "name": "",
+ "meta": {},
+ "id": "document:document",
+ "typeName": "document"
+ },
+ "page:page": {
+ "meta": {},
+ "id": "page:page",
+ "name": "Page 1",
+ "index": "a1",
+ "typeName": "page"
+ },
+ "shape:f4LKGB_8M2qsyWGpHR5Dq": {
+ "x": 30.9375,
+ "y": 69.48828125,
+ "rotation": 0,
+ "isLocked": false,
+ "opacity": 1,
+ "meta": {},
+ "id": "shape:f4LKGB_8M2qsyWGpHR5Dq",
+ "type": "container",
+ "parentId": "page:page",
+ "index": "a1",
+ "props": {
+ "width": 644,
+ "height": 148
+ },
+ "typeName": "shape"
+ },
+ "shape:2oThF4kJ4v31xqKN5lvq2": {
+ "x": 550.9375,
+ "y": 93.48828125,
+ "rotation": 0,
+ "isLocked": false,
+ "opacity": 1,
+ "meta": {},
+ "id": "shape:2oThF4kJ4v31xqKN5lvq2",
+ "type": "element",
+ "props": {
+ "color": "#5BCEFA"
+ },
+ "parentId": "page:page",
+ "index": "a2",
+ "typeName": "shape"
+ },
+ "shape:K2vk_VTaNh-ANaRNOAvgY": {
+ "x": 426.9375,
+ "y": 93.48828125,
+ "rotation": 0,
+ "isLocked": false,
+ "opacity": 1,
+ "meta": {},
+ "id": "shape:K2vk_VTaNh-ANaRNOAvgY",
+ "type": "element",
+ "props": {
+ "color": "#F5A9B8"
+ },
+ "parentId": "page:page",
+ "index": "a3",
+ "typeName": "shape"
+ },
+ "shape:6uouhIK7PvyIRNQHACf-d": {
+ "x": 302.9375,
+ "y": 93.48828125,
+ "rotation": 0,
+ "isLocked": false,
+ "opacity": 1,
+ "meta": {},
+ "id": "shape:6uouhIK7PvyIRNQHACf-d",
+ "type": "element",
+ "props": {
+ "color": "#FFFFFF"
+ },
+ "parentId": "page:page",
+ "index": "a4",
+ "typeName": "shape"
+ },
+ "shape:GTQq2qxkWPHEK7KMIRtsh": {
+ "x": 54.9375,
+ "y": 93.48828125,
+ "rotation": 0,
+ "isLocked": false,
+ "opacity": 1,
+ "meta": {},
+ "id": "shape:GTQq2qxkWPHEK7KMIRtsh",
+ "type": "element",
+ "props": {
+ "color": "#5BCEFA"
+ },
+ "parentId": "page:page",
+ "index": "a5",
+ "typeName": "shape"
+ },
+ "shape:05jMujN6A0sIp6zzHMpbV": {
+ "x": 178.9375,
+ "y": 93.48828125,
+ "rotation": 0,
+ "isLocked": false,
+ "opacity": 1,
+ "meta": {},
+ "id": "shape:05jMujN6A0sIp6zzHMpbV",
+ "type": "element",
+ "props": {
+ "color": "#F5A9B8"
+ },
+ "parentId": "page:page",
+ "index": "a6",
+ "typeName": "shape"
+ },
+ "binding:iOBENBUHvzD8N7mBdIM5l": {
+ "meta": {},
+ "id": "binding:iOBENBUHvzD8N7mBdIM5l",
+ "type": "layout",
+ "fromId": "shape:f4LKGB_8M2qsyWGpHR5Dq",
+ "toId": "shape:05jMujN6A0sIp6zzHMpbV",
+ "props": {
+ "index": "a2",
+ "placeholder": false
+ },
+ "typeName": "binding"
+ },
+ "binding:YTIeOALEmHJk6dczRpQmE": {
+ "meta": {},
+ "id": "binding:YTIeOALEmHJk6dczRpQmE",
+ "type": "layout",
+ "fromId": "shape:f4LKGB_8M2qsyWGpHR5Dq",
+ "toId": "shape:GTQq2qxkWPHEK7KMIRtsh",
+ "props": {
+ "index": "a1",
+ "placeholder": false
+ },
+ "typeName": "binding"
+ },
+ "binding:n4LY_pVuLfjV1qpOTZX-U": {
+ "meta": {},
+ "id": "binding:n4LY_pVuLfjV1qpOTZX-U",
+ "type": "layout",
+ "fromId": "shape:f4LKGB_8M2qsyWGpHR5Dq",
+ "toId": "shape:6uouhIK7PvyIRNQHACf-d",
+ "props": {
+ "index": "a3",
+ "placeholder": false
+ },
+ "typeName": "binding"
+ },
+ "binding:8XayRsWB_nxAH2833SYg1": {
+ "meta": {},
+ "id": "binding:8XayRsWB_nxAH2833SYg1",
+ "type": "layout",
+ "fromId": "shape:f4LKGB_8M2qsyWGpHR5Dq",
+ "toId": "shape:2oThF4kJ4v31xqKN5lvq2",
+ "props": {
+ "index": "a5",
+ "placeholder": false
+ },
+ "typeName": "binding"
+ },
+ "binding:MTYuIRiEVTn2DyVChthry": {
+ "meta": {},
+ "id": "binding:MTYuIRiEVTn2DyVChthry",
+ "type": "layout",
+ "fromId": "shape:f4LKGB_8M2qsyWGpHR5Dq",
+ "toId": "shape:K2vk_VTaNh-ANaRNOAvgY",
+ "props": {
+ "index": "a4",
+ "placeholder": false
+ },
+ "typeName": "binding"
+ }
+ },
+ "schema": {
+ "schemaVersion": 2,
+ "sequences": {
+ "com.tldraw.store": 4,
+ "com.tldraw.asset": 1,
+ "com.tldraw.camera": 1,
+ "com.tldraw.document": 2,
+ "com.tldraw.instance": 25,
+ "com.tldraw.instance_page_state": 5,
+ "com.tldraw.page": 1,
+ "com.tldraw.instance_presence": 5,
+ "com.tldraw.pointer": 1,
+ "com.tldraw.shape": 4,
+ "com.tldraw.asset.bookmark": 2,
+ "com.tldraw.asset.image": 4,
+ "com.tldraw.asset.video": 4,
+ "com.tldraw.shape.group": 0,
+ "com.tldraw.shape.text": 2,
+ "com.tldraw.shape.bookmark": 2,
+ "com.tldraw.shape.draw": 2,
+ "com.tldraw.shape.geo": 9,
+ "com.tldraw.shape.note": 7,
+ "com.tldraw.shape.line": 5,
+ "com.tldraw.shape.frame": 0,
+ "com.tldraw.shape.arrow": 5,
+ "com.tldraw.shape.highlight": 1,
+ "com.tldraw.shape.embed": 4,
+ "com.tldraw.shape.image": 3,
+ "com.tldraw.shape.video": 2,
+ "com.tldraw.shape.container": 0,
+ "com.tldraw.shape.element": 0,
+ "com.tldraw.binding.arrow": 0,
+ "com.tldraw.binding.layout": 0
+ }
+ }
+}
diff --git a/src/tools/CardShapeTool.tsx b/src/tools/CardShapeTool.tsx
deleted file mode 100644
index dcd0b72..0000000
--- a/src/tools/CardShapeTool.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import { BaseBoxShapeTool, TLClickEvent } from 'tldraw'
-export class CardShapeTool extends BaseBoxShapeTool {
- static override id = 'card'
- static override initial = 'idle'
- override shapeType = 'card'
-
- override onDoubleClick: TLClickEvent = (_info) => {
- // you can handle events in handlers like this one;
- // check the BaseBoxShapeTool source as an example
- }
-}
-
-/*
-This file contains our custom tool. The tool is a StateNode with the `id` "card".
-
-We get a lot of functionality for free by extending the BaseBoxShapeTool. but we can
-handle events in out own way by overriding methods like onDoubleClick. For an example
-of a tool with more custom functionality, check out the screenshot-tool example.
-
-*/
\ No newline at end of file
diff --git a/src/tools/ChatBoxTool.ts b/src/tools/ChatBoxTool.ts
index 6dd5e40..795bfda 100644
--- a/src/tools/ChatBoxTool.ts
+++ b/src/tools/ChatBoxTool.ts
@@ -1,6 +1,7 @@
import { BaseBoxShapeTool } from "tldraw";
export class ChatBoxTool extends BaseBoxShapeTool {
- shapeType = 'chatBox';
+ static override id = 'ChatBox'
+ shapeType = 'ChatBox';
override initial = 'idle';
}
\ No newline at end of file
diff --git a/src/tools/VideoChatTool.ts b/src/tools/VideoChatTool.ts
index c3dde15..de0f6d0 100644
--- a/src/tools/VideoChatTool.ts
+++ b/src/tools/VideoChatTool.ts
@@ -1,7 +1,8 @@
import { BaseBoxShapeTool } from "tldraw";
export class VideoChatTool extends BaseBoxShapeTool {
- shapeType = 'videoChat';
+ static override id = 'VideoChat'
+ shapeType = 'VideoChat';
override initial = 'idle';
// Additional methods for handling video chat functionality can be added here
diff --git a/src/ui-overrides.tsx b/src/ui-overrides.tsx
new file mode 100644
index 0000000..0bddd0b
--- /dev/null
+++ b/src/ui-overrides.tsx
@@ -0,0 +1,48 @@
+import {
+ DefaultToolbar,
+ DefaultToolbarContent,
+ TLComponents,
+ TLUiOverrides,
+ TldrawUiMenuItem,
+ useIsToolSelected,
+ useTools,
+} from 'tldraw'
+
+export const uiOverrides: TLUiOverrides = {
+ tools(editor, tools) {
+ tools.VideoChat = {
+ id: 'VideoChat',
+ icon: 'color',
+ label: 'Video',
+ kbd: 'x',
+ onSelect: () => {
+ editor.setCurrentTool('VideoChat')
+ },
+ }
+ tools.ChatBox = {
+ id: 'ChatBox',
+ icon: 'color',
+ label: 'Chat',
+ kbd: 'x',
+ onSelect: () => {
+ editor.setCurrentTool('ChatBox')
+ },
+ }
+ return tools
+ },
+}
+
+export const components: TLComponents = {
+ Toolbar: (props) => {
+ const tools = useTools()
+ const isChatBoxSelected = useIsToolSelected(tools['ChatBox'])
+ const isVideoSelected = useIsToolSelected(tools['VideoChat'])
+ return (
+
+
+
+
+
+ )
+ },
+}
\ No newline at end of file
diff --git a/src/utils/ContainerShapeUtil.tsx b/src/utils/ContainerShapeUtil.tsx
new file mode 100644
index 0000000..e69de29
diff --git a/src/utils/ElementShapeUtil.tsx b/src/utils/ElementShapeUtil.tsx
new file mode 100644
index 0000000..e69de29
diff --git a/src/shapes/HTMLShapeUtil.tsx b/src/utils/HTMLShapeUtil.tsx
similarity index 100%
rename from src/shapes/HTMLShapeUtil.tsx
rename to src/utils/HTMLShapeUtil.tsx
diff --git a/src/utils/card-shape-migrations.ts b/src/utils/card-shape-migrations.ts
new file mode 100644
index 0000000..6021ec8
--- /dev/null
+++ b/src/utils/card-shape-migrations.ts
@@ -0,0 +1,25 @@
+import { createShapePropsMigrationIds, createShapePropsMigrationSequence } from 'tldraw'
+
+const versions = createShapePropsMigrationIds(
+ // this must match the shape type in the shape definition
+ 'card',
+ {
+ AddSomeProperty: 1,
+ }
+)
+
+// Migrations for the custom card shape (optional but very helpful)
+export const cardShapeMigrations = createShapePropsMigrationSequence({
+ sequence: [
+ {
+ id: versions.AddSomeProperty,
+ up(props) {
+ // it is safe to mutate the props object here
+ props.someProperty = 'some value'
+ },
+ down(props) {
+ delete props.someProperty
+ },
+ },
+ ],
+})
\ No newline at end of file
diff --git a/src/utils/card-shape-props.ts b/src/utils/card-shape-props.ts
new file mode 100644
index 0000000..bead2fb
--- /dev/null
+++ b/src/utils/card-shape-props.ts
@@ -0,0 +1,11 @@
+import { DefaultColorStyle, RecordProps, T } from 'tldraw'
+import { ICardShape } from './card-shape-types'
+
+// Validation for our custom card shape's props, using one of tldraw's default styles
+export const cardShapeProps: RecordProps = {
+ w: T.number,
+ h: T.number,
+ color: DefaultColorStyle,
+}
+
+// To generate your own custom styles, check out the custom styles example.
\ No newline at end of file
diff --git a/src/utils/card-shape-types.ts b/src/utils/card-shape-types.ts
new file mode 100644
index 0000000..0ec08e0
--- /dev/null
+++ b/src/utils/card-shape-types.ts
@@ -0,0 +1,11 @@
+import { TLBaseShape, TLDefaultColorStyle } from 'tldraw'
+
+// A type for our custom card shape
+export type ICardShape = TLBaseShape<
+ 'card',
+ {
+ w: number
+ h: number
+ color: TLDefaultColorStyle
+ }
+>
\ No newline at end of file
diff --git a/src/utils/my-interactive-shape-util.tsx b/src/utils/my-interactive-shape-util.tsx
new file mode 100644
index 0000000..ec3ecc7
--- /dev/null
+++ b/src/utils/my-interactive-shape-util.tsx
@@ -0,0 +1,120 @@
+import { BaseBoxShapeUtil, HTMLContainer, RecordProps, T, TLBaseShape } from 'tldraw'
+
+// There's a guide at the bottom of this file!
+
+type IMyInteractiveShape = TLBaseShape<
+ 'my-interactive-shape',
+ {
+ w: number
+ h: number
+ checked: boolean
+ text: string
+ }
+>
+
+export class myInteractiveShape extends BaseBoxShapeUtil {
+ static override type = 'my-interactive-shape' as const
+ static override props: RecordProps = {
+ w: T.number,
+ h: T.number,
+ checked: T.boolean,
+ text: T.string,
+ }
+
+ getDefaultProps(): IMyInteractiveShape['props'] {
+ return {
+ w: 230,
+ h: 230,
+ checked: false,
+ text: '',
+ }
+ }
+
+ // [1]
+ component(shape: IMyInteractiveShape) {
+ return (
+
+
+ this.editor.updateShape({
+ id: shape.id,
+ type: 'my-interactive-shape',
+ props: { checked: !shape.props.checked },
+ })
+ }
+ // [b] This is where we stop event propagation
+ onPointerDown={(e) => e.stopPropagation()}
+ onTouchStart={(e) => e.stopPropagation()}
+ onTouchEnd={(e) => e.stopPropagation()}
+ />
+
+ this.editor.updateShape({
+ id: shape.id,
+ type: 'my-interactive-shape',
+ props: { text: e.currentTarget.value },
+ })
+ }
+ // [c]
+ onPointerDown={(e) => {
+ if (!shape.props.checked) {
+ e.stopPropagation()
+ }
+ }}
+ onTouchStart={(e) => {
+ if (!shape.props.checked) {
+ e.stopPropagation()
+ }
+ }}
+ onTouchEnd={(e) => {
+ if (!shape.props.checked) {
+ e.stopPropagation()
+ }
+ }}
+ />
+
+ )
+ }
+
+ // [5]
+ indicator(shape: IMyInteractiveShape) {
+ return
+ }
+}
+
+/*
+This is a custom shape, for a more in-depth look at how to create a custom shape,
+see our custom shape example.
+
+[1]
+This is where we describe how our shape will render
+
+ [a] We need to set pointer-events to all so that we can interact with our shape. This CSS property is
+ set to "none" off by default. We need to manually opt-in to accepting pointer events by setting it to
+ 'all' or 'auto'.
+
+ [b] We need to stop event propagation so that the editor doesn't select the shape
+ when we click on the checkbox. The 'canvas container' forwards events that it receives
+ on to the editor, so stopping propagation here prevents the event from reaching the canvas.
+
+ [c] If the shape is not checked, we stop event propagation so that the editor doesn't
+ select the shape when we click on the input. If the shape is checked then we allow that event to
+ propagate to the canvas and then get sent to the editor, triggering clicks or drags as usual.
+
+*/
diff --git a/src/utils.tsx b/src/utils/utils.tsx
similarity index 100%
rename from src/utils.tsx
rename to src/utils/utils.tsx
diff --git a/ui-overrides.tsx b/ui-overrides.tsx
deleted file mode 100644
index b709669..0000000
--- a/ui-overrides.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import React from 'react'
-import {
- DefaultKeyboardShortcutsDialog,
- DefaultKeyboardShortcutsDialogContent,
- DefaultToolbar,
- DefaultToolbarContent,
- TLComponents,
- TLUiOverrides,
- TldrawUiMenuItem,
- useIsToolSelected,
- useTools,
-} from 'tldraw'
-
-// There's a guide at the bottom of this file!
-
-export const uiOverrides: TLUiOverrides = {
- tools(editor, tools) {
- // Create a tool item in the ui's context.
- tools.card = {
- id: 'card',
- icon: 'color',
- label: 'Card',
- kbd: 'c',
- onSelect: () => {
- editor.setCurrentTool('card')
- },
- }
- return tools
- },
-}
-
-export const components: TLComponents = {
- Toolbar: (props) => {
- const tools = useTools()
- const isCardSelected = useIsToolSelected(tools['card'])
- return (
-
-
-
-
- )
- },
- KeyboardShortcutsDialog: (props) => {
- const tools = useTools()
- return (
-
-
-
-
- )
- },
-}
-
-/*
-
-This file contains overrides for the Tldraw UI. These overrides are used to add your custom tools to
-the toolbar and the keyboard shortcuts menu.
-
-First we have to add our new tool to the tools object in the tools override. This is where we define
-all the basic information about our new tool - its icon, label, keyboard shortcut, what happens when
-we select it, etc.
-
-Then, we replace the UI components for the toolbar and keyboard shortcut dialog with our own, that
-add our new tool to the existing default content. Ideally, we'd interleave our new tool into the
-ideal place among the default tools, but for now we're just adding it at the start to keep things
-simple.
-*/
\ No newline at end of file
diff --git a/worker/TldrawDurableObject.ts b/worker/TldrawDurableObject.ts
index ed42779..b5e0274 100644
--- a/worker/TldrawDurableObject.ts
+++ b/worker/TldrawDurableObject.ts
@@ -10,12 +10,12 @@ import {
import { AutoRouter, IRequest, error } from 'itty-router'
import throttle from 'lodash.throttle'
import { Environment } from './types'
-import { ChatBoxShape } from '@/shapes/ChatBoxShape'
-import { VideoChatShape } from '@/shapes/VideoChatShape'
+import { ChatBoxShape } from '@/shapes/ChatBoxShapeUtil'
+import { VideoChatShape } from '@/shapes/VideoChatShapeUtil'
// add custom shapes and bindings here if needed:
export const customSchema = createTLSchema({
- shapes: { ...defaultShapeSchemas, chatBox: ChatBoxShape, videoChat: VideoChatShape },
+ shapes: { ...defaultShapeSchemas, ChatBox: ChatBoxShape, VideoChat: VideoChatShape },
// bindings: { ...defaultBindingSchemas },
})