diff --git a/packages/editor/api-report.md b/packages/editor/api-report.md index e2ca21d9d..66d5e4c25 100644 --- a/packages/editor/api-report.md +++ b/packages/editor/api-report.md @@ -77,6 +77,9 @@ import { useValue } from '@tldraw/state'; import { VecModel } from '@tldraw/tlschema'; import { whyAmIRunning } from '@tldraw/state'; +// @public (undocumented) +export function alertMaxShapes(editor: Editor, pageId?: TLPageId): void; + // @public export function angleDistance(fromAngle: number, toAngle: number, direction: number): number; @@ -830,6 +833,7 @@ export class Editor extends EventEmitter { // (undocumented) isShapeOrAncestorLocked(id?: TLShapeId): boolean; mark(markId?: string, onUndo?: boolean, onRedo?: boolean): this; + maxShapesReached(): boolean; moveShapesToPage(shapes: TLShape[] | TLShapeId[], pageId: TLPageId): this; nudgeShapes(shapes: TLShape[] | TLShapeId[], offset: VecLike, historyOptions?: TLCommandHistoryOptions): this; packShapes(shapes: TLShape[] | TLShapeId[], gap: number): this; diff --git a/packages/editor/api/api.json b/packages/editor/api/api.json index 6945f077c..2daa34512 100644 --- a/packages/editor/api/api.json +++ b/packages/editor/api/api.json @@ -172,6 +172,69 @@ "name": "", "preserveMemberOrder": false, "members": [ + { + "kind": "Function", + "canonicalReference": "@tldraw/editor!alertMaxShapes:function(1)", + "docComment": "/**\n * @public\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "export declare function alertMaxShapes(editor: " + }, + { + "kind": "Reference", + "text": "Editor", + "canonicalReference": "@tldraw/editor!Editor:class" + }, + { + "kind": "Content", + "text": ", pageId?: " + }, + { + "kind": "Reference", + "text": "TLPageId", + "canonicalReference": "@tldraw/tlschema!TLPageId:type" + }, + { + "kind": "Content", + "text": "): " + }, + { + "kind": "Content", + "text": "void" + }, + { + "kind": "Content", + "text": ";" + } + ], + "fileUrlPath": "packages/editor/src/lib/editor/Editor.ts", + "returnTypeTokenRange": { + "startIndex": 5, + "endIndex": 6 + }, + "releaseTag": "Public", + "overloadIndex": 1, + "parameters": [ + { + "parameterName": "editor", + "parameterTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "isOptional": false + }, + { + "parameterName": "pageId", + "parameterTypeTokenRange": { + "startIndex": 3, + "endIndex": 4 + }, + "isOptional": true + } + ], + "name": "alertMaxShapes" + }, { "kind": "Function", "canonicalReference": "@tldraw/editor!angleDistance:function(1)", @@ -15374,6 +15437,37 @@ "isAbstract": false, "name": "mark" }, + { + "kind": "Method", + "canonicalReference": "@tldraw/editor!Editor#maxShapesReached:member(1)", + "docComment": "/**\n * Get whether we have reached the maximum number of shapes per page.\n *\n * @public\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "maxShapesReached(): " + }, + { + "kind": "Content", + "text": "boolean" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isStatic": false, + "returnTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "releaseTag": "Public", + "isProtected": false, + "overloadIndex": 1, + "parameters": [], + "isOptional": false, + "isAbstract": false, + "name": "maxShapesReached" + }, { "kind": "Method", "canonicalReference": "@tldraw/editor!Editor#moveShapesToPage:member(1)", diff --git a/packages/editor/src/index.ts b/packages/editor/src/index.ts index 51b62ba6c..0ababc4fc 100644 --- a/packages/editor/src/index.ts +++ b/packages/editor/src/index.ts @@ -130,6 +130,7 @@ export { } from './lib/constants' export { Editor, + alertMaxShapes, type TLAnimationOptions, type TLEditorOptions, type TLResizeShapeOptions, diff --git a/packages/editor/src/lib/editor/Editor.ts b/packages/editor/src/lib/editor/Editor.ts index 4f1b12f72..6ff1eaf82 100644 --- a/packages/editor/src/lib/editor/Editor.ts +++ b/packages/editor/src/lib/editor/Editor.ts @@ -8212,6 +8212,15 @@ export class Editor extends EventEmitter { ]) } + /** + * Get whether we have reached the maximum number of shapes per page. + * + * @public + */ + maxShapesReached() { + return this.getCurrentPageShapeIds().size >= MAX_SHAPES_PER_PAGE + } + /** * Dispatch a cancel event. * @@ -8843,7 +8852,8 @@ export class Editor extends EventEmitter { } } -function alertMaxShapes(editor: Editor, pageId = editor.getCurrentPageId()) { +/** @public */ +export function alertMaxShapes(editor: Editor, pageId = editor.getCurrentPageId()) { const name = editor.getPage(pageId)!.name editor.emit('max-shapes', { name, pageId, count: MAX_SHAPES_PER_PAGE }) } diff --git a/packages/editor/src/lib/editor/tools/BaseBoxShapeTool/children/Pointing.ts b/packages/editor/src/lib/editor/tools/BaseBoxShapeTool/children/Pointing.ts index f9339d06d..560736d20 100644 --- a/packages/editor/src/lib/editor/tools/BaseBoxShapeTool/children/Pointing.ts +++ b/packages/editor/src/lib/editor/tools/BaseBoxShapeTool/children/Pointing.ts @@ -1,5 +1,6 @@ import { createShapeId } from '@tldraw/tlschema' import { Vec } from '../../../../primitives/Vec' +import { alertMaxShapes } from '../../../Editor' import { TLBaseBoxShape } from '../../../shapes/BaseBoxShapeUtil' import { TLEventHandlers } from '../../../types/event-types' import { StateNode } from '../../StateNode' @@ -18,6 +19,11 @@ export class Pointing extends StateNode { override onPointerMove: TLEventHandlers['onPointerMove'] = (info) => { if (this.editor.inputs.isDragging) { + if (this.editor.maxShapesReached()) { + alertMaxShapes(this.editor) + this.cancel() + return + } const { originPagePoint } = this.editor.inputs const shapeType = (this.parent as BaseBoxShapeTool)!.shapeType @@ -28,20 +34,19 @@ export class Pointing extends StateNode { this.editor.mark(this.markId) - this.editor - .createShapes([ - { - id, - type: shapeType, - x: originPagePoint.x, - y: originPagePoint.y, - props: { - w: 1, - h: 1, - }, + this.editor.createShapes([ + { + id, + type: shapeType, + x: originPagePoint.x, + y: originPagePoint.y, + props: { + w: 1, + h: 1, }, - ]) - .select(id) + }, + ]) + this.editor.select(id) this.editor.setCurrentTool('select.resizing', { ...info, target: 'selection', @@ -77,6 +82,12 @@ export class Pointing extends StateNode { return } + if (this.editor.maxShapesReached()) { + alertMaxShapes(this.editor) + this.cancel() + return + } + this.editor.mark(this.markId) const shapeType = (this.parent as BaseBoxShapeTool)!.shapeType as TLBaseBoxShape['type'] diff --git a/packages/tldraw/src/lib/shapes/arrow/toolStates/Pointing.ts b/packages/tldraw/src/lib/shapes/arrow/toolStates/Pointing.ts index acd616f76..9f0ed0984 100644 --- a/packages/tldraw/src/lib/shapes/arrow/toolStates/Pointing.ts +++ b/packages/tldraw/src/lib/shapes/arrow/toolStates/Pointing.ts @@ -1,4 +1,10 @@ -import { StateNode, TLArrowShape, TLEventHandlers, createShapeId } from '@tldraw/editor' +import { + StateNode, + TLArrowShape, + TLEventHandlers, + alertMaxShapes, + createShapeId, +} from '@tldraw/editor' export class Pointing extends StateNode { static override id = 'pointing' @@ -79,6 +85,12 @@ export class Pointing extends StateNode { } createArrowShape() { + if (this.editor.maxShapesReached()) { + alertMaxShapes(this.editor) + this.cancel() + return + } + const { originPagePoint } = this.editor.inputs const id = createShapeId() diff --git a/packages/tldraw/src/lib/shapes/geo/toolStates/Pointing.ts b/packages/tldraw/src/lib/shapes/geo/toolStates/Pointing.ts index 3445efd16..da37d026b 100644 --- a/packages/tldraw/src/lib/shapes/geo/toolStates/Pointing.ts +++ b/packages/tldraw/src/lib/shapes/geo/toolStates/Pointing.ts @@ -4,6 +4,7 @@ import { StateNode, TLEventHandlers, TLGeoShape, + alertMaxShapes, createShapeId, } from '@tldraw/editor' @@ -18,6 +19,11 @@ export class Pointing extends StateNode { override onPointerMove: TLEventHandlers['onPointerMove'] = (info) => { if (this.editor.inputs.isDragging) { + if (this.editor.maxShapesReached()) { + alertMaxShapes(this.editor) + this.cancel() + return + } const { originPagePoint } = this.editor.inputs const id = createShapeId() diff --git a/packages/tldraw/src/lib/shapes/line/toolStates/Pointing.ts b/packages/tldraw/src/lib/shapes/line/toolStates/Pointing.ts index 1ee5b49f3..f1b99d8c9 100644 --- a/packages/tldraw/src/lib/shapes/line/toolStates/Pointing.ts +++ b/packages/tldraw/src/lib/shapes/line/toolStates/Pointing.ts @@ -6,6 +6,7 @@ import { TLLineShape, TLShapeId, Vec, + alertMaxShapes, createShapeId, getIndexAbove, last, @@ -84,6 +85,12 @@ export class Pointing extends StateNode { }, ]) } else { + if (this.editor.maxShapesReached()) { + alertMaxShapes(this.editor) + this.cancel() + return + } + const id = createShapeId() this.markId = `creating:${id}` diff --git a/packages/tldraw/src/lib/shapes/note/toolStates/Pointing.ts b/packages/tldraw/src/lib/shapes/note/toolStates/Pointing.ts index 171c61a19..4beb724cc 100644 --- a/packages/tldraw/src/lib/shapes/note/toolStates/Pointing.ts +++ b/packages/tldraw/src/lib/shapes/note/toolStates/Pointing.ts @@ -4,6 +4,7 @@ import { TLInterruptEvent, TLNoteShape, TLPointerEventInfo, + alertMaxShapes, createShapeId, } from '@tldraw/editor' @@ -23,14 +24,24 @@ export class Pointing extends StateNode { override onEnter = () => { this.wasFocusedOnEnter = !this.editor.getIsMenuOpen() if (this.wasFocusedOnEnter) { - this.shape = this.createShape() + const createdShape = this.createShape() + if (!createdShape) { + this.cancel() + return + } + this.shape = createdShape } } override onPointerMove: TLEventHandlers['onPointerMove'] = (info) => { if (this.editor.inputs.isDragging) { if (!this.wasFocusedOnEnter) { - this.shape = this.createShape() + const createdShape = this.createShape() + if (!createdShape) { + this.cancel() + return + } + this.shape = createdShape } this.editor.setCurrentTool('select.translating', { @@ -84,6 +95,10 @@ export class Pointing extends StateNode { } private createShape() { + if (this.editor.maxShapesReached()) { + alertMaxShapes(this.editor) + return null + } const { inputs: { originPagePoint }, } = this.editor @@ -92,16 +107,15 @@ export class Pointing extends StateNode { this.markId = `creating:${id}` this.editor.mark(this.markId) - this.editor - .createShapes([ - { - id, - type: 'note', - x: originPagePoint.x, - y: originPagePoint.y, - }, - ]) - .select(id) + this.editor.createShapes([ + { + id, + type: 'note', + x: originPagePoint.x, + y: originPagePoint.y, + }, + ]) + this.editor.select(id) const shape = this.editor.getShape(id)! const bounds = this.editor.getShapeGeometry(shape).bounds diff --git a/packages/tldraw/src/lib/shapes/text/toolStates/Pointing.ts b/packages/tldraw/src/lib/shapes/text/toolStates/Pointing.ts index 1fc8bb648..da5831437 100644 --- a/packages/tldraw/src/lib/shapes/text/toolStates/Pointing.ts +++ b/packages/tldraw/src/lib/shapes/text/toolStates/Pointing.ts @@ -1,4 +1,10 @@ -import { StateNode, TLEventHandlers, TLTextShape, createShapeId } from '@tldraw/editor' +import { + StateNode, + TLEventHandlers, + TLTextShape, + alertMaxShapes, + createShapeId, +} from '@tldraw/editor' export class Pointing extends StateNode { static override id = 'pointing' @@ -13,6 +19,11 @@ export class Pointing extends StateNode { override onPointerMove: TLEventHandlers['onPointerMove'] = (info) => { if (this.editor.inputs.isDragging) { + if (this.editor.maxShapesReached()) { + alertMaxShapes(this.editor) + this.cancel() + return + } const { inputs: { originPagePoint }, } = this.editor @@ -75,6 +86,12 @@ export class Pointing extends StateNode { } private complete() { + if (this.editor.maxShapesReached()) { + alertMaxShapes(this.editor) + this.cancel() + return + } + this.editor.mark('creating text shape') const id = createShapeId() const { x, y } = this.editor.inputs.currentPagePoint