From 1367e4c50082acd7b08d5ced5947c2a4c7b3824e Mon Sep 17 00:00:00 2001 From: Steve Ruiz Date: Tue, 7 Nov 2023 09:27:20 +0000 Subject: [PATCH] [feature] Things on the canvas (#2150) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR adds two new component overrides to the editor's `components` slot. They are: - ``, which renders inside of the html layer that scales and translates with the camera - ``, which renders in front of the canvas but behind any UI elements, and which does not scale / pan with the camera. ![Kapture 2023-11-06 at 12 19 15](https://github.com/tldraw/tldraw/assets/23072548/51c0421d-8b39-48b5-9b8a-c717253c3423) ### Change Type - [x] `minor` — New feature ### Test Plan 1. See the "on the canvas" example. ### Release Notes - [editor] Adds two new components, `OnTheCanvas` and `InFrontOfTheCanvas`. --- apps/examples/src/examples/OnTheCanvas.tsx | 87 +++++++++++++++++++ apps/examples/src/index.tsx | 6 ++ packages/editor/api-report.md | 6 ++ packages/editor/api/api.json | 62 +++++++++++++ packages/editor/src/index.ts | 2 + packages/editor/src/lib/components/Canvas.tsx | 14 +++ .../DefaultInFrontOfTheCanvas.tsx | 4 + .../default-components/DefaultOnTheCanvas.tsx | 4 + .../src/lib/hooks/useEditorComponents.tsx | 8 +- packages/tldraw/api/api.json | 3 +- 10 files changed, 193 insertions(+), 3 deletions(-) create mode 100644 apps/examples/src/examples/OnTheCanvas.tsx create mode 100644 packages/editor/src/lib/components/default-components/DefaultInFrontOfTheCanvas.tsx create mode 100644 packages/editor/src/lib/components/default-components/DefaultOnTheCanvas.tsx diff --git a/apps/examples/src/examples/OnTheCanvas.tsx b/apps/examples/src/examples/OnTheCanvas.tsx new file mode 100644 index 000000000..ded21d4bf --- /dev/null +++ b/apps/examples/src/examples/OnTheCanvas.tsx @@ -0,0 +1,87 @@ +import { stopEventPropagation, Tldraw, TLEditorComponents, track, useEditor } from '@tldraw/tldraw' +import '@tldraw/tldraw/tldraw.css' +import { useState } from 'react' + +// The "OnTheCanvas" component is rendered on top of the canvas, but behind the UI. +function MyComponent() { + const [state, setState] = useState(0) + + return ( + <> +
+ The count is {state}! +
+
+ The count is {state}! +
+ + ) +} + +// The "InFrontOfTheCanvas" component is rendered on top of the canvas, but behind the UI. +const MyComponentInFront = track(() => { + const editor = useEditor() + const { selectionRotatedPageBounds } = editor + + if (!selectionRotatedPageBounds) return null + + const pageCoordinates = editor.pageToScreen(selectionRotatedPageBounds.point) + + return ( +
+ This does not scale with the zoom +
+ ) +}) + +const components: Partial = { + OnTheCanvas: MyComponent, + InFrontOfTheCanvas: MyComponentInFront, + SnapLine: null, +} + +export default function OnTheCanvasExample() { + return ( +
+ +
+ ) +} diff --git a/apps/examples/src/index.tsx b/apps/examples/src/index.tsx index 9e66a290a..6a4831a34 100644 --- a/apps/examples/src/index.tsx +++ b/apps/examples/src/index.tsx @@ -26,6 +26,7 @@ import ExternalContentSourcesExample from './examples/ExternalContentSourcesExam import HideUiExample from './examples/HideUiExample' import MetaExample from './examples/MetaExample' import MultipleExample from './examples/MultipleExample' +import OnTheCanvasExample from './examples/OnTheCanvas' import PersistenceExample from './examples/PersistenceExample' import ReadOnlyExample from './examples/ReadOnlyExample' import ScrollExample from './examples/ScrollExample' @@ -84,6 +85,11 @@ export const allExamples: Example[] = [ path: 'readonly', element: , }, + { + title: 'Things on the canvas', + path: 'things-on-the-canvas', + element: , + }, { title: 'Scroll example', path: 'scroll', diff --git a/packages/editor/api-report.md b/packages/editor/api-report.md index 8624fb674..6c50d83f7 100644 --- a/packages/editor/api-report.md +++ b/packages/editor/api-report.md @@ -2244,6 +2244,9 @@ export type TLHoveredShapeIndicatorComponent = ComponentType<{ shapeId: TLShapeId; }>; +// @public (undocumented) +export type TLInFrontOfTheCanvas = ComponentType; + // @public (undocumented) export type TLInterruptEvent = (info: TLInterruptEventInfo) => void; @@ -2322,6 +2325,9 @@ export type TLOnRotateHandler = TLEventChangeHandler; // @public (undocumented) export type TLOnRotateStartHandler = TLEventStartHandler; +// @public (undocumented) +export type TLOnTheCanvas = ComponentType; + // @public (undocumented) export type TLOnTranslateEndHandler = TLEventChangeHandler; diff --git a/packages/editor/api/api.json b/packages/editor/api/api.json index 139421e6b..e0355cb48 100644 --- a/packages/editor/api/api.json +++ b/packages/editor/api/api.json @@ -37949,6 +37949,37 @@ "endIndex": 5 } }, + { + "kind": "TypeAlias", + "canonicalReference": "@tldraw/editor!TLInFrontOfTheCanvas:type", + "docComment": "/**\n * @public\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "export type TLInFrontOfTheCanvas = " + }, + { + "kind": "Reference", + "text": "ComponentType", + "canonicalReference": "@types/react!React.ComponentType:type" + }, + { + "kind": "Content", + "text": "" + }, + { + "kind": "Content", + "text": ";" + } + ], + "fileUrlPath": "packages/editor/src/lib/components/default-components/DefaultInFrontOfTheCanvas.tsx", + "releaseTag": "Public", + "name": "TLInFrontOfTheCanvas", + "typeTokenRange": { + "startIndex": 1, + "endIndex": 3 + } + }, { "kind": "TypeAlias", "canonicalReference": "@tldraw/editor!TLInterruptEvent:type", @@ -39009,6 +39040,37 @@ "endIndex": 5 } }, + { + "kind": "TypeAlias", + "canonicalReference": "@tldraw/editor!TLOnTheCanvas:type", + "docComment": "/**\n * @public\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "export type TLOnTheCanvas = " + }, + { + "kind": "Reference", + "text": "ComponentType", + "canonicalReference": "@types/react!React.ComponentType:type" + }, + { + "kind": "Content", + "text": "" + }, + { + "kind": "Content", + "text": ";" + } + ], + "fileUrlPath": "packages/editor/src/lib/components/default-components/DefaultOnTheCanvas.tsx", + "releaseTag": "Public", + "name": "TLOnTheCanvas", + "typeTokenRange": { + "startIndex": 1, + "endIndex": 3 + } + }, { "kind": "TypeAlias", "canonicalReference": "@tldraw/editor!TLOnTranslateEndHandler:type", diff --git a/packages/editor/src/index.ts b/packages/editor/src/index.ts index 758652b28..c754df6fd 100644 --- a/packages/editor/src/index.ts +++ b/packages/editor/src/index.ts @@ -73,6 +73,8 @@ export { DefaultHoveredShapeIndicator, type TLHoveredShapeIndicatorComponent, } from './lib/components/default-components/DefaultHoveredShapeIndicator' +export { type TLInFrontOfTheCanvas } from './lib/components/default-components/DefaultInFrontOfTheCanvas' +export { type TLOnTheCanvas } from './lib/components/default-components/DefaultOnTheCanvas' export { DefaultScribble, type TLScribbleComponent, diff --git a/packages/editor/src/lib/components/Canvas.tsx b/packages/editor/src/lib/components/Canvas.tsx index b12ae1646..b541d5954 100644 --- a/packages/editor/src/lib/components/Canvas.tsx +++ b/packages/editor/src/lib/components/Canvas.tsx @@ -109,6 +109,7 @@ export function Canvas({ className }: { className?: string }) {
+ {hideShapes ? null : debugSvg ? : }
@@ -126,6 +127,7 @@ export function Canvas({ className }: { className?: string }) { + ) @@ -518,3 +520,15 @@ export function SelectionBackgroundWrapper() { if (!selectionBounds || !SelectionBackground) return null return } + +export function OnTheCanvasWrapper() { + const { OnTheCanvas } = useEditorComponents() + if (!OnTheCanvas) return null + return +} + +export function InFrontOfTheCanvasWrapper() { + const { InFrontOfTheCanvas } = useEditorComponents() + if (!InFrontOfTheCanvas) return null + return +} diff --git a/packages/editor/src/lib/components/default-components/DefaultInFrontOfTheCanvas.tsx b/packages/editor/src/lib/components/default-components/DefaultInFrontOfTheCanvas.tsx new file mode 100644 index 000000000..0ce033ddd --- /dev/null +++ b/packages/editor/src/lib/components/default-components/DefaultInFrontOfTheCanvas.tsx @@ -0,0 +1,4 @@ +import { ComponentType } from 'react' + +/** @public */ +export type TLInFrontOfTheCanvas = ComponentType diff --git a/packages/editor/src/lib/components/default-components/DefaultOnTheCanvas.tsx b/packages/editor/src/lib/components/default-components/DefaultOnTheCanvas.tsx new file mode 100644 index 000000000..f6ad07e14 --- /dev/null +++ b/packages/editor/src/lib/components/default-components/DefaultOnTheCanvas.tsx @@ -0,0 +1,4 @@ +import { ComponentType } from 'react' + +/** @public */ +export type TLOnTheCanvas = ComponentType diff --git a/packages/editor/src/lib/hooks/useEditorComponents.tsx b/packages/editor/src/lib/hooks/useEditorComponents.tsx index cc93686f3..63bf5533d 100644 --- a/packages/editor/src/lib/hooks/useEditorComponents.tsx +++ b/packages/editor/src/lib/hooks/useEditorComponents.tsx @@ -21,6 +21,8 @@ import { DefaultHoveredShapeIndicator, TLHoveredShapeIndicatorComponent, } from '../components/default-components/DefaultHoveredShapeIndicator' +import { TLInFrontOfTheCanvas } from '../components/default-components/DefaultInFrontOfTheCanvas' +import { TLOnTheCanvas } from '../components/default-components/DefaultOnTheCanvas' import { DefaultScribble, TLScribbleComponent, @@ -48,7 +50,7 @@ import { import { DefaultSpinner, TLSpinnerComponent } from '../components/default-components/DefaultSpinner' import { DefaultSvgDefs, TLSvgDefsComponent } from '../components/default-components/DefaultSvgDefs' -interface BaseEditorComponents { +export interface BaseEditorComponents { Background: TLBackgroundComponent SvgDefs: TLSvgDefsComponent Brush: TLBrushComponent @@ -68,6 +70,8 @@ interface BaseEditorComponents { SelectionForeground: TLSelectionForegroundComponent SelectionBackground: TLSelectionBackgroundComponent HoveredShapeIndicator: TLHoveredShapeIndicatorComponent + OnTheCanvas: TLOnTheCanvas + InFrontOfTheCanvas: TLInFrontOfTheCanvas } /** @public */ @@ -113,6 +117,8 @@ export function EditorComponentsProvider({ overrides, children }: ComponentsCont SelectionBackground: DefaultSelectionBackground, SelectionForeground: DefaultSelectionForeground, HoveredShapeIndicator: DefaultHoveredShapeIndicator, + OnTheCanvas: null, + InFrontOfTheCanvas: null, ...overrides, }), [overrides] diff --git a/packages/tldraw/api/api.json b/packages/tldraw/api/api.json index f57682a30..dadb4a2a8 100644 --- a/packages/tldraw/api/api.json +++ b/packages/tldraw/api/api.json @@ -14583,7 +14583,7 @@ "text": "export interface TLUiContextMenuProps " } ], - "fileUrlPath": "packages/tldraw/.tsbuild-api/lib/ui/components/ContextMenu.d.ts", + "fileUrlPath": "packages/tldraw/src/lib/ui/components/ContextMenu.tsx", "releaseTag": "Public", "name": "TLUiContextMenuProps", "preserveMemberOrder": false, @@ -14606,7 +14606,6 @@ "text": ";" } ], - "fileUrlPath": "packages/tldraw/src/lib/ui/components/ContextMenu.tsx", "isReadonly": false, "isOptional": false, "releaseTag": "Public",