history options / markId / createPage (#1796)
This PR: - adds history options to several commands in order to allow them to support squashing and ephemeral data (previously, these commands had boolean values for squashing / ephemeral) It also: - changes `markId` to return the editor instance rather than the mark id passed into the command - removes `focus` and `blur` commands - changes `createPage` parameters - unifies `animateShape` / `animateShapes` options ### Change Type - [x] `major` — Breaking change ### Test Plan - [x] Unit Tests
This commit is contained in:
parent
ae56d975e0
commit
8991468446
72 changed files with 977 additions and 505 deletions
|
@ -22,8 +22,6 @@ export default function APIExample() {
|
||||||
// Create a shape id
|
// Create a shape id
|
||||||
const id = createShapeId('hello')
|
const id = createShapeId('hello')
|
||||||
|
|
||||||
editor.focus()
|
|
||||||
|
|
||||||
// Create a shape
|
// Create a shape
|
||||||
editor.createShapes<TLGeoShape>([
|
editor.createShapes<TLGeoShape>([
|
||||||
{
|
{
|
||||||
|
@ -72,7 +70,7 @@ export default function APIExample() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="tldraw__editor">
|
<div className="tldraw__editor">
|
||||||
<Tldraw persistenceKey="api-example" onMount={handleMount} autoFocus={false}>
|
<Tldraw persistenceKey="api-example" onMount={handleMount}>
|
||||||
<InsideOfEditorContext />
|
<InsideOfEditorContext />
|
||||||
</Tldraw>
|
</Tldraw>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -40,6 +40,7 @@ import { TLAssetPartial } from '@tldraw/tlschema';
|
||||||
import { TLBaseShape } from '@tldraw/tlschema';
|
import { TLBaseShape } from '@tldraw/tlschema';
|
||||||
import { TLBookmarkAsset } from '@tldraw/tlschema';
|
import { TLBookmarkAsset } from '@tldraw/tlschema';
|
||||||
import { TLCamera } from '@tldraw/tlschema';
|
import { TLCamera } from '@tldraw/tlschema';
|
||||||
|
import { TLCursor } from '@tldraw/tlschema';
|
||||||
import { TLCursorType } from '@tldraw/tlschema';
|
import { TLCursorType } from '@tldraw/tlschema';
|
||||||
import { TLDefaultHorizontalAlignStyle } from '@tldraw/tlschema';
|
import { TLDefaultHorizontalAlignStyle } from '@tldraw/tlschema';
|
||||||
import { TLDocument } from '@tldraw/tlschema';
|
import { TLDocument } from '@tldraw/tlschema';
|
||||||
|
@ -530,14 +531,11 @@ export class Editor extends EventEmitter<TLEventMap> {
|
||||||
alignShapes(shapes: TLShape[], operation: 'bottom' | 'center-horizontal' | 'center-vertical' | 'left' | 'right' | 'top'): this;
|
alignShapes(shapes: TLShape[], operation: 'bottom' | 'center-horizontal' | 'center-vertical' | 'left' | 'right' | 'top'): this;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
alignShapes(ids: TLShapeId[], operation: 'bottom' | 'center-horizontal' | 'center-vertical' | 'left' | 'right' | 'top'): this;
|
alignShapes(ids: TLShapeId[], operation: 'bottom' | 'center-horizontal' | 'center-vertical' | 'left' | 'right' | 'top'): this;
|
||||||
animateShape(partial: null | TLShapePartial | undefined, options?: Partial<{
|
animateShape(partial: null | TLShapePartial | undefined, animationOptions?: TLAnimationOptions): this;
|
||||||
|
animateShapes(partials: (null | TLShapePartial | undefined)[], animationOptions?: Partial<{
|
||||||
duration: number;
|
duration: number;
|
||||||
ease: (t: number) => number;
|
easing: (t: number) => number;
|
||||||
}>): this;
|
}>): this;
|
||||||
animateShapes(partials: (null | TLShapePartial | undefined)[], options?: {
|
|
||||||
duration?: number;
|
|
||||||
ease?: (t: number) => number;
|
|
||||||
}): this;
|
|
||||||
animateToShape(shapeId: TLShapeId, opts?: TLAnimationOptions): this;
|
animateToShape(shapeId: TLShapeId, opts?: TLAnimationOptions): this;
|
||||||
animateToUser(userId: string): this;
|
animateToUser(userId: string): this;
|
||||||
// @internal (undocumented)
|
// @internal (undocumented)
|
||||||
|
@ -551,8 +549,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
||||||
bail(): this;
|
bail(): this;
|
||||||
bailToMark(id: string): this;
|
bailToMark(id: string): this;
|
||||||
batch(fn: () => void): this;
|
batch(fn: () => void): this;
|
||||||
// (undocumented)
|
|
||||||
blur: () => void;
|
|
||||||
bringForward(shapes: TLShape[]): this;
|
bringForward(shapes: TLShape[]): this;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
bringForward(ids: TLShapeId[]): this;
|
bringForward(ids: TLShapeId[]): this;
|
||||||
|
@ -589,9 +585,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
||||||
inputs?: Record<string, unknown>;
|
inputs?: Record<string, unknown>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
createPage(title: string, id?: TLPageId, belowPageIndex?: string): this;
|
createPage(page: Partial<TLPage>): this;
|
||||||
createShape<T extends TLUnknownShape>(partial: OptionalKeys<TLShapePartial<T>, 'id'>): this;
|
createShape<T extends TLUnknownShape>(shape: OptionalKeys<TLShapePartial<T>, 'id'>): this;
|
||||||
createShapes<T extends TLUnknownShape>(partials: OptionalKeys<TLShapePartial<T>, 'id'>[]): this;
|
createShapes<T extends TLUnknownShape>(shapes: OptionalKeys<TLShapePartial<T>, 'id'>[]): this;
|
||||||
get croppingShapeId(): null | TLShapeId;
|
get croppingShapeId(): null | TLShapeId;
|
||||||
get currentPage(): TLPage;
|
get currentPage(): TLPage;
|
||||||
get currentPageBounds(): Box2d | undefined;
|
get currentPageBounds(): Box2d | undefined;
|
||||||
|
@ -660,14 +656,13 @@ export class Editor extends EventEmitter<TLEventMap> {
|
||||||
flipShapes(shapes: TLShape[], operation: 'horizontal' | 'vertical'): this;
|
flipShapes(shapes: TLShape[], operation: 'horizontal' | 'vertical'): this;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
flipShapes(ids: TLShapeId[], operation: 'horizontal' | 'vertical'): this;
|
flipShapes(ids: TLShapeId[], operation: 'horizontal' | 'vertical'): this;
|
||||||
// (undocumented)
|
|
||||||
focus: () => void;
|
|
||||||
get focusedGroupId(): TLPageId | TLShapeId;
|
get focusedGroupId(): TLPageId | TLShapeId;
|
||||||
getAncestorPageId(shape?: TLShape): TLPageId | undefined;
|
getAncestorPageId(shape?: TLShape): TLPageId | undefined;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
getAncestorPageId(shapeId?: TLShapeId): TLPageId | undefined;
|
getAncestorPageId(shapeId?: TLShapeId): TLPageId | undefined;
|
||||||
|
getArrowInfo(shape: TLArrowShape): TLArrowInfo | undefined;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
getArrowInfo(shape: TLArrowShape): ArrowInfo | undefined;
|
getArrowInfo(id: TLShapeId): TLArrowInfo | undefined;
|
||||||
getArrowsBoundTo(shapeId: TLShapeId): {
|
getArrowsBoundTo(shapeId: TLShapeId): {
|
||||||
arrowId: TLShapeId;
|
arrowId: TLShapeId;
|
||||||
handleId: "end" | "start";
|
handleId: "end" | "start";
|
||||||
|
@ -677,9 +672,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
||||||
getAsset(id: TLAssetId): TLAsset | undefined;
|
getAsset(id: TLAssetId): TLAsset | undefined;
|
||||||
getAssetForExternalContent(info: TLExternalAssetContent_2): Promise<TLAsset | undefined>;
|
getAssetForExternalContent(info: TLExternalAssetContent_2): Promise<TLAsset | undefined>;
|
||||||
getContainer: () => HTMLElement;
|
getContainer: () => HTMLElement;
|
||||||
getContent(ids: TLShapeId[]): TLContent | undefined;
|
getContentFromCurrentPage(ids: TLShapeId[]): TLContent | undefined;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
getContent(shapes: TLShape[]): TLContent | undefined;
|
getContentFromCurrentPage(shapes: TLShape[]): TLContent | undefined;
|
||||||
getCurrentPageShapeIds(pageId: TLPageId): Set<TLShapeId>;
|
getCurrentPageShapeIds(pageId: TLPageId): Set<TLShapeId>;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
getCurrentPageShapeIds(page: TLPage): Set<TLShapeId>;
|
getCurrentPageShapeIds(page: TLPage): Set<TLShapeId>;
|
||||||
|
@ -772,9 +767,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
||||||
darkMode?: boolean | undefined;
|
darkMode?: boolean | undefined;
|
||||||
preserveAspectRatio: React.SVGAttributes<SVGSVGElement>['preserveAspectRatio'];
|
preserveAspectRatio: React.SVGAttributes<SVGSVGElement>['preserveAspectRatio'];
|
||||||
}>): Promise<SVGSVGElement | undefined>;
|
}>): Promise<SVGSVGElement | undefined>;
|
||||||
groupShapes(ids: TLShapeId[], groupId?: TLShapeId): this;
|
|
||||||
// (undocumented)
|
|
||||||
groupShapes(shapes: TLShape[], groupId?: TLShapeId): this;
|
groupShapes(shapes: TLShape[], groupId?: TLShapeId): this;
|
||||||
|
// (undocumented)
|
||||||
|
groupShapes(ids: TLShapeId[], groupId?: TLShapeId): this;
|
||||||
hasAncestor(shape: TLShape | undefined, ancestorId: TLShapeId): boolean;
|
hasAncestor(shape: TLShape | undefined, ancestorId: TLShapeId): boolean;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
hasAncestor(shapeId: TLShapeId | undefined, ancestorId: TLShapeId): boolean;
|
hasAncestor(shapeId: TLShapeId | undefined, ancestorId: TLShapeId): boolean;
|
||||||
|
@ -828,13 +823,13 @@ export class Editor extends EventEmitter<TLEventMap> {
|
||||||
isShapeOrAncestorLocked(shape?: TLShape): boolean;
|
isShapeOrAncestorLocked(shape?: TLShape): boolean;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
isShapeOrAncestorLocked(id?: TLShapeId): boolean;
|
isShapeOrAncestorLocked(id?: TLShapeId): boolean;
|
||||||
mark(markId?: string, onUndo?: boolean, onRedo?: boolean): string;
|
mark(markId?: string, onUndo?: boolean, onRedo?: boolean): this;
|
||||||
moveShapesToPage(shapes: TLShape[], pageId: TLPageId): this;
|
moveShapesToPage(shapes: TLShape[], pageId: TLPageId): this;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
moveShapesToPage(ids: TLShapeId[], pageId: TLPageId): this;
|
moveShapesToPage(ids: TLShapeId[], pageId: TLPageId): this;
|
||||||
nudgeShapes(shapes: TLShape[], offset: VecLike, ephemeral?: boolean): this;
|
nudgeShapes(shapes: TLShape[], offset: VecLike, historyOptions?: CommandHistoryOptions): this;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
nudgeShapes(ids: TLShapeId[], offset: VecLike, ephemeral?: boolean): this;
|
nudgeShapes(ids: TLShapeId[], offset: VecLike, historyOptions?: CommandHistoryOptions): this;
|
||||||
get onlySelectedShape(): null | TLShape;
|
get onlySelectedShape(): null | TLShape;
|
||||||
get openMenus(): string[];
|
get openMenus(): string[];
|
||||||
packShapes(shapes: TLShape[], gap: number): this;
|
packShapes(shapes: TLShape[], gap: number): this;
|
||||||
|
@ -850,7 +845,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
||||||
pan(offset: VecLike, animation?: TLAnimationOptions): this;
|
pan(offset: VecLike, animation?: TLAnimationOptions): this;
|
||||||
panZoomIntoView(ids: TLShapeId[], animation?: TLAnimationOptions): this;
|
panZoomIntoView(ids: TLShapeId[], animation?: TLAnimationOptions): this;
|
||||||
popFocusLayer(): this;
|
popFocusLayer(): this;
|
||||||
putContent(content: TLContent, options?: {
|
putContentOntoCurrentPage(content: TLContent, options?: {
|
||||||
point?: VecLike;
|
point?: VecLike;
|
||||||
select?: boolean;
|
select?: boolean;
|
||||||
preservePosition?: boolean;
|
preservePosition?: boolean;
|
||||||
|
@ -864,9 +859,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
||||||
registerExternalContentHandler<T extends TLExternalContent_2['type']>(type: T, handler: ((info: T extends TLExternalContent_2['type'] ? TLExternalContent_2 & {
|
registerExternalContentHandler<T extends TLExternalContent_2['type']>(type: T, handler: ((info: T extends TLExternalContent_2['type'] ? TLExternalContent_2 & {
|
||||||
type: T;
|
type: T;
|
||||||
} : TLExternalContent_2) => void) | null): this;
|
} : TLExternalContent_2) => void) | null): this;
|
||||||
renamePage(page: TLPage, name: string, squashing?: boolean): this;
|
renamePage(page: TLPage, name: string, historyOptions?: CommandHistoryOptions): this;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
renamePage(id: TLPageId, name: string, squashing?: boolean): this;
|
renamePage(id: TLPageId, name: string, historyOptions?: CommandHistoryOptions): this;
|
||||||
get renderingBounds(): Box2d;
|
get renderingBounds(): Box2d;
|
||||||
get renderingBoundsExpanded(): Box2d;
|
get renderingBoundsExpanded(): Box2d;
|
||||||
renderingBoundsMargin: number;
|
renderingBoundsMargin: number;
|
||||||
|
@ -885,15 +880,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
reparentShapes(ids: TLShapeId[], parentId: TLParentId, insertIndex?: string): this;
|
reparentShapes(ids: TLShapeId[], parentId: TLParentId, insertIndex?: string): this;
|
||||||
resetZoom(point?: Vec2d, animation?: TLAnimationOptions): this;
|
resetZoom(point?: Vec2d, animation?: TLAnimationOptions): this;
|
||||||
resizeShape(id: TLShapeId, scale: VecLike, options?: {
|
resizeShape(shape: TLShape, scale: VecLike, options?: TLResizeShapeOptions): this;
|
||||||
initialBounds?: Box2d;
|
// (undocumented)
|
||||||
scaleOrigin?: VecLike;
|
resizeShape(id: TLShapeId, scale: VecLike, options?: TLResizeShapeOptions): this;
|
||||||
scaleAxisRotation?: number;
|
|
||||||
initialShape?: TLShape;
|
|
||||||
initialPageTransform?: MatLike;
|
|
||||||
dragHandle?: TLResizeHandle;
|
|
||||||
mode?: TLResizeMode;
|
|
||||||
}): this;
|
|
||||||
readonly root: RootState;
|
readonly root: RootState;
|
||||||
rotateShapesBy(shapes: TLShape[], delta: number): this;
|
rotateShapesBy(shapes: TLShape[], delta: number): this;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
|
@ -921,18 +910,19 @@ export class Editor extends EventEmitter<TLEventMap> {
|
||||||
sendToBack(ids: TLShapeId[]): this;
|
sendToBack(ids: TLShapeId[]): this;
|
||||||
setCamera(point: VecLike, animation?: TLAnimationOptions): this;
|
setCamera(point: VecLike, animation?: TLAnimationOptions): this;
|
||||||
setCroppingShapeId(id: null | TLShapeId): this;
|
setCroppingShapeId(id: null | TLShapeId): this;
|
||||||
setCurrentPage(page: TLPage, opts?: TLViewportOptions): this;
|
setCurrentPage(page: TLPage, historyOptions?: CommandHistoryOptions): this;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
setCurrentPage(pageId: TLPageId, opts?: TLViewportOptions): this;
|
setCurrentPage(pageId: TLPageId, historyOptions?: CommandHistoryOptions): this;
|
||||||
setCurrentTool(id: string, info?: {}): this;
|
setCurrentTool(id: string, info?: {}): this;
|
||||||
|
setCursor: (cursor: Partial<TLCursor>) => this;
|
||||||
setEditingShapeId(id: null | TLShapeId): this;
|
setEditingShapeId(id: null | TLShapeId): this;
|
||||||
setErasingShapeIds(ids: TLShapeId[]): this;
|
setErasingShapeIds(ids: TLShapeId[]): this;
|
||||||
setFocusedGroupId(next: null | TLShapeId): this;
|
setFocusedGroupId(next: null | TLShapeId): this;
|
||||||
setHintingIds(ids: TLShapeId[]): this;
|
setHintingIds(ids: TLShapeId[]): this;
|
||||||
setHoveredShapeId(id: null | TLShapeId): this;
|
setHoveredShapeId(id: null | TLShapeId): this;
|
||||||
setOpacity(opacity: number, ephemeral?: boolean, squashing?: boolean): this;
|
setOpacity(opacity: number, historyOptions?: CommandHistoryOptions): this;
|
||||||
setSelectedShapeIds(ids: TLShapeId[], squashing?: boolean): this;
|
setSelectedShapeIds(ids: TLShapeId[], historyOptions?: CommandHistoryOptions): this;
|
||||||
setStyle<T>(style: StyleProp<T>, value: T, ephemeral?: boolean, squashing?: boolean): this;
|
setStyle<T>(style: StyleProp<T>, value: T, historyOptions?: CommandHistoryOptions): this;
|
||||||
shapeUtils: {
|
shapeUtils: {
|
||||||
readonly [K in string]?: ShapeUtil<TLUnknownShape>;
|
readonly [K in string]?: ShapeUtil<TLUnknownShape>;
|
||||||
};
|
};
|
||||||
|
@ -964,19 +954,19 @@ export class Editor extends EventEmitter<TLEventMap> {
|
||||||
toggleLock(shapes: TLShape[]): this;
|
toggleLock(shapes: TLShape[]): this;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
toggleLock(ids: TLShapeId[]): this;
|
toggleLock(ids: TLShapeId[]): this;
|
||||||
undo(): HistoryManager<this>;
|
undo(): this;
|
||||||
ungroupShapes(ids: TLShapeId[]): this;
|
ungroupShapes(ids: TLShapeId[]): this;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
ungroupShapes(ids: TLShape[]): this;
|
ungroupShapes(ids: TLShape[]): this;
|
||||||
updateAssets(assets: TLAssetPartial[]): this;
|
updateAssets(assets: TLAssetPartial[]): this;
|
||||||
updateCurrentPageState(partial: Partial<Omit<TLInstancePageState, 'editingShapeId' | 'focusedGroupId' | 'pageId' | 'selectedShapeIds'>>, ephemeral?: boolean): this;
|
updateCurrentPageState(partial: Partial<Omit<TLInstancePageState, 'editingShapeId' | 'focusedGroupId' | 'pageId' | 'selectedShapeIds'>>, historyOptions?: CommandHistoryOptions): this;
|
||||||
updateDocumentSettings(settings: Partial<TLDocument>): this;
|
updateDocumentSettings(settings: Partial<TLDocument>): this;
|
||||||
updateInstanceState(partial: Partial<Omit<TLInstance, 'currentPageId'>>, ephemeral?: boolean, squashing?: boolean): this;
|
updateInstanceState(partial: Partial<Omit<TLInstance, 'currentPageId'>>, historyOptions?: CommandHistoryOptions): this;
|
||||||
updatePage(partial: RequiredKeys<TLPage, 'id'>, squashing?: boolean): this;
|
updatePage(partial: RequiredKeys<TLPage, 'id'>, historyOptions?: CommandHistoryOptions): this;
|
||||||
// @internal
|
// @internal
|
||||||
updateRenderingBounds(): this;
|
updateRenderingBounds(): this;
|
||||||
updateShape<T extends TLUnknownShape>(partial: null | TLShapePartial<T> | undefined, squashing?: boolean): this;
|
updateShape<T extends TLUnknownShape>(partial: null | TLShapePartial<T> | undefined, historyOptions?: CommandHistoryOptions): this;
|
||||||
updateShapes<T extends TLUnknownShape>(partials: (null | TLShapePartial<T> | undefined)[], squashing?: boolean): this;
|
updateShapes<T extends TLUnknownShape>(partials: (null | TLShapePartial<T> | undefined)[], historyOptions?: CommandHistoryOptions): this;
|
||||||
updateViewportScreenBounds(center?: boolean): this;
|
updateViewportScreenBounds(center?: boolean): this;
|
||||||
readonly user: UserPreferencesManager;
|
readonly user: UserPreferencesManager;
|
||||||
get viewportPageBounds(): Box2d;
|
get viewportPageBounds(): Box2d;
|
||||||
|
@ -1128,7 +1118,7 @@ export abstract class Geometry2d {
|
||||||
export function getArcLength(C: VecLike, r: number, A: VecLike, B: VecLike): number;
|
export function getArcLength(C: VecLike, r: number, A: VecLike, B: VecLike): number;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export function getArrowheadPathForType(info: ArrowInfo, side: 'end' | 'start', strokeWidth: number): string | undefined;
|
export function getArrowheadPathForType(info: TLArrowInfo, side: 'end' | 'start', strokeWidth: number): string | undefined;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export function getArrowTerminalsInArrowSpace(editor: Editor, shape: TLArrowShape): {
|
export function getArrowTerminalsInArrowSpace(editor: Editor, shape: TLArrowShape): {
|
||||||
|
@ -1140,7 +1130,7 @@ export function getArrowTerminalsInArrowSpace(editor: Editor, shape: TLArrowShap
|
||||||
export function getCursor(cursor: TLCursorType, rotation?: number, color?: string): string;
|
export function getCursor(cursor: TLCursorType, rotation?: number, color?: string): string;
|
||||||
|
|
||||||
// @public
|
// @public
|
||||||
export function getCurvedArrowHandlePath(info: ArrowInfo & {
|
export function getCurvedArrowHandlePath(info: TLArrowInfo & {
|
||||||
isStraight: false;
|
isStraight: false;
|
||||||
}): string;
|
}): string;
|
||||||
|
|
||||||
|
@ -1198,12 +1188,12 @@ export function getRotationSnapshot({ editor }: {
|
||||||
}): null | TLRotationSnapshot;
|
}): null | TLRotationSnapshot;
|
||||||
|
|
||||||
// @public
|
// @public
|
||||||
export function getSolidCurvedArrowPath(info: ArrowInfo & {
|
export function getSolidCurvedArrowPath(info: TLArrowInfo & {
|
||||||
isStraight: false;
|
isStraight: false;
|
||||||
}): string;
|
}): string;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export function getSolidStraightArrowPath(info: ArrowInfo & {
|
export function getSolidStraightArrowPath(info: TLArrowInfo & {
|
||||||
isStraight: true;
|
isStraight: true;
|
||||||
}): string;
|
}): string;
|
||||||
|
|
||||||
|
@ -1211,7 +1201,7 @@ export function getSolidStraightArrowPath(info: ArrowInfo & {
|
||||||
export const getStarBounds: (sides: number, w: number, h: number) => Box2d;
|
export const getStarBounds: (sides: number, w: number, h: number) => Box2d;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export function getStraightArrowHandlePath(info: ArrowInfo & {
|
export function getStraightArrowHandlePath(info: TLArrowInfo & {
|
||||||
isStraight: true;
|
isStraight: true;
|
||||||
}): string;
|
}): string;
|
||||||
|
|
||||||
|
@ -1988,7 +1978,7 @@ export const TAU: number;
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export type TLAnimationOptions = Partial<{
|
export type TLAnimationOptions = Partial<{
|
||||||
duration: number;
|
duration: number;
|
||||||
easing: typeof EASINGS.easeInOutCubic;
|
easing: (t: number) => number;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
|
@ -2144,7 +2134,6 @@ export type TLEditorComponents = {
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export interface TLEditorOptions {
|
export interface TLEditorOptions {
|
||||||
getContainer: () => HTMLElement;
|
getContainer: () => HTMLElement;
|
||||||
// (undocumented)
|
|
||||||
initialState?: string;
|
initialState?: string;
|
||||||
shapeUtils: readonly TLShapeUtilConstructor<TLUnknownShape>[];
|
shapeUtils: readonly TLShapeUtilConstructor<TLUnknownShape>[];
|
||||||
store: TLStore;
|
store: TLStore;
|
||||||
|
@ -2473,6 +2462,17 @@ export type TLResizeInfo<T extends TLShape> = {
|
||||||
// @public
|
// @public
|
||||||
export type TLResizeMode = 'resize_bounds' | 'scale_shape';
|
export type TLResizeMode = 'resize_bounds' | 'scale_shape';
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
export type TLResizeShapeOptions = Partial<{
|
||||||
|
initialBounds: Box2d;
|
||||||
|
scaleOrigin: VecLike;
|
||||||
|
scaleAxisRotation: number;
|
||||||
|
initialShape: TLShape;
|
||||||
|
initialPageTransform: MatLike;
|
||||||
|
dragHandle: TLResizeHandle;
|
||||||
|
mode: TLResizeMode;
|
||||||
|
}>;
|
||||||
|
|
||||||
// @public
|
// @public
|
||||||
export type TLRotationSnapshot = {
|
export type TLRotationSnapshot = {
|
||||||
selectionPageCenter: Vec2d;
|
selectionPageCenter: Vec2d;
|
||||||
|
|
|
@ -136,7 +136,12 @@ export {
|
||||||
SVG_PADDING,
|
SVG_PADDING,
|
||||||
ZOOMS,
|
ZOOMS,
|
||||||
} from './lib/constants'
|
} from './lib/constants'
|
||||||
export { Editor, type TLAnimationOptions, type TLEditorOptions } from './lib/editor/Editor'
|
export {
|
||||||
|
Editor,
|
||||||
|
type TLAnimationOptions,
|
||||||
|
type TLEditorOptions,
|
||||||
|
type TLResizeShapeOptions,
|
||||||
|
} from './lib/editor/Editor'
|
||||||
export {
|
export {
|
||||||
SnapManager,
|
SnapManager,
|
||||||
type GapsSnapLine,
|
type GapsSnapLine,
|
||||||
|
|
|
@ -274,7 +274,7 @@ function TldrawEditorWithReadyStore({
|
||||||
|
|
||||||
React.useLayoutEffect(() => {
|
React.useLayoutEffect(() => {
|
||||||
if (editor && autoFocus) {
|
if (editor && autoFocus) {
|
||||||
editor.focus()
|
editor.getContainer().focus()
|
||||||
}
|
}
|
||||||
}, [editor, autoFocus])
|
}, [editor, autoFocus])
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,4 +1,4 @@
|
||||||
import { HistoryManager } from './HistoryManager'
|
import { CommandHistoryOptions, HistoryManager } from './HistoryManager'
|
||||||
import { stack } from './Stack'
|
import { stack } from './Stack'
|
||||||
|
|
||||||
function createCounterHistoryManager() {
|
function createCounterHistoryManager() {
|
||||||
|
@ -286,3 +286,153 @@ describe(HistoryManager, () => {
|
||||||
expect(editor.getCount()).toBe(2)
|
expect(editor.getCount()).toBe(2)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('history options', () => {
|
||||||
|
let manager: HistoryManager<any>
|
||||||
|
let state: { a: number; b: number }
|
||||||
|
|
||||||
|
let setA: (n: number, historyOptions?: CommandHistoryOptions) => any
|
||||||
|
let setB: (n: number, historyOptions?: CommandHistoryOptions) => any
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
manager = new HistoryManager({ emit: () => void null }, () => {
|
||||||
|
return
|
||||||
|
})
|
||||||
|
|
||||||
|
state = {
|
||||||
|
a: 0,
|
||||||
|
b: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
setA = manager.createCommand(
|
||||||
|
'setA',
|
||||||
|
(n: number, historyOptions?: CommandHistoryOptions) => ({
|
||||||
|
data: { next: n, prev: state.a },
|
||||||
|
...historyOptions,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
do: ({ next }) => {
|
||||||
|
state = { ...state, a: next }
|
||||||
|
},
|
||||||
|
undo: ({ prev }) => {
|
||||||
|
state = { ...state, a: prev }
|
||||||
|
},
|
||||||
|
squash: ({ prev }, { next }) => ({ prev, next }),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
setB = manager.createCommand(
|
||||||
|
'setB',
|
||||||
|
(n: number, historyOptions?: CommandHistoryOptions) => ({
|
||||||
|
data: { next: n, prev: state.b },
|
||||||
|
...historyOptions,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
do: ({ next }) => {
|
||||||
|
state = { ...state, b: next }
|
||||||
|
},
|
||||||
|
undo: ({ prev }) => {
|
||||||
|
state = { ...state, b: prev }
|
||||||
|
},
|
||||||
|
squash: ({ prev }, { next }) => ({ prev, next }),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets, undoes, redoes', () => {
|
||||||
|
manager.mark()
|
||||||
|
setA(1)
|
||||||
|
manager.mark()
|
||||||
|
setB(1)
|
||||||
|
manager.mark()
|
||||||
|
setB(2)
|
||||||
|
|
||||||
|
expect(state).toMatchObject({ a: 1, b: 2 })
|
||||||
|
|
||||||
|
manager.undo()
|
||||||
|
|
||||||
|
expect(state).toMatchObject({ a: 1, b: 1 })
|
||||||
|
|
||||||
|
manager.redo()
|
||||||
|
|
||||||
|
expect(state).toMatchObject({ a: 1, b: 2 })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets, undoes, redoes', () => {
|
||||||
|
manager.mark()
|
||||||
|
setA(1)
|
||||||
|
manager.mark()
|
||||||
|
setB(1)
|
||||||
|
manager.mark()
|
||||||
|
setB(2)
|
||||||
|
setB(3)
|
||||||
|
setB(4)
|
||||||
|
|
||||||
|
expect(state).toMatchObject({ a: 1, b: 4 })
|
||||||
|
|
||||||
|
manager.undo()
|
||||||
|
|
||||||
|
expect(state).toMatchObject({ a: 1, b: 1 })
|
||||||
|
|
||||||
|
manager.redo()
|
||||||
|
|
||||||
|
expect(state).toMatchObject({ a: 1, b: 4 })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets ephemeral, undoes, redos', () => {
|
||||||
|
manager.mark()
|
||||||
|
setA(1)
|
||||||
|
manager.mark()
|
||||||
|
setB(1) // B 0->1
|
||||||
|
manager.mark()
|
||||||
|
setB(2, { ephemeral: true }) // B 0->2, but ephemeral
|
||||||
|
|
||||||
|
expect(state).toMatchObject({ a: 1, b: 2 })
|
||||||
|
|
||||||
|
manager.undo() // undoes B 2->0
|
||||||
|
|
||||||
|
expect(state).toMatchObject({ a: 1, b: 0 })
|
||||||
|
|
||||||
|
manager.redo() // redoes B 0->1, but not B 1-> 2
|
||||||
|
|
||||||
|
expect(state).toMatchObject({ a: 1, b: 1 }) // no change, b 1->2 was ephemeral
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets squashing, undoes, redos', () => {
|
||||||
|
manager.mark()
|
||||||
|
setA(1)
|
||||||
|
manager.mark()
|
||||||
|
setB(1)
|
||||||
|
setB(2, { squashing: true }) // squashes with the previous command
|
||||||
|
setB(3, { squashing: true }) // squashes with the previous command
|
||||||
|
|
||||||
|
expect(state).toMatchObject({ a: 1, b: 3 })
|
||||||
|
|
||||||
|
manager.undo()
|
||||||
|
|
||||||
|
expect(state).toMatchObject({ a: 1, b: 0 })
|
||||||
|
|
||||||
|
manager.redo()
|
||||||
|
|
||||||
|
expect(state).toMatchObject({ a: 1, b: 3 })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets squashing and ephemeral, undoes, redos', () => {
|
||||||
|
manager.mark()
|
||||||
|
setA(1)
|
||||||
|
manager.mark()
|
||||||
|
setB(1)
|
||||||
|
setB(2, { squashing: true }) // squashes with the previous command
|
||||||
|
setB(3, { squashing: true, ephemeral: true }) // squashes with the previous command
|
||||||
|
|
||||||
|
expect(state).toMatchObject({ a: 1, b: 3 })
|
||||||
|
|
||||||
|
manager.undo()
|
||||||
|
|
||||||
|
expect(state).toMatchObject({ a: 1, b: 0 })
|
||||||
|
|
||||||
|
manager.redo()
|
||||||
|
|
||||||
|
expect(state).toMatchObject({ a: 1, b: 2 }) // B2->3 was ephemeral
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
|
@ -4,13 +4,26 @@ import { uniqueId } from '../../utils/uniqueId'
|
||||||
import { TLCommandHandler, TLHistoryEntry } from '../types/history-types'
|
import { TLCommandHandler, TLHistoryEntry } from '../types/history-types'
|
||||||
import { Stack, stack } from './Stack'
|
import { Stack, stack } from './Stack'
|
||||||
|
|
||||||
|
/** @public */
|
||||||
|
export type CommandHistoryOptions = Partial<{
|
||||||
|
/**
|
||||||
|
* When true, this command will be squashed with the previous command in the undo / redo stack.
|
||||||
|
*/
|
||||||
|
squashing: boolean
|
||||||
|
/**
|
||||||
|
* When true, this command will not add anything to the undo / redo stack. Its change will never be undone or redone.
|
||||||
|
*/
|
||||||
|
ephemeral: boolean
|
||||||
|
/**
|
||||||
|
* When true, adding this this command will not clear out the redo stack.
|
||||||
|
*/
|
||||||
|
preservesRedoStack: boolean
|
||||||
|
}>
|
||||||
|
|
||||||
type CommandFn<Data> = (...args: any[]) =>
|
type CommandFn<Data> = (...args: any[]) =>
|
||||||
| {
|
| ({
|
||||||
data: Data
|
data: Data
|
||||||
squashing?: boolean
|
} & CommandHistoryOptions)
|
||||||
ephemeral?: boolean
|
|
||||||
preservesRedoStack?: boolean
|
|
||||||
}
|
|
||||||
| null
|
| null
|
||||||
| undefined
|
| undefined
|
||||||
| void
|
| void
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
import { TLArrowShapeArrowheadStyle } from '@tldraw/tlschema'
|
import { TLArrowShapeArrowheadStyle } from '@tldraw/tlschema'
|
||||||
import { VecLike } from '../../../../primitives/Vec2d'
|
import { VecLike } from '../../../../primitives/Vec2d'
|
||||||
|
|
||||||
export type ArrowPoint = {
|
/** @public */
|
||||||
|
export type TLArrowPoint = {
|
||||||
handle: VecLike
|
handle: VecLike
|
||||||
point: VecLike
|
point: VecLike
|
||||||
arrowhead: TLArrowShapeArrowheadStyle
|
arrowhead: TLArrowShapeArrowheadStyle
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ArcInfo {
|
/** @public */
|
||||||
|
export interface TLArcInfo {
|
||||||
center: VecLike
|
center: VecLike
|
||||||
radius: number
|
radius: number
|
||||||
size: number
|
size: number
|
||||||
|
@ -16,20 +18,21 @@ export interface ArcInfo {
|
||||||
sweepFlag: number
|
sweepFlag: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ArrowInfo =
|
/** @public */
|
||||||
|
export type TLArrowInfo =
|
||||||
| {
|
| {
|
||||||
isStraight: false
|
isStraight: false
|
||||||
start: ArrowPoint
|
start: TLArrowPoint
|
||||||
end: ArrowPoint
|
end: TLArrowPoint
|
||||||
middle: VecLike
|
middle: VecLike
|
||||||
handleArc: ArcInfo
|
handleArc: TLArcInfo
|
||||||
bodyArc: ArcInfo
|
bodyArc: TLArcInfo
|
||||||
isValid: boolean
|
isValid: boolean
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
isStraight: true
|
isStraight: true
|
||||||
start: ArrowPoint
|
start: TLArrowPoint
|
||||||
end: ArrowPoint
|
end: TLArrowPoint
|
||||||
middle: VecLike
|
middle: VecLike
|
||||||
isValid: boolean
|
isValid: boolean
|
||||||
length: number
|
length: number
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Vec2d, VecLike } from '../../../../primitives/Vec2d'
|
import { Vec2d, VecLike } from '../../../../primitives/Vec2d'
|
||||||
import { intersectCircleCircle } from '../../../../primitives/intersect'
|
import { intersectCircleCircle } from '../../../../primitives/intersect'
|
||||||
import { PI, TAU } from '../../../../primitives/utils'
|
import { PI, TAU } from '../../../../primitives/utils'
|
||||||
import { ArrowInfo } from './arrow-types'
|
import { TLArrowInfo } from './arrow-types'
|
||||||
|
|
||||||
type TLArrowPointsInfo = {
|
type TLArrowPointsInfo = {
|
||||||
point: VecLike
|
point: VecLike
|
||||||
|
@ -9,7 +9,7 @@ type TLArrowPointsInfo = {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getArrowPoints(
|
function getArrowPoints(
|
||||||
info: ArrowInfo,
|
info: TLArrowInfo,
|
||||||
side: 'start' | 'end',
|
side: 'start' | 'end',
|
||||||
strokeWidth: number
|
strokeWidth: number
|
||||||
): TLArrowPointsInfo {
|
): TLArrowPointsInfo {
|
||||||
|
@ -110,7 +110,7 @@ export function getPipeHead() {
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export function getArrowheadPathForType(
|
export function getArrowheadPathForType(
|
||||||
info: ArrowInfo,
|
info: TLArrowInfo,
|
||||||
side: 'start' | 'end',
|
side: 'start' | 'end',
|
||||||
strokeWidth: number
|
strokeWidth: number
|
||||||
): string | undefined {
|
): string | undefined {
|
||||||
|
|
|
@ -13,7 +13,7 @@ import {
|
||||||
shortAngleDist,
|
shortAngleDist,
|
||||||
} from '../../../../primitives/utils'
|
} from '../../../../primitives/utils'
|
||||||
import type { Editor } from '../../../Editor'
|
import type { Editor } from '../../../Editor'
|
||||||
import { ArcInfo, ArrowInfo } from './arrow-types'
|
import { TLArcInfo, TLArrowInfo } from './arrow-types'
|
||||||
import {
|
import {
|
||||||
BOUND_ARROW_OFFSET,
|
BOUND_ARROW_OFFSET,
|
||||||
MIN_ARROW_LENGTH,
|
MIN_ARROW_LENGTH,
|
||||||
|
@ -24,7 +24,11 @@ import {
|
||||||
} from './shared'
|
} from './shared'
|
||||||
import { getStraightArrowInfo } from './straight-arrow'
|
import { getStraightArrowInfo } from './straight-arrow'
|
||||||
|
|
||||||
export function getCurvedArrowInfo(editor: Editor, shape: TLArrowShape, extraBend = 0): ArrowInfo {
|
export function getCurvedArrowInfo(
|
||||||
|
editor: Editor,
|
||||||
|
shape: TLArrowShape,
|
||||||
|
extraBend = 0
|
||||||
|
): TLArrowInfo {
|
||||||
const { arrowheadEnd, arrowheadStart } = shape.props
|
const { arrowheadEnd, arrowheadStart } = shape.props
|
||||||
const bend = shape.props.bend + extraBend
|
const bend = shape.props.bend + extraBend
|
||||||
|
|
||||||
|
@ -291,7 +295,7 @@ export function getCurvedArrowInfo(editor: Editor, shape: TLArrowShape, extraBen
|
||||||
* @param info - The arrow info.
|
* @param info - The arrow info.
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export function getCurvedArrowHandlePath(info: ArrowInfo & { isStraight: false }) {
|
export function getCurvedArrowHandlePath(info: TLArrowInfo & { isStraight: false }) {
|
||||||
const {
|
const {
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
|
@ -306,7 +310,7 @@ export function getCurvedArrowHandlePath(info: ArrowInfo & { isStraight: false }
|
||||||
* @param info - The arrow info.
|
* @param info - The arrow info.
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export function getSolidCurvedArrowPath(info: ArrowInfo & { isStraight: false }) {
|
export function getSolidCurvedArrowPath(info: TLArrowInfo & { isStraight: false }) {
|
||||||
const {
|
const {
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
|
@ -373,7 +377,7 @@ export function getArcBoundingBox(center: VecLike, radius: number, start: VecLik
|
||||||
* @param b - The end of the arc
|
* @param b - The end of the arc
|
||||||
* @param c - A point on the arc
|
* @param c - A point on the arc
|
||||||
*/
|
*/
|
||||||
export function getArcInfo(a: VecLike, b: VecLike, c: VecLike): ArcInfo {
|
export function getArcInfo(a: VecLike, b: VecLike, c: VecLike): TLArcInfo {
|
||||||
// find a circle from the three points
|
// find a circle from the three points
|
||||||
const u = -2 * (a.x * (b.y - c.y) - a.y * (b.x - c.x) + b.x * c.y - c.x * b.y)
|
const u = -2 * (a.x * (b.y - c.y) - a.y * (b.x - c.x) + b.x * c.y - c.x * b.y)
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {
|
||||||
intersectLineSegmentPolyline,
|
intersectLineSegmentPolyline,
|
||||||
} from '../../../../primitives/intersect'
|
} from '../../../../primitives/intersect'
|
||||||
import { Editor } from '../../../Editor'
|
import { Editor } from '../../../Editor'
|
||||||
import { ArrowInfo } from './arrow-types'
|
import { TLArrowInfo } from './arrow-types'
|
||||||
import {
|
import {
|
||||||
BOUND_ARROW_OFFSET,
|
BOUND_ARROW_OFFSET,
|
||||||
BoundShapeInfo,
|
BoundShapeInfo,
|
||||||
|
@ -17,7 +17,7 @@ import {
|
||||||
getBoundShapeInfoForTerminal,
|
getBoundShapeInfoForTerminal,
|
||||||
} from './shared'
|
} from './shared'
|
||||||
|
|
||||||
export function getStraightArrowInfo(editor: Editor, shape: TLArrowShape): ArrowInfo {
|
export function getStraightArrowInfo(editor: Editor, shape: TLArrowShape): TLArrowInfo {
|
||||||
const { start, end, arrowheadStart, arrowheadEnd } = shape.props
|
const { start, end, arrowheadStart, arrowheadEnd } = shape.props
|
||||||
|
|
||||||
const terminalsInArrowSpace = getArrowTerminalsInArrowSpace(editor, shape)
|
const terminalsInArrowSpace = getArrowTerminalsInArrowSpace(editor, shape)
|
||||||
|
@ -204,12 +204,12 @@ function updateArrowheadPointWithBoundShape(
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export function getStraightArrowHandlePath(info: ArrowInfo & { isStraight: true }) {
|
export function getStraightArrowHandlePath(info: TLArrowInfo & { isStraight: true }) {
|
||||||
return getArrowPath(info.start.handle, info.end.handle)
|
return getArrowPath(info.start.handle, info.end.handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export function getSolidStraightArrowPath(info: ArrowInfo & { isStraight: true }) {
|
export function getSolidStraightArrowPath(info: TLArrowInfo & { isStraight: true }) {
|
||||||
return getArrowPath(info.start.point, info.end.point)
|
return getArrowPath(info.start.point, info.end.point)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ export class Idle extends StateNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
override onEnter = () => {
|
override onEnter = () => {
|
||||||
this.editor.updateInstanceState({ cursor: { type: 'cross', rotation: 0 } }, true)
|
this.editor.setCursor({ type: 'cross', rotation: 0 })
|
||||||
}
|
}
|
||||||
|
|
||||||
override onCancel = () => {
|
override onCancel = () => {
|
||||||
|
|
|
@ -8,7 +8,7 @@ export class Idle extends StateNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
override onEnter = () => {
|
override onEnter = () => {
|
||||||
this.editor.updateInstanceState({ cursor: { type: 'cross', rotation: 0 } }, true)
|
this.editor.setCursor({ type: 'cross', rotation: 0 })
|
||||||
}
|
}
|
||||||
|
|
||||||
override onCancel = () => {
|
override onCancel = () => {
|
||||||
|
|
|
@ -80,7 +80,8 @@ export class Pointing extends StateNode {
|
||||||
|
|
||||||
const id = createShapeId()
|
const id = createShapeId()
|
||||||
|
|
||||||
this.markId = this.editor.mark(`creating:${id}`)
|
this.markId = `creating:${id}`
|
||||||
|
this.editor.mark(this.markId)
|
||||||
|
|
||||||
this.editor.createShapes<TLArrowShape>([
|
this.editor.createShapes<TLArrowShape>([
|
||||||
{
|
{
|
||||||
|
@ -109,7 +110,7 @@ export class Pointing extends StateNode {
|
||||||
if (startTerminal?.type === 'binding') {
|
if (startTerminal?.type === 'binding') {
|
||||||
this.editor.setHintingIds([startTerminal.boundShapeId])
|
this.editor.setHintingIds([startTerminal.boundShapeId])
|
||||||
}
|
}
|
||||||
this.editor.updateShapes([change], true)
|
this.editor.updateShapes([change], { squashing: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache the current shape after those changes
|
// Cache the current shape after those changes
|
||||||
|
@ -139,7 +140,7 @@ export class Pointing extends StateNode {
|
||||||
if (endTerminal?.type === 'binding') {
|
if (endTerminal?.type === 'binding') {
|
||||||
this.editor.setHintingIds([endTerminal.boundShapeId])
|
this.editor.setHintingIds([endTerminal.boundShapeId])
|
||||||
}
|
}
|
||||||
this.editor.updateShapes([change], true)
|
this.editor.updateShapes([change], { squashing: true })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,7 +154,7 @@ export class Pointing extends StateNode {
|
||||||
})
|
})
|
||||||
|
|
||||||
if (change) {
|
if (change) {
|
||||||
this.editor.updateShapes([change], true)
|
this.editor.updateShapes([change], { squashing: true })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -364,7 +364,9 @@ export class Drawing extends StateNode {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.editor.updateShapes<TLDrawShape | TLHighlightShape>([shapePartial], true)
|
this.editor.updateShapes<TLDrawShape | TLHighlightShape>([shapePartial], {
|
||||||
|
squashing: true,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -424,7 +426,7 @@ export class Drawing extends StateNode {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.editor.updateShapes([shapePartial], true)
|
this.editor.updateShapes([shapePartial], { squashing: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
|
@ -566,7 +568,7 @@ export class Drawing extends StateNode {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.editor.updateShapes([shapePartial], true)
|
this.editor.updateShapes([shapePartial], { squashing: true })
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -611,7 +613,7 @@ export class Drawing extends StateNode {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.editor.updateShapes([shapePartial], true)
|
this.editor.updateShapes([shapePartial], { squashing: true })
|
||||||
|
|
||||||
// Set a maximum length for the lines array; after 200 points, complete the line.
|
// Set a maximum length for the lines array; after 200 points, complete the line.
|
||||||
if (newPoints.length > 500) {
|
if (newPoints.length > 500) {
|
||||||
|
|
|
@ -8,7 +8,7 @@ export class Idle extends StateNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
override onEnter = () => {
|
override onEnter = () => {
|
||||||
this.editor.updateInstanceState({ cursor: { type: 'cross', rotation: 0 } }, true)
|
this.editor.setCursor({ type: 'cross', rotation: 0 })
|
||||||
}
|
}
|
||||||
|
|
||||||
override onCancel = () => {
|
override onCancel = () => {
|
||||||
|
|
|
@ -38,7 +38,7 @@ export const FrameLabelInput = forwardRef<
|
||||||
props: { name: value },
|
props: { name: value },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
true
|
{ squashing: true }
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
[id, editor]
|
[id, editor]
|
||||||
|
@ -61,7 +61,7 @@ export const FrameLabelInput = forwardRef<
|
||||||
props: { name: value },
|
props: { name: value },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
true
|
{ squashing: true }
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
[id, editor]
|
[id, editor]
|
||||||
|
|
|
@ -8,7 +8,7 @@ export class Idle extends StateNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
override onEnter = () => {
|
override onEnter = () => {
|
||||||
this.editor.updateInstanceState({ cursor: { type: 'cross', rotation: 0 } }, true)
|
this.editor.setCursor({ type: 'cross', rotation: 0 })
|
||||||
}
|
}
|
||||||
|
|
||||||
override onKeyUp: TLEventHandlers['onKeyUp'] = (info) => {
|
override onKeyUp: TLEventHandlers['onKeyUp'] = (info) => {
|
||||||
|
|
|
@ -157,7 +157,7 @@ describe('Misc', () => {
|
||||||
y: 150,
|
y: 150,
|
||||||
})
|
})
|
||||||
|
|
||||||
editor.nudgeShapes(editor.selectedShapeIds, { x: 0, y: 10 }, true)
|
editor.nudgeShapes(editor.selectedShapeIds, { x: 0, y: 10 })
|
||||||
|
|
||||||
editor.expectShapeToMatch({
|
editor.expectShapeToMatch({
|
||||||
id: id,
|
id: id,
|
||||||
|
|
|
@ -7,7 +7,7 @@ export class Idle extends StateNode {
|
||||||
|
|
||||||
override onEnter = (info: { shapeId: TLShapeId }) => {
|
override onEnter = (info: { shapeId: TLShapeId }) => {
|
||||||
this.shapeId = info.shapeId
|
this.shapeId = info.shapeId
|
||||||
this.editor.updateInstanceState({ cursor: { type: 'cross', rotation: 0 } }, true)
|
this.editor.setCursor({ type: 'cross', rotation: 0 })
|
||||||
}
|
}
|
||||||
|
|
||||||
override onPointerDown: TLEventHandlers['onPointerDown'] = () => {
|
override onPointerDown: TLEventHandlers['onPointerDown'] = () => {
|
||||||
|
|
|
@ -30,7 +30,8 @@ export class Pointing extends StateNode {
|
||||||
const shape = info.shapeId && this.editor.getShape<TLLineShape>(info.shapeId)
|
const shape = info.shapeId && this.editor.getShape<TLLineShape>(info.shapeId)
|
||||||
|
|
||||||
if (shape) {
|
if (shape) {
|
||||||
this.markId = this.editor.mark(`creating:${shape.id}`)
|
this.markId = `creating:${shape.id}`
|
||||||
|
this.editor.mark(this.markId)
|
||||||
this.shape = shape
|
this.shape = shape
|
||||||
|
|
||||||
if (inputs.shiftKey) {
|
if (inputs.shiftKey) {
|
||||||
|
@ -85,7 +86,8 @@ export class Pointing extends StateNode {
|
||||||
} else {
|
} else {
|
||||||
const id = createShapeId()
|
const id = createShapeId()
|
||||||
|
|
||||||
this.markId = this.editor.mark(`creating:${id}`)
|
this.markId = `creating:${id}`
|
||||||
|
this.editor.mark(this.markId)
|
||||||
|
|
||||||
this.editor.createShapes<TLLineShape>([
|
this.editor.createShapes<TLLineShape>([
|
||||||
{
|
{
|
||||||
|
|
|
@ -8,7 +8,7 @@ export class Idle extends StateNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
override onEnter = () => {
|
override onEnter = () => {
|
||||||
this.editor.updateInstanceState({ cursor: { type: 'cross', rotation: 0 } }, true)
|
this.editor.setCursor({ type: 'cross', rotation: 0 })
|
||||||
}
|
}
|
||||||
|
|
||||||
override onCancel = () => {
|
override onCancel = () => {
|
||||||
|
|
|
@ -38,7 +38,7 @@ export class Idle extends StateNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
override onEnter = () => {
|
override onEnter = () => {
|
||||||
this.editor.updateInstanceState({ cursor: { type: 'cross', rotation: 0 } }, true)
|
this.editor.setCursor({ type: 'cross', rotation: 0 })
|
||||||
}
|
}
|
||||||
|
|
||||||
override onKeyDown: TLEventHandlers['onKeyDown'] = (info) => {
|
override onKeyDown: TLEventHandlers['onKeyDown'] = (info) => {
|
||||||
|
|
|
@ -19,7 +19,8 @@ export class Pointing extends StateNode {
|
||||||
|
|
||||||
const id = createShapeId()
|
const id = createShapeId()
|
||||||
|
|
||||||
this.markId = this.editor.mark(`creating:${id}`)
|
this.markId = `creating:${id}`
|
||||||
|
this.editor.mark(this.markId)
|
||||||
|
|
||||||
this.editor.createShapes<TLTextShape>([
|
this.editor.createShapes<TLTextShape>([
|
||||||
{
|
{
|
||||||
|
|
|
@ -10,6 +10,6 @@ export class EraserTool extends StateNode {
|
||||||
static override children = () => [Idle, Pointing, Erasing]
|
static override children = () => [Idle, Pointing, Erasing]
|
||||||
|
|
||||||
override onEnter = () => {
|
override onEnter = () => {
|
||||||
this.editor.updateInstanceState({ cursor: { type: 'cross', rotation: 0 } }, true)
|
this.editor.setCursor({ type: 'cross', rotation: 0 })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,8 @@ export class Erasing extends StateNode {
|
||||||
private excludedShapeIds = new Set<TLShapeId>()
|
private excludedShapeIds = new Set<TLShapeId>()
|
||||||
|
|
||||||
override onEnter = (info: TLPointerEventInfo) => {
|
override onEnter = (info: TLPointerEventInfo) => {
|
||||||
this.markId = this.editor.mark('erase scribble begin')
|
this.markId = 'erase scribble begin'
|
||||||
|
this.editor.mark(this.markId)
|
||||||
this.info = info
|
this.info = info
|
||||||
|
|
||||||
const { originPagePoint } = this.editor.inputs
|
const { originPagePoint } = this.editor.inputs
|
||||||
|
|
|
@ -4,7 +4,7 @@ export class Idle extends StateNode {
|
||||||
static override id = 'idle'
|
static override id = 'idle'
|
||||||
|
|
||||||
override onEnter = () => {
|
override onEnter = () => {
|
||||||
this.editor.updateInstanceState({ cursor: { type: 'grab', rotation: 0 } }, true)
|
this.editor.setCursor({ type: 'grab', rotation: 0 })
|
||||||
}
|
}
|
||||||
|
|
||||||
override onPointerDown: TLEventHandlers['onPointerDown'] = (info) => {
|
override onPointerDown: TLEventHandlers['onPointerDown'] = (info) => {
|
||||||
|
|
|
@ -5,7 +5,10 @@ export class Pointing extends StateNode {
|
||||||
|
|
||||||
override onEnter = () => {
|
override onEnter = () => {
|
||||||
this.editor.stopCameraAnimation()
|
this.editor.stopCameraAnimation()
|
||||||
this.editor.updateInstanceState({ cursor: { type: 'grabbing', rotation: 0 } }, true)
|
this.editor.updateInstanceState(
|
||||||
|
{ cursor: { type: 'grabbing', rotation: 0 } },
|
||||||
|
{ ephemeral: true }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override onPointerMove: TLEventHandlers['onPointerMove'] = (info) => {
|
override onPointerMove: TLEventHandlers['onPointerMove'] = (info) => {
|
||||||
|
|
|
@ -9,6 +9,6 @@ export class LaserTool extends StateNode {
|
||||||
static override children = () => [Idle, Lasering]
|
static override children = () => [Idle, Lasering]
|
||||||
|
|
||||||
override onEnter = () => {
|
override onEnter = () => {
|
||||||
this.editor.updateInstanceState({ cursor: { type: 'cross', rotation: 0 } }, true)
|
this.editor.setCursor({ type: 'cross', rotation: 0 })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,7 +72,7 @@ export class Brushing extends StateNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
override onCancel?: TLCancelEvent | undefined = (info) => {
|
override onCancel?: TLCancelEvent | undefined = (info) => {
|
||||||
this.editor.setSelectedShapeIds(this.initialSelectedShapeIds, true)
|
this.editor.setSelectedShapeIds(this.initialSelectedShapeIds, { squashing: true })
|
||||||
this.parent.transition('idle', info)
|
this.parent.transition('idle', info)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,7 +168,7 @@ export class Brushing extends StateNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.editor.updateInstanceState({ brush: { ...this.brush.toJson() } })
|
this.editor.updateInstanceState({ brush: { ...this.brush.toJson() } })
|
||||||
this.editor.setSelectedShapeIds(Array.from(results), true)
|
this.editor.setSelectedShapeIds(Array.from(results), { squashing: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
override onInterrupt: TLInterruptEvent = () => {
|
override onInterrupt: TLInterruptEvent = () => {
|
||||||
|
|
|
@ -5,7 +5,10 @@ export class Idle extends StateNode {
|
||||||
static override id = 'idle'
|
static override id = 'idle'
|
||||||
|
|
||||||
override onEnter = () => {
|
override onEnter = () => {
|
||||||
this.editor.updateInstanceState({ cursor: { type: 'default', rotation: 0 } }, true)
|
this.editor.updateInstanceState(
|
||||||
|
{ cursor: { type: 'default', rotation: 0 } },
|
||||||
|
{ ephemeral: true }
|
||||||
|
)
|
||||||
|
|
||||||
const { onlySelectedShape } = this.editor
|
const { onlySelectedShape } = this.editor
|
||||||
|
|
||||||
|
@ -22,7 +25,10 @@ export class Idle extends StateNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
override onExit: TLExitEventHandler = () => {
|
override onExit: TLExitEventHandler = () => {
|
||||||
this.editor.updateInstanceState({ cursor: { type: 'default', rotation: 0 } }, true)
|
this.editor.updateInstanceState(
|
||||||
|
{ cursor: { type: 'default', rotation: 0 } },
|
||||||
|
{ ephemeral: true }
|
||||||
|
)
|
||||||
|
|
||||||
this.editor.off('change-history', this.cleanupCroppingState)
|
this.editor.off('change-history', this.cleanupCroppingState)
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,12 +27,15 @@ export class TranslatingCrop extends StateNode {
|
||||||
this.snapshot = this.createSnapshot()
|
this.snapshot = this.createSnapshot()
|
||||||
|
|
||||||
this.editor.mark(this.markId)
|
this.editor.mark(this.markId)
|
||||||
this.editor.updateInstanceState({ cursor: { type: 'move', rotation: 0 } }, true)
|
this.editor.setCursor({ type: 'move', rotation: 0 })
|
||||||
this.updateShapes()
|
this.updateShapes()
|
||||||
}
|
}
|
||||||
|
|
||||||
override onExit = () => {
|
override onExit = () => {
|
||||||
this.editor.updateInstanceState({ cursor: { type: 'default', rotation: 0 } }, true)
|
this.editor.updateInstanceState(
|
||||||
|
{ cursor: { type: 'default', rotation: 0 } },
|
||||||
|
{ ephemeral: true }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override onPointerMove = () => {
|
override onPointerMove = () => {
|
||||||
|
@ -99,7 +102,7 @@ export class TranslatingCrop extends StateNode {
|
||||||
const partial = getTranslateCroppedImageChange(this.editor, shape, delta)
|
const partial = getTranslateCroppedImageChange(this.editor, shape, delta)
|
||||||
|
|
||||||
if (partial) {
|
if (partial) {
|
||||||
this.editor.updateShapes([partial], true)
|
this.editor.updateShapes([partial], { squashing: true })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,8 @@ export class Cropping extends StateNode {
|
||||||
}
|
}
|
||||||
) => {
|
) => {
|
||||||
this.info = info
|
this.info = info
|
||||||
this.markId = this.editor.mark('cropping')
|
this.markId = 'cropping'
|
||||||
|
this.editor.mark(this.markId)
|
||||||
this.snapshot = this.createSnapshot()
|
this.snapshot = this.createSnapshot()
|
||||||
this.updateShapes()
|
this.updateShapes()
|
||||||
}
|
}
|
||||||
|
@ -199,7 +200,7 @@ export class Cropping extends StateNode {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
this.editor.updateShapes([partial], true)
|
this.editor.updateShapes([partial], { squashing: true })
|
||||||
this.updateCursor()
|
this.updateCursor()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,8 @@ export class DraggingHandle extends StateNode {
|
||||||
this.info = info
|
this.info = info
|
||||||
this.parent.currentToolIdMask = info.onInteractionEnd
|
this.parent.currentToolIdMask = info.onInteractionEnd
|
||||||
this.shapeId = shape.id
|
this.shapeId = shape.id
|
||||||
this.markId = isCreating ? `creating:${shape.id}` : this.editor.mark('dragging handle')
|
this.markId = isCreating ? `creating:${shape.id}` : 'dragging handle'
|
||||||
|
if (!isCreating) this.editor.mark(this.markId)
|
||||||
this.initialHandle = deepCopy(handle)
|
this.initialHandle = deepCopy(handle)
|
||||||
this.initialPageTransform = this.editor.getShapePageTransform(shape)!
|
this.initialPageTransform = this.editor.getShapePageTransform(shape)!
|
||||||
this.initialPageRotation = this.initialPageTransform.rotation()
|
this.initialPageRotation = this.initialPageTransform.rotation()
|
||||||
|
@ -60,7 +61,7 @@ export class DraggingHandle extends StateNode {
|
||||||
|
|
||||||
this.editor.updateInstanceState(
|
this.editor.updateInstanceState(
|
||||||
{ cursor: { type: isCreating ? 'cross' : 'grabbing', rotation: 0 } },
|
{ cursor: { type: isCreating ? 'cross' : 'grabbing', rotation: 0 } },
|
||||||
true
|
{ ephemeral: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
// <!-- Only relevant to arrows
|
// <!-- Only relevant to arrows
|
||||||
|
@ -168,7 +169,10 @@ export class DraggingHandle extends StateNode {
|
||||||
this.parent.currentToolIdMask = undefined
|
this.parent.currentToolIdMask = undefined
|
||||||
this.editor.setHintingIds([])
|
this.editor.setHintingIds([])
|
||||||
this.editor.snaps.clear()
|
this.editor.snaps.clear()
|
||||||
this.editor.updateInstanceState({ cursor: { type: 'default', rotation: 0 } }, true)
|
this.editor.updateInstanceState(
|
||||||
|
{ cursor: { type: 'default', rotation: 0 } },
|
||||||
|
{ ephemeral: true }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private complete() {
|
private complete() {
|
||||||
|
@ -298,7 +302,7 @@ export class DraggingHandle extends StateNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changes) {
|
if (changes) {
|
||||||
editor.updateShapes([next], true)
|
editor.updateShapes([next], { squashing: true })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,10 @@ export class Idle extends StateNode {
|
||||||
|
|
||||||
override onEnter = () => {
|
override onEnter = () => {
|
||||||
this.parent.currentToolIdMask = undefined
|
this.parent.currentToolIdMask = undefined
|
||||||
this.editor.updateInstanceState({ cursor: { type: 'default', rotation: 0 } }, true)
|
this.editor.updateInstanceState(
|
||||||
|
{ cursor: { type: 'default', rotation: 0 } },
|
||||||
|
{ ephemeral: true }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override onPointerMove: TLEventHandlers['onPointerMove'] = () => {
|
override onPointerMove: TLEventHandlers['onPointerMove'] = () => {
|
||||||
|
|
|
@ -33,7 +33,10 @@ export class PointingCropHandle extends StateNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
override onExit = () => {
|
override onExit = () => {
|
||||||
this.editor.updateInstanceState({ cursor: { type: 'default', rotation: 0 } }, true)
|
this.editor.updateInstanceState(
|
||||||
|
{ cursor: { type: 'default', rotation: 0 } },
|
||||||
|
{ ephemeral: true }
|
||||||
|
)
|
||||||
this.parent.currentToolIdMask = undefined
|
this.parent.currentToolIdMask = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,12 +14,18 @@ export class PointingHandle extends StateNode {
|
||||||
this.editor.setHintingIds([initialTerminal.boundShapeId])
|
this.editor.setHintingIds([initialTerminal.boundShapeId])
|
||||||
}
|
}
|
||||||
|
|
||||||
this.editor.updateInstanceState({ cursor: { type: 'grabbing', rotation: 0 } }, true)
|
this.editor.updateInstanceState(
|
||||||
|
{ cursor: { type: 'grabbing', rotation: 0 } },
|
||||||
|
{ ephemeral: true }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override onExit = () => {
|
override onExit = () => {
|
||||||
this.editor.setHintingIds([])
|
this.editor.setHintingIds([])
|
||||||
this.editor.updateInstanceState({ cursor: { type: 'default', rotation: 0 } }, true)
|
this.editor.updateInstanceState(
|
||||||
|
{ cursor: { type: 'default', rotation: 0 } },
|
||||||
|
{ ephemeral: true }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override onPointerUp: TLEventHandlers['onPointerUp'] = () => {
|
override onPointerUp: TLEventHandlers['onPointerUp'] = () => {
|
||||||
|
|
|
@ -28,7 +28,10 @@ export class PointingRotateHandle extends StateNode {
|
||||||
|
|
||||||
override onExit = () => {
|
override onExit = () => {
|
||||||
this.parent.currentToolIdMask = undefined
|
this.parent.currentToolIdMask = undefined
|
||||||
this.editor.updateInstanceState({ cursor: { type: 'default', rotation: 0 } }, true)
|
this.editor.updateInstanceState(
|
||||||
|
{ cursor: { type: 'default', rotation: 0 } },
|
||||||
|
{ ephemeral: true }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override onPointerMove = () => {
|
override onPointerMove = () => {
|
||||||
|
|
|
@ -56,13 +56,16 @@ export class Resizing extends StateNode {
|
||||||
this.creationCursorOffset = creationCursorOffset
|
this.creationCursorOffset = creationCursorOffset
|
||||||
|
|
||||||
if (info.isCreating) {
|
if (info.isCreating) {
|
||||||
this.editor.updateInstanceState({ cursor: { type: 'cross', rotation: 0 } }, true)
|
this.editor.updateInstanceState(
|
||||||
|
{ cursor: { type: 'cross', rotation: 0 } },
|
||||||
|
{ ephemeral: true }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.snapshot = this._createSnapshot()
|
this.snapshot = this._createSnapshot()
|
||||||
this.markId = isCreating
|
this.markId = isCreating ? `creating:${this.editor.onlySelectedShape!.id}` : 'starting resizing'
|
||||||
? `creating:${this.editor.onlySelectedShape!.id}`
|
|
||||||
: this.editor.mark('starting resizing')
|
if (!isCreating) this.editor.mark(this.markId)
|
||||||
|
|
||||||
this.handleResizeStart()
|
this.handleResizeStart()
|
||||||
this.updateShapes()
|
this.updateShapes()
|
||||||
|
@ -349,12 +352,15 @@ export class Resizing extends StateNode {
|
||||||
|
|
||||||
nextCursor.rotation = rotation
|
nextCursor.rotation = rotation
|
||||||
|
|
||||||
this.editor.updateInstanceState({ cursor: nextCursor })
|
this.editor.setCursor(nextCursor)
|
||||||
}
|
}
|
||||||
|
|
||||||
override onExit = () => {
|
override onExit = () => {
|
||||||
this.parent.currentToolIdMask = undefined
|
this.parent.currentToolIdMask = undefined
|
||||||
this.editor.updateInstanceState({ cursor: { type: 'default', rotation: 0 } }, true)
|
this.editor.updateInstanceState(
|
||||||
|
{ cursor: { type: 'default', rotation: 0 } },
|
||||||
|
{ ephemeral: true }
|
||||||
|
)
|
||||||
this.editor.snaps.clear()
|
this.editor.snaps.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,8 @@ export class Rotating extends StateNode {
|
||||||
this.info = info
|
this.info = info
|
||||||
this.parent.currentToolIdMask = info.onInteractionEnd
|
this.parent.currentToolIdMask = info.onInteractionEnd
|
||||||
|
|
||||||
this.markId = this.editor.mark('rotate start')
|
this.markId = 'rotate start'
|
||||||
|
this.editor.mark(this.markId)
|
||||||
|
|
||||||
const snapshot = getRotationSnapshot({ editor: this.editor })
|
const snapshot = getRotationSnapshot({ editor: this.editor })
|
||||||
if (!snapshot) return this.parent.transition('idle', this.info)
|
if (!snapshot) return this.parent.transition('idle', this.info)
|
||||||
|
@ -40,7 +41,7 @@ export class Rotating extends StateNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
override onExit = () => {
|
override onExit = () => {
|
||||||
this.editor.updateInstanceState({ cursor: { type: 'none', rotation: 0 } }, true)
|
this.editor.setCursor({ type: 'default', rotation: 0 })
|
||||||
this.parent.currentToolIdMask = undefined
|
this.parent.currentToolIdMask = undefined
|
||||||
|
|
||||||
this.snapshot = {} as TLRotationSnapshot
|
this.snapshot = {} as TLRotationSnapshot
|
||||||
|
|
|
@ -170,7 +170,7 @@ export class ScribbleBrushing extends StateNode {
|
||||||
: [...newlySelectedShapeIds]
|
: [...newlySelectedShapeIds]
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
true
|
{ squashing: true }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,7 +179,7 @@ export class ScribbleBrushing extends StateNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
private cancel() {
|
private cancel() {
|
||||||
this.editor.setSelectedShapeIds([...this.initialSelectedShapeIds], true)
|
this.editor.setSelectedShapeIds([...this.initialSelectedShapeIds], { squashing: true })
|
||||||
this.parent.transition('idle', {})
|
this.parent.transition('idle', {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,9 +53,8 @@ export class Translating extends StateNode {
|
||||||
this.isCreating = isCreating
|
this.isCreating = isCreating
|
||||||
this.editAfterComplete = editAfterComplete
|
this.editAfterComplete = editAfterComplete
|
||||||
|
|
||||||
this.markId = isCreating
|
this.markId = isCreating ? `creating:${this.editor.onlySelectedShape!.id}` : 'translating'
|
||||||
? this.editor.mark(`creating:${this.editor.onlySelectedShape!.id}`)
|
this.editor.mark(this.markId)
|
||||||
: this.editor.mark('translating')
|
|
||||||
this.handleEnter(info)
|
this.handleEnter(info)
|
||||||
this.editor.on('tick', this.updateParent)
|
this.editor.on('tick', this.updateParent)
|
||||||
}
|
}
|
||||||
|
@ -66,7 +65,10 @@ export class Translating extends StateNode {
|
||||||
this.selectionSnapshot = {} as any
|
this.selectionSnapshot = {} as any
|
||||||
this.snapshot = {} as any
|
this.snapshot = {} as any
|
||||||
this.editor.snaps.clear()
|
this.editor.snaps.clear()
|
||||||
this.editor.updateInstanceState({ cursor: { type: 'default', rotation: 0 } }, true)
|
this.editor.updateInstanceState(
|
||||||
|
{ cursor: { type: 'default', rotation: 0 } },
|
||||||
|
{ ephemeral: true }
|
||||||
|
)
|
||||||
this.dragAndDropManager.clear()
|
this.dragAndDropManager.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,7 +113,8 @@ export class Translating extends StateNode {
|
||||||
|
|
||||||
this.isCloning = true
|
this.isCloning = true
|
||||||
this.reset()
|
this.reset()
|
||||||
this.markId = this.editor.mark('translating')
|
this.markId = 'translating'
|
||||||
|
this.editor.mark(this.markId)
|
||||||
|
|
||||||
this.editor.duplicateShapes(Array.from(this.editor.selectedShapeIds))
|
this.editor.duplicateShapes(Array.from(this.editor.selectedShapeIds))
|
||||||
|
|
||||||
|
@ -124,7 +127,8 @@ export class Translating extends StateNode {
|
||||||
this.isCloning = false
|
this.isCloning = false
|
||||||
this.snapshot = this.selectionSnapshot
|
this.snapshot = this.selectionSnapshot
|
||||||
this.reset()
|
this.reset()
|
||||||
this.markId = this.editor.mark('translating')
|
this.markId = 'translating'
|
||||||
|
this.editor.mark(this.markId)
|
||||||
this.updateShapes()
|
this.updateShapes()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,7 +175,7 @@ export class Translating extends StateNode {
|
||||||
this.isCloning = false
|
this.isCloning = false
|
||||||
this.info = info
|
this.info = info
|
||||||
|
|
||||||
this.editor.updateInstanceState({ cursor: { type: 'move', rotation: 0 } }, true)
|
this.editor.setCursor({ type: 'move', rotation: 0 })
|
||||||
this.selectionSnapshot = getTranslatingSnapshot(this.editor)
|
this.selectionSnapshot = getTranslatingSnapshot(this.editor)
|
||||||
|
|
||||||
// Don't clone on create; otherwise clone on altKey
|
// Don't clone on create; otherwise clone on altKey
|
||||||
|
@ -406,6 +410,6 @@ export function moveShapesToPoint({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
true
|
{ squashing: true }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ export class ZoomTool extends StateNode {
|
||||||
this.currentToolIdMask = undefined
|
this.currentToolIdMask = undefined
|
||||||
this.editor.updateInstanceState(
|
this.editor.updateInstanceState(
|
||||||
{ zoomBrush: null, cursor: { type: 'default', rotation: 0 } },
|
{ zoomBrush: null, cursor: { type: 'default', rotation: 0 } },
|
||||||
true
|
{ ephemeral: true }
|
||||||
)
|
)
|
||||||
this.currentToolIdMask = undefined
|
this.currentToolIdMask = undefined
|
||||||
}
|
}
|
||||||
|
@ -53,9 +53,15 @@ export class ZoomTool extends StateNode {
|
||||||
|
|
||||||
private updateCursor() {
|
private updateCursor() {
|
||||||
if (this.editor.inputs.altKey) {
|
if (this.editor.inputs.altKey) {
|
||||||
this.editor.updateInstanceState({ cursor: { type: 'zoom-out', rotation: 0 } }, true)
|
this.editor.updateInstanceState(
|
||||||
|
{ cursor: { type: 'zoom-out', rotation: 0 } },
|
||||||
|
{ ephemeral: true }
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
this.editor.updateInstanceState({ cursor: { type: 'zoom-in', rotation: 0 } }, true)
|
this.editor.updateInstanceState(
|
||||||
|
{ cursor: { type: 'zoom-in', rotation: 0 } },
|
||||||
|
{ ephemeral: true }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,13 +77,11 @@ export const MoveToPageMenu = track(function MoveToPageMenu() {
|
||||||
<_ContextMenu.Item
|
<_ContextMenu.Item
|
||||||
key="new-page"
|
key="new-page"
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
editor.mark('move_shapes_to_page')
|
|
||||||
const newPageId = PageRecordType.createId()
|
const newPageId = PageRecordType.createId()
|
||||||
const ids = editor.selectedShapeIds
|
const ids = editor.selectedShapeIds
|
||||||
const oldPageId = editor.currentPageId
|
|
||||||
editor.batch(() => {
|
editor.batch(() => {
|
||||||
editor.createPage('Page 1', newPageId)
|
editor.mark('move_shapes_to_page')
|
||||||
editor.setCurrentPage(oldPageId)
|
editor.createPage({ name: 'Page', id: newPageId })
|
||||||
editor.moveShapesToPage(ids, newPageId)
|
editor.moveShapesToPage(ids, newPageId)
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -17,7 +17,7 @@ export const PageItemInput = function PageItemInput({
|
||||||
|
|
||||||
const handleChange = useCallback(
|
const handleChange = useCallback(
|
||||||
(value: string) => {
|
(value: string) => {
|
||||||
editor.renamePage(id, value ? value : 'New Page', true)
|
editor.renamePage(id, value ? value : 'New Page', { ephemeral: true })
|
||||||
},
|
},
|
||||||
[editor, id]
|
[editor, id]
|
||||||
)
|
)
|
||||||
|
@ -25,7 +25,7 @@ export const PageItemInput = function PageItemInput({
|
||||||
const handleComplete = useCallback(
|
const handleComplete = useCallback(
|
||||||
(value: string) => {
|
(value: string) => {
|
||||||
editor.mark('rename page')
|
editor.mark('rename page')
|
||||||
editor.renamePage(id, value || 'New Page', false)
|
editor.renamePage(id, value || 'New Page', { ephemeral: false })
|
||||||
},
|
},
|
||||||
[editor, id]
|
[editor, id]
|
||||||
)
|
)
|
||||||
|
|
|
@ -240,10 +240,13 @@ export const PageMenu = function PageMenu() {
|
||||||
const handleCreatePageClick = useCallback(() => {
|
const handleCreatePageClick = useCallback(() => {
|
||||||
if (isReadonlyMode) return
|
if (isReadonlyMode) return
|
||||||
|
|
||||||
editor.mark('creating page')
|
editor.batch(() => {
|
||||||
const newPageId = PageRecordType.createId()
|
editor.mark('creating page')
|
||||||
editor.createPage(msg('page-menu.new-page-initial-name'), newPageId)
|
const newPageId = PageRecordType.createId()
|
||||||
setIsEditing(true)
|
editor.createPage({ name: msg('page-menu.new-page-initial-name'), id: newPageId })
|
||||||
|
editor.setCurrentPage(newPageId)
|
||||||
|
setIsEditing(true)
|
||||||
|
})
|
||||||
}, [editor, msg, isReadonlyMode])
|
}, [editor, msg, isReadonlyMode])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -383,8 +386,10 @@ export const PageMenu = function PageMenu() {
|
||||||
editor.renamePage(page.id, name)
|
editor.renamePage(page.id, name)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setIsEditing(true)
|
editor.batch(() => {
|
||||||
editor.setCurrentPage(page.id)
|
setIsEditing(true)
|
||||||
|
editor.setCurrentPage(page.id)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -95,7 +95,7 @@ function useStyleChangeCallback() {
|
||||||
|
|
||||||
return React.useMemo(() => {
|
return React.useMemo(() => {
|
||||||
return function <T>(style: StyleProp<T>, value: T, squashing: boolean) {
|
return function <T>(style: StyleProp<T>, value: T, squashing: boolean) {
|
||||||
editor.setStyle(style, value, squashing)
|
editor.setStyle(style, value, { squashing })
|
||||||
editor.updateInstanceState({ isChangingStyle: true })
|
editor.updateInstanceState({ isChangingStyle: true })
|
||||||
}
|
}
|
||||||
}, [editor])
|
}, [editor])
|
||||||
|
@ -118,7 +118,7 @@ function CommonStylePickerSet({
|
||||||
const handleOpacityValueChange = React.useCallback(
|
const handleOpacityValueChange = React.useCallback(
|
||||||
(value: number, ephemeral: boolean) => {
|
(value: number, ephemeral: boolean) => {
|
||||||
const item = tldrawSupportedOpacities[value]
|
const item = tldrawSupportedOpacities[value]
|
||||||
editor.setOpacity(item, ephemeral)
|
editor.setOpacity(item, { ephemeral })
|
||||||
editor.updateInstanceState({ isChangingStyle: true })
|
editor.updateInstanceState({ isChangingStyle: true })
|
||||||
},
|
},
|
||||||
[editor]
|
[editor]
|
||||||
|
|
|
@ -323,7 +323,7 @@ export async function pasteExcalidrawContent(editor: Editor, clipboard: any, poi
|
||||||
|
|
||||||
editor.mark('paste')
|
editor.mark('paste')
|
||||||
|
|
||||||
editor.putContent(tldrawContent, {
|
editor.putContentOntoCurrentPage(tldrawContent, {
|
||||||
point: p,
|
point: p,
|
||||||
select: false,
|
select: false,
|
||||||
preserveIds: true,
|
preserveIds: true,
|
||||||
|
|
|
@ -12,7 +12,7 @@ export function pasteTldrawContent(editor: Editor, clipboard: TLContent, point?:
|
||||||
const p = point ?? (editor.inputs.shiftKey ? editor.inputs.currentPagePoint : undefined)
|
const p = point ?? (editor.inputs.shiftKey ? editor.inputs.currentPagePoint : undefined)
|
||||||
|
|
||||||
editor.mark('paste')
|
editor.mark('paste')
|
||||||
editor.putContent(clipboard, {
|
editor.putContentOntoCurrentPage(clipboard, {
|
||||||
point: p,
|
point: p,
|
||||||
select: true,
|
select: true,
|
||||||
})
|
})
|
||||||
|
|
|
@ -210,7 +210,7 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
|
||||||
readonlyOk: false,
|
readonlyOk: false,
|
||||||
onSelect(source) {
|
onSelect(source) {
|
||||||
trackEvent('toggle-auto-size', { source })
|
trackEvent('toggle-auto-size', { source })
|
||||||
editor.mark()
|
editor.mark('toggling auto size')
|
||||||
editor.updateShapes(
|
editor.updateShapes(
|
||||||
editor.selectedShapes
|
editor.selectedShapes
|
||||||
.filter(
|
.filter(
|
||||||
|
@ -842,7 +842,7 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
|
||||||
{
|
{
|
||||||
exportBackground: !editor.instanceState.exportBackground,
|
exportBackground: !editor.instanceState.exportBackground,
|
||||||
},
|
},
|
||||||
true
|
{ ephemeral: true }
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
checkbox: true,
|
checkbox: true,
|
||||||
|
@ -902,7 +902,7 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
|
||||||
{
|
{
|
||||||
isDebugMode: !editor.instanceState.isDebugMode,
|
isDebugMode: !editor.instanceState.isDebugMode,
|
||||||
},
|
},
|
||||||
true
|
{ ephemeral: true }
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
checkbox: true,
|
checkbox: true,
|
||||||
|
|
|
@ -514,7 +514,7 @@ async function handleClipboardThings(editor: Editor, things: ClipboardThing[], p
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
const handleNativeOrMenuCopy = (editor: Editor) => {
|
const handleNativeOrMenuCopy = (editor: Editor) => {
|
||||||
const content = editor.getContent(editor.selectedShapeIds)
|
const content = editor.getContentFromCurrentPage(editor.selectedShapeIds)
|
||||||
if (!content) {
|
if (!content) {
|
||||||
if (navigator && navigator.clipboard) {
|
if (navigator && navigator.clipboard) {
|
||||||
navigator.clipboard.writeText('')
|
navigator.clipboard.writeText('')
|
||||||
|
|
|
@ -93,7 +93,7 @@ export function useCopyAs() {
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'json': {
|
case 'json': {
|
||||||
const data = editor.getContent(ids)
|
const data = editor.getContentFromCurrentPage(ids)
|
||||||
|
|
||||||
if (window.navigator.clipboard) {
|
if (window.navigator.clipboard) {
|
||||||
const jsonStr = JSON.stringify(data)
|
const jsonStr = JSON.stringify(data)
|
||||||
|
|
|
@ -79,7 +79,7 @@ export function useExportAs() {
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'json': {
|
case 'json': {
|
||||||
const data = editor.getContent(ids)
|
const data = editor.getContentFromCurrentPage(ids)
|
||||||
const dataURL = URL.createObjectURL(
|
const dataURL = URL.createObjectURL(
|
||||||
new Blob([JSON.stringify(data, null, 4)], { type: 'application/json' })
|
new Blob([JSON.stringify(data, null, 4)], { type: 'application/json' })
|
||||||
)
|
)
|
||||||
|
|
|
@ -111,7 +111,7 @@ export function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {
|
||||||
[GeoShapeGeoStyle.id]: id,
|
[GeoShapeGeoStyle.id]: id,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
true
|
{ ephemeral: true }
|
||||||
)
|
)
|
||||||
editor.setCurrentTool('geo')
|
editor.setCurrentTool('geo')
|
||||||
trackEvent('select-tool', { source, id: `geo-${id}` })
|
trackEvent('select-tool', { source, id: `geo-${id}` })
|
||||||
|
|
|
@ -113,7 +113,7 @@ export function buildFromV1Document(editor: Editor, document: LegacyTldrawDocume
|
||||||
} else {
|
} else {
|
||||||
const pageId = PageRecordType.createId()
|
const pageId = PageRecordType.createId()
|
||||||
v1PageIdsToV2PageIds.set(v1Page.id, pageId)
|
v1PageIdsToV2PageIds.set(v1Page.id, pageId)
|
||||||
editor.createPage(v1Page.name ?? 'Page', pageId)
|
editor.createPage({ name: v1Page.name ?? 'Page', id: pageId })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ beforeEach(() => {
|
||||||
])
|
])
|
||||||
|
|
||||||
const page1 = editor.currentPageId
|
const page1 = editor.currentPageId
|
||||||
editor.createPage('page 2', ids.page2)
|
editor.createPage({ name: 'page 2', id: ids.page2 })
|
||||||
editor.setCurrentPage(page1)
|
editor.setCurrentPage(page1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -429,12 +429,12 @@ describe('isFocused', () => {
|
||||||
expect(focusMock).not.toHaveBeenCalled()
|
expect(focusMock).not.toHaveBeenCalled()
|
||||||
expect(blurMock).not.toHaveBeenCalled()
|
expect(blurMock).not.toHaveBeenCalled()
|
||||||
|
|
||||||
editor.focus()
|
editor.getContainer().focus()
|
||||||
|
|
||||||
expect(focusMock).toHaveBeenCalled()
|
expect(focusMock).toHaveBeenCalled()
|
||||||
expect(blurMock).not.toHaveBeenCalled()
|
expect(blurMock).not.toHaveBeenCalled()
|
||||||
|
|
||||||
editor.blur()
|
editor.getContainer().blur()
|
||||||
|
|
||||||
expect(blurMock).toHaveBeenCalled()
|
expect(blurMock).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
@ -471,7 +471,7 @@ describe('getShapeUtil', () => {
|
||||||
{ id: ids.box1, type: 'blorg', x: 100, y: 100, props: { w: 100, h: 100 } },
|
{ id: ids.box1, type: 'blorg', x: 100, y: 100, props: { w: 100, h: 100 } },
|
||||||
])
|
])
|
||||||
const page1 = editor.currentPageId
|
const page1 = editor.currentPageId
|
||||||
editor.createPage('page 2', ids.page2)
|
editor.createPage({ name: 'page 2', id: ids.page2 })
|
||||||
editor.setCurrentPage(page1)
|
editor.setCurrentPage(page1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,7 @@ describe('createSessionStateSnapshotSignal', () => {
|
||||||
expect(isGridMode).toBe(true)
|
expect(isGridMode).toBe(true)
|
||||||
expect(numPages).toBe(1)
|
expect(numPages).toBe(1)
|
||||||
|
|
||||||
editor.createPage('new page')
|
editor.createPage({ name: 'new page' })
|
||||||
|
|
||||||
expect(isGridMode).toBe(true)
|
expect(isGridMode).toBe(true)
|
||||||
expect(editor.pages.length).toBe(2)
|
expect(editor.pages.length).toBe(2)
|
||||||
|
|
|
@ -136,7 +136,7 @@ export class TestEditor extends Editor {
|
||||||
|
|
||||||
copy = (ids = this.selectedShapeIds) => {
|
copy = (ids = this.selectedShapeIds) => {
|
||||||
if (ids.length > 0) {
|
if (ids.length > 0) {
|
||||||
const content = this.getContent(ids)
|
const content = this.getContentFromCurrentPage(ids)
|
||||||
if (content) {
|
if (content) {
|
||||||
this.clipboard = content
|
this.clipboard = content
|
||||||
}
|
}
|
||||||
|
@ -146,7 +146,7 @@ export class TestEditor extends Editor {
|
||||||
|
|
||||||
cut = (ids = this.selectedShapeIds) => {
|
cut = (ids = this.selectedShapeIds) => {
|
||||||
if (ids.length > 0) {
|
if (ids.length > 0) {
|
||||||
const content = this.getContent(ids)
|
const content = this.getContentFromCurrentPage(ids)
|
||||||
if (content) {
|
if (content) {
|
||||||
this.clipboard = content
|
this.clipboard = content
|
||||||
}
|
}
|
||||||
|
@ -160,7 +160,7 @@ export class TestEditor extends Editor {
|
||||||
const p = this.inputs.shiftKey ? this.inputs.currentPagePoint : point
|
const p = this.inputs.shiftKey ? this.inputs.currentPagePoint : point
|
||||||
|
|
||||||
this.mark('pasting')
|
this.mark('pasting')
|
||||||
this.putContent(this.clipboard, {
|
this.putContentOntoCurrentPage(this.clipboard, {
|
||||||
point: p,
|
point: p,
|
||||||
select: true,
|
select: true,
|
||||||
})
|
})
|
||||||
|
|
|
@ -219,7 +219,10 @@ describe('<TldrawEditor />', () => {
|
||||||
|
|
||||||
expect(editor).toBeTruthy()
|
expect(editor).toBeTruthy()
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
editor.updateInstanceState({ screenBounds: { x: 0, y: 0, w: 1080, h: 720 } }, true, true)
|
editor.updateInstanceState(
|
||||||
|
{ screenBounds: { x: 0, y: 0, w: 1080, h: 720 } },
|
||||||
|
{ ephemeral: true, squashing: true }
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const id = createShapeId()
|
const id = createShapeId()
|
||||||
|
@ -340,7 +343,10 @@ describe('Custom shapes', () => {
|
||||||
|
|
||||||
expect(editor).toBeTruthy()
|
expect(editor).toBeTruthy()
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
editor.updateInstanceState({ screenBounds: { x: 0, y: 0, w: 1080, h: 720 } }, true, true)
|
editor.updateInstanceState(
|
||||||
|
{ screenBounds: { x: 0, y: 0, w: 1080, h: 720 } },
|
||||||
|
{ ephemeral: true, squashing: true }
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(editor.shapeUtils.card).toBeTruthy()
|
expect(editor.shapeUtils.card).toBeTruthy()
|
||||||
|
|
|
@ -248,7 +248,7 @@ describe('arrowBindingsIndex', () => {
|
||||||
expect(editor.getArrowsBoundTo(ids.box2)).toHaveLength(3)
|
expect(editor.getArrowsBoundTo(ids.box2)).toHaveLength(3)
|
||||||
expect(editor.getArrowsBoundTo(ids.box1)).toHaveLength(3)
|
expect(editor.getArrowsBoundTo(ids.box1)).toHaveLength(3)
|
||||||
|
|
||||||
editor.nudgeShapes([ids.box2], { x: 0, y: -1 }, true)
|
editor.nudgeShapes([ids.box2], { x: 0, y: -1 })
|
||||||
|
|
||||||
expect(editor.getArrowsBoundTo(ids.box2)).toHaveLength(3)
|
expect(editor.getArrowsBoundTo(ids.box2)).toHaveLength(3)
|
||||||
expect(editor.getArrowsBoundTo(ids.box1)).toHaveLength(3)
|
expect(editor.getArrowsBoundTo(ids.box1)).toHaveLength(3)
|
||||||
|
|
129
packages/tldraw/src/test/cleanup.test.ts
Normal file
129
packages/tldraw/src/test/cleanup.test.ts
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
import { TLArrowShape, createShapeId } from '@tldraw/editor'
|
||||||
|
import { TestEditor } from './TestEditor'
|
||||||
|
|
||||||
|
let editor: TestEditor
|
||||||
|
|
||||||
|
const ids = {
|
||||||
|
box1: createShapeId('box1'),
|
||||||
|
box2: createShapeId('box2'),
|
||||||
|
box3: createShapeId('box3'),
|
||||||
|
box4: createShapeId('box4'),
|
||||||
|
box5: createShapeId('box5'),
|
||||||
|
frame1: createShapeId('frame1'),
|
||||||
|
group1: createShapeId('group1'),
|
||||||
|
group2: createShapeId('group2'),
|
||||||
|
group3: createShapeId('group3'),
|
||||||
|
arrow1: createShapeId('arrow1'),
|
||||||
|
arrow2: createShapeId('arrow2'),
|
||||||
|
arrow3: createShapeId('arrow3'),
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
editor = new TestEditor()
|
||||||
|
})
|
||||||
|
|
||||||
|
function arrow() {
|
||||||
|
return editor.currentPageShapes.find((s) => s.type === 'arrow') as TLArrowShape
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('restoring bound arrows', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
editor.createShapes([
|
||||||
|
{ id: ids.box1, type: 'geo', x: 0, y: 0 },
|
||||||
|
{ id: ids.box2, type: 'geo', x: 200, y: 0 },
|
||||||
|
])
|
||||||
|
// create arrow from box1 to box2
|
||||||
|
editor
|
||||||
|
.setCurrentTool('arrow')
|
||||||
|
.pointerMove(50, 50)
|
||||||
|
.pointerDown()
|
||||||
|
.pointerMove(250, 50)
|
||||||
|
.pointerUp()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('removes bound arrows on delete, restores them on undo but only when change was done by user', () => {
|
||||||
|
editor.mark('deleting')
|
||||||
|
editor.deleteShapes([ids.box2])
|
||||||
|
expect(arrow().props.end.type).toBe('point')
|
||||||
|
editor.undo()
|
||||||
|
expect(arrow().props.end.type).toBe('binding')
|
||||||
|
editor.redo()
|
||||||
|
expect(arrow().props.end.type).toBe('point')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('removes / restores multiple bindings', () => {
|
||||||
|
editor.mark('deleting')
|
||||||
|
expect(arrow().props.start.type).toBe('binding')
|
||||||
|
expect(arrow().props.end.type).toBe('binding')
|
||||||
|
|
||||||
|
editor.deleteShapes([ids.box1, ids.box2])
|
||||||
|
expect(arrow().props.start.type).toBe('point')
|
||||||
|
expect(arrow().props.end.type).toBe('point')
|
||||||
|
|
||||||
|
editor.undo()
|
||||||
|
expect(arrow().props.start.type).toBe('binding')
|
||||||
|
expect(arrow().props.end.type).toBe('binding')
|
||||||
|
|
||||||
|
editor.redo()
|
||||||
|
expect(arrow().props.start.type).toBe('point')
|
||||||
|
expect(arrow().props.end.type).toBe('point')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('restoring bound arrows multiplayer', () => {
|
||||||
|
it('restores bound arrows after the shape was deleted by a different client', () => {
|
||||||
|
editor.mark('before creating box shape')
|
||||||
|
editor.createShapes([{ id: ids.box2, type: 'geo', x: 100, y: 0 }])
|
||||||
|
|
||||||
|
editor.setCurrentTool('arrow').pointerMove(0, 50).pointerDown().pointerMove(150, 50).pointerUp()
|
||||||
|
|
||||||
|
// console.log(JSON.stringify(editor.history._undos.value.toArray(), null, 2))
|
||||||
|
|
||||||
|
expect(arrow().props.start.type).toBe('point')
|
||||||
|
expect(arrow().props.end.type).toBe('binding')
|
||||||
|
|
||||||
|
// Merge a change from a remote source that deletes box 2
|
||||||
|
editor.store.mergeRemoteChanges(() => {
|
||||||
|
editor.store.remove([ids.box2])
|
||||||
|
})
|
||||||
|
|
||||||
|
// box is gone
|
||||||
|
expect(editor.getShape(ids.box2)).toBeUndefined()
|
||||||
|
// arrow is still there, but without its binding
|
||||||
|
expect(arrow()).not.toBeUndefined()
|
||||||
|
expect(arrow().props.start.type).toBe('point')
|
||||||
|
expect(arrow().props.end.type).toBe('point')
|
||||||
|
|
||||||
|
editor.undo() // undo creating the arrow
|
||||||
|
|
||||||
|
// arrow is gone too now
|
||||||
|
expect(editor.currentPageShapeIds.size).toBe(0)
|
||||||
|
|
||||||
|
editor.redo() // redo creating the arrow
|
||||||
|
|
||||||
|
expect(editor.getShape(ids.box2)).toBeUndefined()
|
||||||
|
expect(arrow()).not.toBeUndefined()
|
||||||
|
expect(arrow().props.start.type).toBe('point')
|
||||||
|
expect(arrow().props.end.type).toBe('point')
|
||||||
|
|
||||||
|
editor.undo() // undo creating arrow
|
||||||
|
|
||||||
|
expect(editor.currentPageShapeIds.size).toBe(0)
|
||||||
|
|
||||||
|
editor.undo() // undo creating box
|
||||||
|
|
||||||
|
expect(editor.currentPageShapeIds.size).toBe(0)
|
||||||
|
|
||||||
|
editor.redo() // redo creating box
|
||||||
|
|
||||||
|
// box is back! arrow is gone
|
||||||
|
expect(editor.getShape(ids.box2)).not.toBeUndefined()
|
||||||
|
expect(arrow()).toBeUndefined()
|
||||||
|
|
||||||
|
editor.redo() // redo creating arrow
|
||||||
|
|
||||||
|
// box is back! arrow should be bound
|
||||||
|
expect(arrow().props.start.type).toBe('point')
|
||||||
|
expect(arrow().props.end.type).toBe('binding')
|
||||||
|
})
|
||||||
|
})
|
|
@ -10,13 +10,21 @@ beforeEach(() => {
|
||||||
it('Creates a page', () => {
|
it('Creates a page', () => {
|
||||||
const oldPageId = editor.currentPageId
|
const oldPageId = editor.currentPageId
|
||||||
const n = editor.pages.length
|
const n = editor.pages.length
|
||||||
editor.createPage('Page 1')
|
editor.mark('creating new page')
|
||||||
|
editor.createPage({ name: 'Page 1' })
|
||||||
expect(editor.pages.length).toBe(n + 1)
|
expect(editor.pages.length).toBe(n + 1)
|
||||||
const newPageId = editor.pages[n].id
|
const newPageId = editor.pages[n].id
|
||||||
|
// does not move to the new page right away
|
||||||
|
expect(editor.currentPageId).toBe(oldPageId)
|
||||||
|
|
||||||
|
// needs to be done manually
|
||||||
|
editor.setCurrentPage(newPageId)
|
||||||
expect(editor.currentPageId).toBe(newPageId)
|
expect(editor.currentPageId).toBe(newPageId)
|
||||||
|
|
||||||
editor.undo()
|
editor.undo()
|
||||||
expect(editor.pages.length).toBe(n)
|
expect(editor.pages.length).toBe(n)
|
||||||
expect(editor.currentPageId).toBe(oldPageId)
|
expect(editor.currentPageId).toBe(oldPageId)
|
||||||
|
|
||||||
editor.redo()
|
editor.redo()
|
||||||
expect(editor.pages.length).toBe(n + 1)
|
expect(editor.pages.length).toBe(n + 1)
|
||||||
expect(editor.currentPageId).toBe(newPageId)
|
expect(editor.currentPageId).toBe(newPageId)
|
||||||
|
@ -24,7 +32,7 @@ it('Creates a page', () => {
|
||||||
|
|
||||||
it("Doesn't create a page if max pages is reached", () => {
|
it("Doesn't create a page if max pages is reached", () => {
|
||||||
for (let i = 0; i < MAX_PAGES + 1; i++) {
|
for (let i = 0; i < MAX_PAGES + 1; i++) {
|
||||||
editor.createPage(`Test Page ${i}`)
|
editor.createPage({ name: `Test Page ${i}` })
|
||||||
}
|
}
|
||||||
expect(editor.pages.length).toBe(MAX_PAGES)
|
expect(editor.pages.length).toBe(MAX_PAGES)
|
||||||
})
|
})
|
||||||
|
@ -52,6 +60,6 @@ it('[regression] does not die if every page has the same index', () => {
|
||||||
|
|
||||||
expect(editor.pages.every((p) => p.index === page.index)).toBe(true)
|
expect(editor.pages.every((p) => p.index === page.index)).toBe(true)
|
||||||
|
|
||||||
editor.createPage('My Special Test Page')
|
editor.createPage({ name: 'My Special Test Page' })
|
||||||
expect(editor.pages.some((p) => p.name === 'My Special Test Page')).toBe(true)
|
expect(editor.pages.some((p) => p.name === 'My Special Test Page')).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
|
@ -10,7 +10,7 @@ beforeEach(() => {
|
||||||
describe('deletePage', () => {
|
describe('deletePage', () => {
|
||||||
it('deletes the page', () => {
|
it('deletes the page', () => {
|
||||||
const page2Id = PageRecordType.createId('page2')
|
const page2Id = PageRecordType.createId('page2')
|
||||||
editor.createPage('New Page 2', page2Id)
|
editor.createPage({ name: 'New Page 2', id: page2Id })
|
||||||
|
|
||||||
const pages = editor.pages
|
const pages = editor.pages
|
||||||
expect(pages.length).toBe(2)
|
expect(pages.length).toBe(2)
|
||||||
|
@ -20,13 +20,13 @@ describe('deletePage', () => {
|
||||||
})
|
})
|
||||||
it('is undoable and redoable', () => {
|
it('is undoable and redoable', () => {
|
||||||
const page2Id = PageRecordType.createId('page2')
|
const page2Id = PageRecordType.createId('page2')
|
||||||
editor.mark()
|
editor.mark('before creating page')
|
||||||
editor.createPage('New Page 2', page2Id)
|
editor.createPage({ name: 'New Page 2', id: page2Id })
|
||||||
|
|
||||||
const pages = editor.pages
|
const pages = editor.pages
|
||||||
expect(pages.length).toBe(2)
|
expect(pages.length).toBe(2)
|
||||||
|
|
||||||
editor.mark()
|
editor.mark('before deleting page')
|
||||||
editor.deletePage(pages[0].id)
|
editor.deletePage(pages[0].id)
|
||||||
expect(editor.pages.length).toBe(1)
|
expect(editor.pages.length).toBe(1)
|
||||||
|
|
||||||
|
@ -39,8 +39,8 @@ describe('deletePage', () => {
|
||||||
})
|
})
|
||||||
it('does not allow deleting all pages', () => {
|
it('does not allow deleting all pages', () => {
|
||||||
const page2Id = PageRecordType.createId('page2')
|
const page2Id = PageRecordType.createId('page2')
|
||||||
editor.mark()
|
editor.mark('before creating page')
|
||||||
editor.createPage('New Page 2', page2Id)
|
editor.createPage({ name: 'New Page 2', id: page2Id })
|
||||||
|
|
||||||
const pages = editor.pages
|
const pages = editor.pages
|
||||||
editor.deletePage(pages[1].id)
|
editor.deletePage(pages[1].id)
|
||||||
|
@ -53,8 +53,8 @@ describe('deletePage', () => {
|
||||||
})
|
})
|
||||||
it('switches the page if you are deleting the current page', () => {
|
it('switches the page if you are deleting the current page', () => {
|
||||||
const page2Id = PageRecordType.createId('page2')
|
const page2Id = PageRecordType.createId('page2')
|
||||||
editor.mark()
|
editor.mark('before creating page')
|
||||||
editor.createPage('New Page 2', page2Id)
|
editor.createPage({ name: 'New Page 2', id: page2Id })
|
||||||
|
|
||||||
const currentPageId = editor.currentPageId
|
const currentPageId = editor.currentPageId
|
||||||
editor.deletePage(currentPageId)
|
editor.deletePage(currentPageId)
|
||||||
|
@ -65,8 +65,8 @@ describe('deletePage', () => {
|
||||||
it('switches the page if another user or tab deletes the current page', () => {
|
it('switches the page if another user or tab deletes the current page', () => {
|
||||||
const currentPageId = editor.currentPageId
|
const currentPageId = editor.currentPageId
|
||||||
const page2Id = PageRecordType.createId('page2')
|
const page2Id = PageRecordType.createId('page2')
|
||||||
editor.mark()
|
editor.mark('before creating')
|
||||||
editor.createPage('New Page 2', page2Id)
|
editor.createPage({ name: 'New Page 2', id: page2Id })
|
||||||
|
|
||||||
editor.store.mergeRemoteChanges(() => {
|
editor.store.mergeRemoteChanges(() => {
|
||||||
editor.store.remove([currentPageId])
|
editor.store.remove([currentPageId])
|
||||||
|
|
|
@ -49,7 +49,7 @@ beforeEach(() => {
|
||||||
describe('Editor.deleteShapes', () => {
|
describe('Editor.deleteShapes', () => {
|
||||||
it('Deletes a shape', () => {
|
it('Deletes a shape', () => {
|
||||||
editor.select(ids.box3, ids.box4)
|
editor.select(ids.box3, ids.box4)
|
||||||
editor.mark()
|
editor.mark('before deleting')
|
||||||
editor.deleteShapes(editor.selectedShapeIds) // delete the selected shapes
|
editor.deleteShapes(editor.selectedShapeIds) // delete the selected shapes
|
||||||
expect(editor.getShape(ids.box3)).toBeUndefined()
|
expect(editor.getShape(ids.box3)).toBeUndefined()
|
||||||
expect(editor.getShape(ids.box4)).toBeUndefined()
|
expect(editor.getShape(ids.box4)).toBeUndefined()
|
||||||
|
@ -74,7 +74,7 @@ describe('Editor.deleteShapes', () => {
|
||||||
it('Deletes descendants', () => {
|
it('Deletes descendants', () => {
|
||||||
editor.reparentShapes([ids.box4], ids.box3)
|
editor.reparentShapes([ids.box4], ids.box3)
|
||||||
editor.select(ids.box3)
|
editor.select(ids.box3)
|
||||||
editor.mark()
|
editor.mark('before deleting')
|
||||||
editor.deleteShapes(editor.selectedShapeIds) // should be a noop, nothing to delete
|
editor.deleteShapes(editor.selectedShapeIds) // should be a noop, nothing to delete
|
||||||
expect(editor.getShape(ids.box3)).toBeUndefined()
|
expect(editor.getShape(ids.box3)).toBeUndefined()
|
||||||
expect(editor.getShape(ids.box4)).toBeUndefined()
|
expect(editor.getShape(ids.box4)).toBeUndefined()
|
||||||
|
@ -90,7 +90,7 @@ describe('Editor.deleteShapes', () => {
|
||||||
describe('When deleting arrows', () => {
|
describe('When deleting arrows', () => {
|
||||||
it('Restores any bindings on undo', () => {
|
it('Restores any bindings on undo', () => {
|
||||||
editor.select(ids.arrow1)
|
editor.select(ids.arrow1)
|
||||||
editor.mark()
|
editor.mark('before deleting')
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
expect(editor._arrowBindingsIndex.value[ids.box1]).not.toBeUndefined()
|
expect(editor._arrowBindingsIndex.value[ids.box1]).not.toBeUndefined()
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
|
|
|
@ -14,14 +14,18 @@ const ids = {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
editor = new TestEditor()
|
editor = new TestEditor()
|
||||||
editor.createPage(ids.page1, ids.page1)
|
const page0Id = editor.currentPageId
|
||||||
|
editor.createPage({ name: ids.page1, id: ids.page1 })
|
||||||
|
expect(editor.currentPageId).toBe(page0Id)
|
||||||
|
editor.setCurrentPage(ids.page1)
|
||||||
|
expect(editor.currentPageId).toBe(ids.page1)
|
||||||
editor.createShapes([
|
editor.createShapes([
|
||||||
{ id: ids.ellipse1, type: 'geo', x: 0, y: 0, props: { geo: 'ellipse' } },
|
{ id: ids.ellipse1, type: 'geo', x: 0, y: 0, props: { geo: 'ellipse' } },
|
||||||
{ id: ids.box1, type: 'geo', x: 0, y: 0 },
|
{ id: ids.box1, type: 'geo', x: 0, y: 0 },
|
||||||
{ id: ids.box2, parentId: ids.box1, type: 'geo', x: 150, y: 150 },
|
{ id: ids.box2, parentId: ids.box1, type: 'geo', x: 150, y: 150 },
|
||||||
])
|
])
|
||||||
editor.createPage(ids.page2, ids.page2)
|
editor.createPage({ name: ids.page2, id: ids.page2 })
|
||||||
editor.setCurrentPage(ids.page1)
|
expect(editor.currentPageId).toBe(ids.page1)
|
||||||
|
|
||||||
expect(editor.getShape(ids.box1)!.parentId).toEqual(ids.page1)
|
expect(editor.getShape(ids.box1)!.parentId).toEqual(ids.page1)
|
||||||
expect(editor.getShape(ids.box2)!.parentId).toEqual(ids.box1)
|
expect(editor.getShape(ids.box2)!.parentId).toEqual(ids.box1)
|
||||||
|
@ -103,7 +107,8 @@ describe('Editor.moveShapesToPage', () => {
|
||||||
editor = new TestEditor()
|
editor = new TestEditor()
|
||||||
const page2Id = PageRecordType.createId('newPage2')
|
const page2Id = PageRecordType.createId('newPage2')
|
||||||
|
|
||||||
editor.createPage('New Page 2', page2Id)
|
editor.createPage({ name: 'New Page 2', id: page2Id })
|
||||||
|
editor.setCurrentPage(page2Id)
|
||||||
expect(editor.currentPageId).toBe(page2Id)
|
expect(editor.currentPageId).toBe(page2Id)
|
||||||
editor.createShapes([{ id: ids.box1, type: 'geo', x: 0, y: 0, props: { geo: 'ellipse' } }])
|
editor.createShapes([{ id: ids.box1, type: 'geo', x: 0, y: 0, props: { geo: 'ellipse' } }])
|
||||||
editor.expectShapeToMatch({
|
editor.expectShapeToMatch({
|
||||||
|
@ -113,7 +118,9 @@ describe('Editor.moveShapesToPage', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
const page3Id = PageRecordType.createId('newPage3')
|
const page3Id = PageRecordType.createId('newPage3')
|
||||||
editor.createPage('New Page 3', page3Id)
|
|
||||||
|
editor.createPage({ name: 'New Page 3', id: page3Id })
|
||||||
|
editor.setCurrentPage(page3Id)
|
||||||
expect(editor.currentPageId).toBe(page3Id)
|
expect(editor.currentPageId).toBe(page3Id)
|
||||||
editor.createShapes([{ id: ids.box2, type: 'geo', x: 0, y: 0, props: { geo: 'ellipse' } }])
|
editor.createShapes([{ id: ids.box2, type: 'geo', x: 0, y: 0, props: { geo: 'ellipse' } }])
|
||||||
editor.expectShapeToMatch({
|
editor.expectShapeToMatch({
|
||||||
|
|
|
@ -39,22 +39,22 @@ function nudgeAndGet(ids: TLShapeId[], key: string, shiftKey: boolean) {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case 'ArrowLeft': {
|
case 'ArrowLeft': {
|
||||||
editor.mark('nudge')
|
editor.mark('nudge')
|
||||||
editor.nudgeShapes(editor.selectedShapeIds, { x: -step, y: 0 }, shiftKey)
|
editor.nudgeShapes(editor.selectedShapeIds, { x: -step, y: 0 })
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'ArrowRight': {
|
case 'ArrowRight': {
|
||||||
editor.mark('nudge')
|
editor.mark('nudge')
|
||||||
editor.nudgeShapes(editor.selectedShapeIds, { x: step, y: 0 }, shiftKey)
|
editor.nudgeShapes(editor.selectedShapeIds, { x: step, y: 0 })
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'ArrowUp': {
|
case 'ArrowUp': {
|
||||||
editor.mark('nudge')
|
editor.mark('nudge')
|
||||||
editor.nudgeShapes(editor.selectedShapeIds, { x: 0, y: -step }, shiftKey)
|
editor.nudgeShapes(editor.selectedShapeIds, { x: 0, y: -step })
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'ArrowDown': {
|
case 'ArrowDown': {
|
||||||
editor.mark('nudge')
|
editor.mark('nudge')
|
||||||
editor.nudgeShapes(editor.selectedShapeIds, { x: 0, y: step }, shiftKey)
|
editor.nudgeShapes(editor.selectedShapeIds, { x: 0, y: step })
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,17 +16,17 @@ describe('Migrations', () => {
|
||||||
const withoutSchema = structuredClone(clipboardContent)
|
const withoutSchema = structuredClone(clipboardContent)
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
delete withoutSchema.schema
|
delete withoutSchema.schema
|
||||||
expect(() => editor.putContent(withoutSchema)).toThrowError()
|
expect(() => editor.putContentOntoCurrentPage(withoutSchema)).toThrowError()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Does not throw error if content has a schema', () => {
|
it('Does not throw error if content has a schema', () => {
|
||||||
expect(() => editor.putContent(clipboardContent)).not.toThrowError()
|
expect(() => editor.putContentOntoCurrentPage(clipboardContent)).not.toThrowError()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Throws error if any shape is invalid due to wrong type', () => {
|
it('Throws error if any shape is invalid due to wrong type', () => {
|
||||||
const withInvalidShapeType = structuredClone(clipboardContent)
|
const withInvalidShapeType = structuredClone(clipboardContent)
|
||||||
withInvalidShapeType.shapes[0].type = 'invalid'
|
withInvalidShapeType.shapes[0].type = 'invalid'
|
||||||
expect(() => editor.putContent(withInvalidShapeType)).toThrowError()
|
expect(() => editor.putContentOntoCurrentPage(withInvalidShapeType)).toThrowError()
|
||||||
})
|
})
|
||||||
|
|
||||||
// we temporarily disabled validations
|
// we temporarily disabled validations
|
||||||
|
@ -34,6 +34,6 @@ describe('Migrations', () => {
|
||||||
const withInvalidShapeModel = structuredClone(clipboardContent)
|
const withInvalidShapeModel = structuredClone(clipboardContent)
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
withInvalidShapeModel.shapes[0].x = 'invalid'
|
withInvalidShapeModel.shapes[0].x = 'invalid'
|
||||||
expect(() => editor.putContent(withInvalidShapeModel)).toThrowError()
|
expect(() => editor.putContentOntoCurrentPage(withInvalidShapeModel)).toThrowError()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -869,7 +869,7 @@ describe('When undoing and redoing...', () => {
|
||||||
ids['G']
|
ids['G']
|
||||||
)
|
)
|
||||||
|
|
||||||
editor.mark()
|
editor.mark('before sending to back')
|
||||||
editor.sendBackward([ids['F'], ids['G']])
|
editor.sendBackward([ids['F'], ids['G']])
|
||||||
|
|
||||||
expectShapesInOrder(
|
expectShapesInOrder(
|
||||||
|
|
|
@ -12,8 +12,12 @@ describe('setCurrentPage', () => {
|
||||||
const page1Id = editor.pages[0].id
|
const page1Id = editor.pages[0].id
|
||||||
const page2Id = PageRecordType.createId('page2')
|
const page2Id = PageRecordType.createId('page2')
|
||||||
|
|
||||||
editor.createPage('New Page 2', page2Id)
|
editor.createPage({ name: 'New Page 2', id: page2Id })
|
||||||
|
expect(editor.currentPageId).toBe(page1Id)
|
||||||
|
|
||||||
|
editor.setCurrentPage(page2Id)
|
||||||
expect(editor.currentPageId).toEqual(page2Id)
|
expect(editor.currentPageId).toEqual(page2Id)
|
||||||
|
|
||||||
expect(editor.currentPage).toEqual(editor.pages[1])
|
expect(editor.currentPage).toEqual(editor.pages[1])
|
||||||
|
|
||||||
editor.setCurrentPage(page1Id)
|
editor.setCurrentPage(page1Id)
|
||||||
|
@ -21,7 +25,9 @@ describe('setCurrentPage', () => {
|
||||||
expect(editor.currentPage).toEqual(editor.pages[0])
|
expect(editor.currentPage).toEqual(editor.pages[0])
|
||||||
|
|
||||||
const page3Id = PageRecordType.createId('page3')
|
const page3Id = PageRecordType.createId('page3')
|
||||||
editor.createPage('New Page 3', page3Id)
|
editor.createPage({ name: 'New Page 3', id: page3Id })
|
||||||
|
expect(editor.currentPageId).toBe(page1Id)
|
||||||
|
editor.setCurrentPage(page3Id)
|
||||||
|
|
||||||
expect(editor.currentPageId).toEqual(page3Id)
|
expect(editor.currentPageId).toEqual(page3Id)
|
||||||
expect(editor.currentPage).toEqual(editor.pages[2])
|
expect(editor.currentPage).toEqual(editor.pages[2])
|
||||||
|
@ -41,7 +47,7 @@ describe('setCurrentPage', () => {
|
||||||
|
|
||||||
it('squashes', () => {
|
it('squashes', () => {
|
||||||
const page2Id = PageRecordType.createId('page2')
|
const page2Id = PageRecordType.createId('page2')
|
||||||
editor.createPage('New Page 2', page2Id)
|
editor.createPage({ name: 'New Page 2', index: page2Id })
|
||||||
|
|
||||||
editor.history.clear()
|
editor.history.clear()
|
||||||
editor.setCurrentPage(editor.pages[1].id)
|
editor.setCurrentPage(editor.pages[1].id)
|
||||||
|
@ -53,7 +59,7 @@ describe('setCurrentPage', () => {
|
||||||
it('preserves the undo stack', () => {
|
it('preserves the undo stack', () => {
|
||||||
const boxId = createShapeId('geo')
|
const boxId = createShapeId('geo')
|
||||||
const page2Id = PageRecordType.createId('page2')
|
const page2Id = PageRecordType.createId('page2')
|
||||||
editor.createPage('New Page 2', page2Id)
|
editor.createPage({ name: 'New Page 2', id: page2Id })
|
||||||
|
|
||||||
editor.history.clear()
|
editor.history.clear()
|
||||||
editor.createShapes([{ type: 'geo', id: boxId, props: { w: 100, h: 100 } }])
|
editor.createShapes([{ type: 'geo', id: boxId, props: { w: 100, h: 100 } }])
|
||||||
|
@ -68,8 +74,8 @@ describe('setCurrentPage', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('logs an error when trying to navigate to a page that does not exist', () => {
|
it('logs an error when trying to navigate to a page that does not exist', () => {
|
||||||
const page2Id = PageRecordType.createId('page2')
|
const initialPageId = editor.currentPageId
|
||||||
editor.createPage('New Page 2', page2Id)
|
expect(editor.currentPageId).toBe(initialPageId)
|
||||||
console.error = jest.fn()
|
console.error = jest.fn()
|
||||||
|
|
||||||
expect(() => {
|
expect(() => {
|
||||||
|
@ -77,6 +83,6 @@ describe('setCurrentPage', () => {
|
||||||
}).not.toThrow()
|
}).not.toThrow()
|
||||||
|
|
||||||
expect(console.error).toHaveBeenCalled()
|
expect(console.error).toHaveBeenCalled()
|
||||||
expect(editor.currentPageId).toEqual(page2Id)
|
expect(editor.currentPageId).toBe(initialPageId)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -52,7 +52,7 @@ describe('shapeIdsInCurrentPage', () => {
|
||||||
{ type: 'geo', id: ids.box3 },
|
{ type: 'geo', id: ids.box3 },
|
||||||
])
|
])
|
||||||
const id = PageRecordType.createId('page2')
|
const id = PageRecordType.createId('page2')
|
||||||
editor.createPage('New Page 2', id)
|
editor.createPage({ name: 'New Page 2', id })
|
||||||
editor.setCurrentPage(id)
|
editor.setCurrentPage(id)
|
||||||
editor.createShapes([
|
editor.createShapes([
|
||||||
{ type: 'geo', id: ids.box4 },
|
{ type: 'geo', id: ids.box4 },
|
||||||
|
|
|
@ -1731,3 +1731,55 @@ describe('When dragging a shape onto a parent', () => {
|
||||||
expect(editor.getShape(ids.box1)?.parentId).toBe(editor.currentPageId)
|
expect(editor.getShape(ids.box1)?.parentId).toBe(editor.currentPageId)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('When dragging shapes', () => {
|
||||||
|
it('should drag and undo and redo', () => {
|
||||||
|
editor.deleteShapes(editor.currentPageShapes)
|
||||||
|
|
||||||
|
editor.setCurrentTool('arrow').pointerMove(0, 0).pointerDown().pointerMove(100, 100).pointerUp()
|
||||||
|
|
||||||
|
editor.expectShapeToMatch({
|
||||||
|
id: editor.currentPageShapes[0]!.id,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
editor.setCurrentTool('geo').pointerMove(-10, 100).pointerDown().pointerUp()
|
||||||
|
|
||||||
|
editor.expectShapeToMatch({
|
||||||
|
id: editor.currentPageShapes[1]!.id,
|
||||||
|
x: -110,
|
||||||
|
y: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
editor
|
||||||
|
.selectAll()
|
||||||
|
.pointerMove(50, 50)
|
||||||
|
.pointerDown()
|
||||||
|
.pointerMove(100, 50)
|
||||||
|
.pointerUp()
|
||||||
|
.expectShapeToMatch({
|
||||||
|
id: editor.currentPageShapes[0]!.id,
|
||||||
|
x: 50, // 50 to the right
|
||||||
|
y: 0,
|
||||||
|
})
|
||||||
|
.expectShapeToMatch({
|
||||||
|
id: editor.currentPageShapes[1]!.id,
|
||||||
|
x: -60, // 50 to the right
|
||||||
|
y: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
editor
|
||||||
|
.undo()
|
||||||
|
.expectShapeToMatch({
|
||||||
|
id: editor.currentPageShapes[0]!.id,
|
||||||
|
x: 0, // 50 to the right
|
||||||
|
y: 0,
|
||||||
|
})
|
||||||
|
.expectShapeToMatch({
|
||||||
|
id: editor.currentPageShapes[1]!.id,
|
||||||
|
x: -110, // 50 to the right
|
||||||
|
y: 0,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
18
packages/tldraw/src/test/viewport-following.test.ts
Normal file
18
packages/tldraw/src/test/viewport-following.test.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { TestEditor } from './TestEditor'
|
||||||
|
|
||||||
|
let editor: TestEditor
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
editor = new TestEditor()
|
||||||
|
editor
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('When following a user', () => {
|
||||||
|
it.todo('starts following a user')
|
||||||
|
it.todo('stops following a user')
|
||||||
|
it.todo('stops following a user when the camera changes due to user action')
|
||||||
|
it.todo('moves the camera to follow the user without unfollowing them')
|
||||||
|
it.todo('stops any animations while following')
|
||||||
|
it.todo('stops following a user when the page changes due to user action')
|
||||||
|
it.todo('follows a user to another page without unfollowing them')
|
||||||
|
})
|
Loading…
Reference in a new issue