[refactor] Remove TLShapeDef
, getShapeUtilByType
. (#1432)
This PR removes `TLShapeDef` and associated helpers / references. It purposely loosens the configuration and typings to better support customization. ### Change Type - [x] `major` — Breaking Change ### Test Plan 1. Use the app! ### Release Notes - [tlschema] Update props of `createTLSchema` - [editor] Update props of `TldrawEditorConfig` - [editor] Remove `App.getShapeUtilByType` - [editor] Update `App.getShapeUtil` to take a type rather than a shape --------- Co-authored-by: alex <alex@dytry.ch>
This commit is contained in:
parent
3ce18c0c31
commit
649125cdad
55 changed files with 527 additions and 690 deletions
|
@ -1,5 +1,4 @@
|
|||
import {
|
||||
defineShape,
|
||||
HTMLContainer,
|
||||
MenuGroup,
|
||||
menuItem,
|
||||
|
@ -30,23 +29,13 @@ type CardShape = TLBaseShape<
|
|||
}
|
||||
>
|
||||
|
||||
// Shape Definition
|
||||
// ----------------
|
||||
// The shape definition is used to tell TypeScript about the shape
|
||||
// and to register the shape with the app.
|
||||
export const CardShape = defineShape<CardShape>({
|
||||
type: 'card',
|
||||
getShapeUtil: () => CardUtil,
|
||||
// validator: createShapeValidator({ ... })
|
||||
})
|
||||
|
||||
// Shape Util
|
||||
// ----------
|
||||
// The CardUtil class is used by the app to answer questions about a
|
||||
// shape of the 'card' type. For example, what is the default props
|
||||
// for this shape? What should we render for it, or for its indicator?
|
||||
class CardUtil extends TLBoxUtil<CardShape> {
|
||||
static type = 'card'
|
||||
static override type = 'card' as const
|
||||
|
||||
// There are a LOT of other things we could add here, like these flags
|
||||
override isAspectRatioLocked = (_shape: CardShape) => false
|
||||
|
@ -105,8 +94,11 @@ export class CardTool extends TLBoxTool {
|
|||
// Finally, collect the custom tools and shapes into a config object
|
||||
const customTldrawConfig = new TldrawEditorConfig({
|
||||
tools: [CardTool],
|
||||
shapes: [CardShape],
|
||||
allowUnknownShapes: true,
|
||||
shapes: {
|
||||
card: {
|
||||
util: CardUtil,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// ... and we can make our custom shape example!
|
||||
|
|
|
@ -1,11 +1,4 @@
|
|||
import {
|
||||
createShapeId,
|
||||
defineShape,
|
||||
TLBaseShape,
|
||||
TLBoxUtil,
|
||||
Tldraw,
|
||||
TldrawEditorConfig,
|
||||
} from '@tldraw/tldraw'
|
||||
import { createShapeId, TLBaseShape, TLBoxUtil, Tldraw, TldrawEditorConfig } from '@tldraw/tldraw'
|
||||
import '@tldraw/tldraw/editor.css'
|
||||
import '@tldraw/tldraw/ui.css'
|
||||
|
||||
|
@ -13,7 +6,6 @@ export default function ErrorBoundaryExample() {
|
|||
return (
|
||||
<div className="tldraw__editor">
|
||||
<Tldraw
|
||||
persistenceKey="error-boundary-example"
|
||||
components={{
|
||||
// disable app-level error boundaries:
|
||||
ErrorFallback: null,
|
||||
|
@ -34,6 +26,10 @@ export default function ErrorBoundaryExample() {
|
|||
props: { message: 'Something has gone wrong' },
|
||||
},
|
||||
])
|
||||
|
||||
// center the camera on the error shape
|
||||
app.zoomToFit()
|
||||
app.resetZoom()
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
@ -44,24 +40,25 @@ export default function ErrorBoundaryExample() {
|
|||
// shape type that always throws an error. See CustomConfigExample for more info
|
||||
// on creating custom shapes.
|
||||
type ErrorShape = TLBaseShape<'error', { w: number; h: number; message: string }>
|
||||
const ErrorShape = defineShape<ErrorShape>({
|
||||
type: 'error',
|
||||
getShapeUtil: () =>
|
||||
class ErrorShapeUtil extends TLBoxUtil<ErrorShape> {
|
||||
static type = 'error'
|
||||
defaultProps() {
|
||||
return { message: 'Error!', w: 100, h: 100 }
|
||||
}
|
||||
render(shape: ErrorShape) {
|
||||
throw new Error(shape.props.message)
|
||||
}
|
||||
indicator() {
|
||||
throw new Error(`Error shape indicator!`)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
class ErrorUtil extends TLBoxUtil<ErrorShape> {
|
||||
override type = 'error' as const
|
||||
|
||||
defaultProps() {
|
||||
return { message: 'Error!', w: 100, h: 100 }
|
||||
}
|
||||
render(shape: ErrorShape) {
|
||||
throw new Error(shape.props.message)
|
||||
}
|
||||
indicator() {
|
||||
throw new Error(`Error shape indicator!`)
|
||||
}
|
||||
}
|
||||
|
||||
const customConfigWithErrorShape = new TldrawEditorConfig({
|
||||
shapes: [ErrorShape],
|
||||
allowUnknownShapes: true,
|
||||
shapes: {
|
||||
error: {
|
||||
util: ErrorUtil,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
|
@ -30,6 +30,7 @@ import { MatLike } from '@tldraw/primitives';
|
|||
import { Matrix2d } from '@tldraw/primitives';
|
||||
import { Matrix2dModel } from '@tldraw/primitives';
|
||||
import { Migrations } from '@tldraw/tlstore';
|
||||
import { MigrationsForShapes } from '@tldraw/tlschema';
|
||||
import { Polyline2d } from '@tldraw/primitives';
|
||||
import * as React_2 from 'react';
|
||||
import { default as React_3 } from 'react';
|
||||
|
@ -43,7 +44,6 @@ import { Signal } from 'signia';
|
|||
import { sortByIndex } from '@tldraw/indices';
|
||||
import { StoreSchema } from '@tldraw/tlstore';
|
||||
import { StoreSnapshot } from '@tldraw/tlstore';
|
||||
import { StoreValidator } from '@tldraw/tlstore';
|
||||
import { StrokePoint } from '@tldraw/primitives';
|
||||
import { TLAlignType } from '@tldraw/tlschema';
|
||||
import { TLArrowheadType } from '@tldraw/tlschema';
|
||||
|
@ -103,6 +103,7 @@ import { TLUserId } from '@tldraw/tlschema';
|
|||
import { TLUserPresence } from '@tldraw/tlschema';
|
||||
import { TLVideoAsset } from '@tldraw/tlschema';
|
||||
import { TLVideoShape } from '@tldraw/tlschema';
|
||||
import { ValidatorsForShapes } from '@tldraw/tlschema';
|
||||
import { Vec2d } from '@tldraw/primitives';
|
||||
import { Vec2dModel } from '@tldraw/tlschema';
|
||||
import { VecLike } from '@tldraw/primitives';
|
||||
|
@ -286,8 +287,11 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
getShapesAndDescendantsInOrder(ids: TLShapeId[]): TLShape[];
|
||||
getShapesAtPoint(point: VecLike): TLShape[];
|
||||
getShapesInPage(pageId: TLPageId): TLShape[];
|
||||
getShapeUtil<T extends TLShape = TLShape>(shape: T): TLShapeUtil<T>;
|
||||
getShapeUtilByDef<Def extends TLShapeDef<any, any>>(def: Def): ReturnType<Def['createShapeUtils']>;
|
||||
getShapeUtil<C extends {
|
||||
new (...args: any[]): TLShapeUtil<any>;
|
||||
type: string;
|
||||
}>(util: C): InstanceType<C>;
|
||||
getShapeUtil<S extends TLUnknownShape>(shape: S | TLShapePartial<S>): TLShapeUtil<S>;
|
||||
getSortedChildIds(parentId: TLParentId): TLShapeId[];
|
||||
getStateDescendant(path: string): StateNode | undefined;
|
||||
getStrokeWidth(id: TLSizeStyle['id']): number;
|
||||
|
@ -359,6 +363,10 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
isSelected(id: TLShapeId): boolean;
|
||||
isShapeInPage(shape: TLShape, pageId?: TLPageId): boolean;
|
||||
isShapeInViewport(id: TLShapeId): boolean;
|
||||
isShapeOfType<T extends TLUnknownShape>(shape: TLUnknownShape, util: {
|
||||
new (...args: any): TLShapeUtil<T>;
|
||||
type: string;
|
||||
}): shape is T;
|
||||
// (undocumented)
|
||||
get isSnapMode(): boolean;
|
||||
// (undocumented)
|
||||
|
@ -375,7 +383,7 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
title: string;
|
||||
description: string;
|
||||
}>;
|
||||
get onlySelectedShape(): TLBaseShape<any, any> | null;
|
||||
get onlySelectedShape(): null | TLShape;
|
||||
get openMenus(): string[];
|
||||
packShapes(ids?: TLShapeId[], padding?: number): this;
|
||||
get pages(): TLPage[];
|
||||
|
@ -436,7 +444,7 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
get selectedIds(): TLShapeId[];
|
||||
get selectedIdsSet(): ReadonlySet<TLShapeId>;
|
||||
get selectedPageBounds(): Box2d | null;
|
||||
get selectedShapes(): TLBaseShape<any, any>[];
|
||||
get selectedShapes(): TLShape[];
|
||||
// (undocumented)
|
||||
get selectionBounds(): Box2d | undefined;
|
||||
// (undocumented)
|
||||
|
@ -652,14 +660,6 @@ export function defaultEmptyAs(str: string, dflt: string): string;
|
|||
// @internal (undocumented)
|
||||
export const DefaultErrorFallback: TLErrorFallback;
|
||||
|
||||
// @public (undocumented)
|
||||
export function defineShape<ShapeType extends TLUnknownShape, ShapeUtil extends TLShapeUtil<ShapeType> = TLShapeUtil<ShapeType>>({ type, getShapeUtil, validator, migrations, }: {
|
||||
type: ShapeType['type'];
|
||||
getShapeUtil: () => TLShapeUtilConstructor<ShapeType, ShapeUtil>;
|
||||
validator?: StoreValidator<ShapeType>;
|
||||
migrations?: Migrations;
|
||||
}): TLShapeDef<ShapeType, ShapeUtil>;
|
||||
|
||||
// @internal (undocumented)
|
||||
export const DOUBLE_CLICK_DURATION = 450;
|
||||
|
||||
|
@ -805,7 +805,7 @@ export function getRotationSnapshot({ app }: {
|
|||
initialCursorAngle: number;
|
||||
initialSelectionRotation: number;
|
||||
shapeSnapshots: {
|
||||
shape: TLBaseShape<any, any>;
|
||||
shape: TLShape;
|
||||
initialPagePoint: Vec2d;
|
||||
}[];
|
||||
};
|
||||
|
@ -1580,9 +1580,6 @@ export const TEXT_PROPS: {
|
|||
maxWidth: string;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const TLArrowShapeDef: TLShapeDef<TLArrowShape, TLArrowUtil>;
|
||||
|
||||
// @public (undocumented)
|
||||
export class TLArrowUtil extends TLShapeUtil<TLArrowShape> {
|
||||
// (undocumented)
|
||||
|
@ -1657,9 +1654,6 @@ export interface TLBaseEventInfo {
|
|||
type: UiEventType;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const TLBookmarkShapeDef: TLShapeDef<TLBookmarkShape, TLBookmarkUtil>;
|
||||
|
||||
// @public (undocumented)
|
||||
export class TLBookmarkUtil extends TLBoxUtil<TLBookmarkShape> {
|
||||
// (undocumented)
|
||||
|
@ -1799,12 +1793,7 @@ export function TldrawEditor(props: TldrawEditorProps): JSX.Element;
|
|||
|
||||
// @public (undocumented)
|
||||
export class TldrawEditorConfig {
|
||||
constructor(args: {
|
||||
shapes?: readonly TLShapeDef<any, any>[];
|
||||
tools?: readonly StateNodeConstructor[];
|
||||
allowUnknownShapes?: boolean;
|
||||
derivePresenceState?: (store: TLStore) => Signal<null | TLInstancePresence>;
|
||||
});
|
||||
constructor(opts: TldrawEditorConfigOptions);
|
||||
// (undocumented)
|
||||
createStore(config: {
|
||||
initialData?: StoreSnapshot<TLRecord>;
|
||||
|
@ -1814,7 +1803,11 @@ export class TldrawEditorConfig {
|
|||
// (undocumented)
|
||||
static readonly default: TldrawEditorConfig;
|
||||
// (undocumented)
|
||||
readonly shapes: readonly TLUnknownShapeDef[];
|
||||
readonly shapeMigrations: MigrationsForShapes<TLShape>;
|
||||
// (undocumented)
|
||||
readonly shapeUtils: UtilsForShapes<TLShape>;
|
||||
// (undocumented)
|
||||
readonly shapeValidators: ValidatorsForShapes<TLShape>;
|
||||
// (undocumented)
|
||||
readonly storeSchema: StoreSchema<TLRecord, TLStoreProps>;
|
||||
// (undocumented)
|
||||
|
@ -1844,9 +1837,6 @@ export interface TldrawEditorProps {
|
|||
userId?: TLUserId;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const TLDrawShapeDef: TLShapeDef<TLDrawShape, TLDrawUtil>;
|
||||
|
||||
// @public (undocumented)
|
||||
export class TLDrawUtil extends TLShapeUtil<TLDrawShape> {
|
||||
// (undocumented)
|
||||
|
@ -1928,9 +1918,6 @@ export interface TLEditorComponents {
|
|||
ZoomBrush: null | TLBrushComponent;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const TLEmbedShapeDef: TLShapeDef<TLEmbedShape, TLEmbedUtil>;
|
||||
|
||||
// @public (undocumented)
|
||||
export class TLEmbedUtil extends TLBoxUtil<TLEmbedShape> {
|
||||
// (undocumented)
|
||||
|
@ -2048,9 +2035,6 @@ export type TLEventName = 'cancel' | 'complete' | 'interrupt' | 'wheel' | TLCLic
|
|||
// @public (undocumented)
|
||||
export type TLExportType = 'jpeg' | 'json' | 'png' | 'svg' | 'webp';
|
||||
|
||||
// @public (undocumented)
|
||||
export const TLFrameShapeDef: TLShapeDef<TLFrameShape, TLFrameUtil>;
|
||||
|
||||
// @public (undocumented)
|
||||
export class TLFrameUtil extends TLBoxUtil<TLFrameShape> {
|
||||
// (undocumented)
|
||||
|
@ -2081,9 +2065,6 @@ export class TLFrameUtil extends TLBoxUtil<TLFrameShape> {
|
|||
static type: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const TLGeoShapeDef: TLShapeDef<TLGeoShape, TLGeoUtil>;
|
||||
|
||||
// @public (undocumented)
|
||||
export class TLGeoUtil extends TLBoxUtil<TLGeoShape> {
|
||||
// (undocumented)
|
||||
|
@ -2200,9 +2181,6 @@ export class TLGeoUtil extends TLBoxUtil<TLGeoShape> {
|
|||
static type: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const TLGroupShapeDef: TLShapeDef<TLGroupShape, TLGroupUtil>;
|
||||
|
||||
// @public (undocumented)
|
||||
export class TLGroupUtil extends TLShapeUtil<TLGroupShape> {
|
||||
// (undocumented)
|
||||
|
@ -2232,9 +2210,6 @@ export class TLGroupUtil extends TLShapeUtil<TLGroupShape> {
|
|||
// @public (undocumented)
|
||||
export type TLHistoryEntry = TLCommand | TLMark;
|
||||
|
||||
// @public (undocumented)
|
||||
export const TLImageShapeDef: TLShapeDef<TLImageShape, TLImageUtil>;
|
||||
|
||||
// @public (undocumented)
|
||||
export class TLImageUtil extends TLBoxUtil<TLImageShape> {
|
||||
// (undocumented)
|
||||
|
@ -2280,9 +2255,6 @@ export type TLKeyboardEventInfo = TLBaseEventInfo & {
|
|||
// @public (undocumented)
|
||||
export type TLKeyboardEventName = 'key_down' | 'key_repeat' | 'key_up';
|
||||
|
||||
// @public (undocumented)
|
||||
export const TLLineShapeDef: TLShapeDef<TLLineShape, TLLineUtil>;
|
||||
|
||||
// @public (undocumented)
|
||||
export class TLLineUtil extends TLShapeUtil<TLLineShape> {
|
||||
// (undocumented)
|
||||
|
@ -2331,9 +2303,6 @@ export type TLMark = {
|
|||
onRedo: boolean;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const TLNoteShapeDef: TLShapeDef<TLNoteShape, TLNoteUtil>;
|
||||
|
||||
// @public (undocumented)
|
||||
export class TLNoteUtil extends TLShapeUtil<TLNoteShape> {
|
||||
// (undocumented)
|
||||
|
@ -2478,21 +2447,7 @@ export type TLResizeMode = 'resize_bounds' | 'scale_shape';
|
|||
export type TLSelectionHandle = RotateCorner | SelectionCorner | SelectionEdge;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface TLShapeDef<ShapeType extends TLUnknownShape, ShapeUtil extends TLShapeUtil<ShapeType> = TLShapeUtil<ShapeType>> {
|
||||
// (undocumented)
|
||||
readonly createShapeUtils: (app: App) => ShapeUtil;
|
||||
// (undocumented)
|
||||
readonly is: (shape: TLUnknownShape) => shape is ShapeType;
|
||||
// (undocumented)
|
||||
readonly migrations: Migrations;
|
||||
// (undocumented)
|
||||
readonly type: ShapeType['type'];
|
||||
// (undocumented)
|
||||
readonly validator?: StoreValidator<ShapeType>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export abstract class TLShapeUtil<T extends TLUnknownShape> {
|
||||
export abstract class TLShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
||||
constructor(app: App, type: T['type']);
|
||||
// (undocumented)
|
||||
app: App;
|
||||
|
@ -2558,6 +2513,8 @@ export abstract class TLShapeUtil<T extends TLUnknownShape> {
|
|||
transform(shape: T): Matrix2d;
|
||||
// (undocumented)
|
||||
readonly type: T['type'];
|
||||
// (undocumented)
|
||||
static type: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
|
@ -2569,9 +2526,6 @@ export interface TLShapeUtilConstructor<T extends TLUnknownShape, ShapeUtil exte
|
|||
// @public (undocumented)
|
||||
export type TLShapeUtilFlag<T> = (shape: T) => boolean;
|
||||
|
||||
// @public (undocumented)
|
||||
export const TLTextShapeDef: TLShapeDef<TLTextShape, TLTextUtil>;
|
||||
|
||||
// @public (undocumented)
|
||||
export class TLTextUtil extends TLShapeUtil<TLTextShape> {
|
||||
// (undocumented)
|
||||
|
@ -2660,12 +2614,6 @@ export class TLTextUtil extends TLShapeUtil<TLTextShape> {
|
|||
// @public (undocumented)
|
||||
export type TLTickEvent = (elapsed: number) => void;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLUnknownShapeDef = TLShapeDef<TLUnknownShape, TLShapeUtil<TLUnknownShape>>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const TLVideoShapeDef: TLShapeDef<TLVideoShape, TLVideoUtil>;
|
||||
|
||||
// @public (undocumented)
|
||||
export class TLVideoUtil extends TLBoxUtil<TLVideoShape> {
|
||||
// (undocumented)
|
||||
|
|
|
@ -27,24 +27,17 @@ export {
|
|||
type AppOptions,
|
||||
type TLChange,
|
||||
} from './lib/app/App'
|
||||
export { TLArrowShapeDef, TLArrowUtil } from './lib/app/shapeutils/TLArrowUtil/TLArrowUtil'
|
||||
export {
|
||||
TLBookmarkShapeDef,
|
||||
TLBookmarkUtil,
|
||||
} from './lib/app/shapeutils/TLBookmarkUtil/TLBookmarkUtil'
|
||||
export { TLArrowUtil } from './lib/app/shapeutils/TLArrowUtil/TLArrowUtil'
|
||||
export { TLBookmarkUtil } from './lib/app/shapeutils/TLBookmarkUtil/TLBookmarkUtil'
|
||||
export { TLBoxUtil } from './lib/app/shapeutils/TLBoxUtil'
|
||||
export { TLDrawShapeDef, TLDrawUtil } from './lib/app/shapeutils/TLDrawUtil/TLDrawUtil'
|
||||
export { TLEmbedShapeDef, TLEmbedUtil } from './lib/app/shapeutils/TLEmbedUtil/TLEmbedUtil'
|
||||
export { TLFrameShapeDef, TLFrameUtil } from './lib/app/shapeutils/TLFrameUtil/TLFrameUtil'
|
||||
export { TLGeoShapeDef, TLGeoUtil } from './lib/app/shapeutils/TLGeoUtil/TLGeoUtil'
|
||||
export { TLGroupShapeDef, TLGroupUtil } from './lib/app/shapeutils/TLGroupUtil/TLGroupUtil'
|
||||
export { TLImageShapeDef, TLImageUtil } from './lib/app/shapeutils/TLImageUtil/TLImageUtil'
|
||||
export {
|
||||
TLLineShapeDef,
|
||||
TLLineUtil,
|
||||
getSplineForLineShape,
|
||||
} from './lib/app/shapeutils/TLLineUtil/TLLineUtil'
|
||||
export { TLNoteShapeDef, TLNoteUtil } from './lib/app/shapeutils/TLNoteUtil/TLNoteUtil'
|
||||
export { TLDrawUtil } from './lib/app/shapeutils/TLDrawUtil/TLDrawUtil'
|
||||
export { TLEmbedUtil } from './lib/app/shapeutils/TLEmbedUtil/TLEmbedUtil'
|
||||
export { TLFrameUtil } from './lib/app/shapeutils/TLFrameUtil/TLFrameUtil'
|
||||
export { TLGeoUtil } from './lib/app/shapeutils/TLGeoUtil/TLGeoUtil'
|
||||
export { TLGroupUtil } from './lib/app/shapeutils/TLGroupUtil/TLGroupUtil'
|
||||
export { TLImageUtil } from './lib/app/shapeutils/TLImageUtil/TLImageUtil'
|
||||
export { TLLineUtil, getSplineForLineShape } from './lib/app/shapeutils/TLLineUtil/TLLineUtil'
|
||||
export { TLNoteUtil } from './lib/app/shapeutils/TLNoteUtil/TLNoteUtil'
|
||||
export {
|
||||
TLShapeUtil,
|
||||
type OnBeforeCreateHandler,
|
||||
|
@ -71,8 +64,8 @@ export {
|
|||
type TLShapeUtilConstructor,
|
||||
type TLShapeUtilFlag,
|
||||
} from './lib/app/shapeutils/TLShapeUtil'
|
||||
export { INDENT, TLTextShapeDef, TLTextUtil } from './lib/app/shapeutils/TLTextUtil/TLTextUtil'
|
||||
export { TLVideoShapeDef, TLVideoUtil } from './lib/app/shapeutils/TLVideoUtil/TLVideoUtil'
|
||||
export { INDENT, TLTextUtil } from './lib/app/shapeutils/TLTextUtil/TLTextUtil'
|
||||
export { TLVideoUtil } from './lib/app/shapeutils/TLVideoUtil/TLVideoUtil'
|
||||
export { StateNode, type StateNodeConstructor } from './lib/app/statechart/StateNode'
|
||||
export { TLBoxTool, type TLBoxLike } from './lib/app/statechart/TLBoxTool/TLBoxTool'
|
||||
export { type ClipboardPayload, type TLClipboardModel } from './lib/app/types/clipboard-types'
|
||||
|
@ -138,11 +131,6 @@ export {
|
|||
type ReadySyncedStore,
|
||||
type SyncedStore,
|
||||
} from './lib/config/SyncedStore'
|
||||
export {
|
||||
defineShape,
|
||||
type TLShapeDef,
|
||||
type TLUnknownShapeDef,
|
||||
} from './lib/config/TLShapeDefinition'
|
||||
export { TldrawEditorConfig } from './lib/config/TldrawEditorConfig'
|
||||
export {
|
||||
ANIMATION_MEDIUM_MS,
|
||||
|
|
|
@ -76,7 +76,6 @@ import {
|
|||
import { EventEmitter } from 'eventemitter3'
|
||||
import { nanoid } from 'nanoid'
|
||||
import { EMPTY_ARRAY, atom, computed, transact } from 'signia'
|
||||
import { TLShapeDef } from '../config/TLShapeDefinition'
|
||||
import { TldrawEditorConfig } from '../config/TldrawEditorConfig'
|
||||
import {
|
||||
ANIMATION_MEDIUM_MS,
|
||||
|
@ -118,17 +117,17 @@ import { HistoryManager } from './managers/HistoryManager'
|
|||
import { SnapManager } from './managers/SnapManager'
|
||||
import { TextManager } from './managers/TextManager'
|
||||
import { TickManager } from './managers/TickManager'
|
||||
import { TLArrowShapeDef } from './shapeutils/TLArrowUtil/TLArrowUtil'
|
||||
import { TLArrowUtil } from './shapeutils/TLArrowUtil/TLArrowUtil'
|
||||
import { getCurvedArrowInfo } from './shapeutils/TLArrowUtil/arrow/curved-arrow'
|
||||
import {
|
||||
getArrowTerminalsInArrowSpace,
|
||||
getIsArrowStraight,
|
||||
} from './shapeutils/TLArrowUtil/arrow/shared'
|
||||
import { getStraightArrowInfo } from './shapeutils/TLArrowUtil/arrow/straight-arrow'
|
||||
import { TLFrameShapeDef } from './shapeutils/TLFrameUtil/TLFrameUtil'
|
||||
import { TLGroupShapeDef } from './shapeutils/TLGroupUtil/TLGroupUtil'
|
||||
import { TLFrameUtil } from './shapeutils/TLFrameUtil/TLFrameUtil'
|
||||
import { TLGroupUtil } from './shapeutils/TLGroupUtil/TLGroupUtil'
|
||||
import { TLResizeMode, TLShapeUtil } from './shapeutils/TLShapeUtil'
|
||||
import { TLTextShapeDef } from './shapeutils/TLTextUtil/TLTextUtil'
|
||||
import { TLTextUtil } from './shapeutils/TLTextUtil/TLTextUtil'
|
||||
import { TLExportColors } from './shapeutils/shared/TLExportColors'
|
||||
import { RootState } from './statechart/RootState'
|
||||
import { StateNode } from './statechart/StateNode'
|
||||
|
@ -192,10 +191,7 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
|
||||
// Set the shape utils
|
||||
this.shapeUtils = Object.fromEntries(
|
||||
config.shapes.map((def) => [
|
||||
def.type,
|
||||
def.createShapeUtils(this) as TLShapeUtil<TLUnknownShape>,
|
||||
])
|
||||
Object.entries(config.shapeUtils).map(([type, Util]) => [type, new Util(this, type)])
|
||||
)
|
||||
|
||||
if (typeof window !== 'undefined' && 'navigator' in window) {
|
||||
|
@ -243,7 +239,7 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
this._updateDepth--
|
||||
}
|
||||
this.store.onAfterCreate = (record) => {
|
||||
if (record.typeName === 'shape' && TLArrowShapeDef.is(record)) {
|
||||
if (record.typeName === 'shape' && this.isShapeOfType(record, TLArrowUtil)) {
|
||||
this._arrowDidUpdate(record)
|
||||
}
|
||||
}
|
||||
|
@ -934,38 +930,42 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
*/
|
||||
shapeUtils: { readonly [K in string]?: TLShapeUtil<TLUnknownShape> }
|
||||
|
||||
/**
|
||||
* Get a shape util for a given shape or shape type.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* app.getShapeUtil(myBoxShape)
|
||||
* ```
|
||||
*
|
||||
* @param type - The shape type.
|
||||
* @public
|
||||
*/
|
||||
getShapeUtil<T extends TLShape = TLShape>(shape: T): TLShapeUtil<T> {
|
||||
return this.shapeUtils[shape.type] as any as TLShapeUtil<T>
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a shape util by its definition.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* app.getShapeUtilByDef(TLDrawShapeDef)
|
||||
* app.getShapeUtil(TLArrowUtil)
|
||||
* ```
|
||||
*
|
||||
* @param def - The shape definition.
|
||||
* @param util - The shape util.
|
||||
* @public
|
||||
*/
|
||||
getShapeUtilByDef<Def extends TLShapeDef<any, any>>(
|
||||
def: Def
|
||||
): ReturnType<Def['createShapeUtils']> {
|
||||
return this.shapeUtils[def.type] as ReturnType<Def['createShapeUtils']>
|
||||
getShapeUtil<C extends { new (...args: any[]): TLShapeUtil<any>; type: string }>(
|
||||
util: C
|
||||
): InstanceType<C>
|
||||
/**
|
||||
* Get a shape util from a shape itself.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* const util = app.getShapeUtil(myShape)
|
||||
* const util = app.getShapeUtil<TLArrowUtil>(myShape)
|
||||
* const util = app.getShapeUtil(TLArrowUtil)
|
||||
* ```
|
||||
*
|
||||
* @param shape - A shape or shape partial.
|
||||
* @public
|
||||
*/
|
||||
getShapeUtil<S extends TLUnknownShape>(shape: S | TLShapePartial<S>): TLShapeUtil<S>
|
||||
getShapeUtil<T extends TLShapeUtil>({
|
||||
type,
|
||||
}: {
|
||||
type: T extends TLShapeUtil<infer R> ? R['type'] : string
|
||||
}): T {
|
||||
return this.shapeUtils[type] as T
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1183,6 +1183,7 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
},
|
||||
])
|
||||
}
|
||||
|
||||
for (const parentId of this._invalidParents) {
|
||||
this._invalidParents.delete(parentId)
|
||||
const parent = this.getShapeById(parentId)
|
||||
|
@ -1213,7 +1214,7 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
|
||||
/** @internal */
|
||||
private _reparentArrow(arrowId: TLShapeId) {
|
||||
const arrow = this.getShapeById(arrowId) as TLArrowShape | undefined
|
||||
const arrow = this.getShapeById<TLArrowShape>(arrowId)
|
||||
if (!arrow) return
|
||||
const { start, end } = arrow.props
|
||||
const startShape = start.type === 'binding' ? this.getShapeById(start.boundShapeId) : undefined
|
||||
|
@ -1237,7 +1238,8 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
this.reparentShapesById([arrowId], nextParentId)
|
||||
}
|
||||
|
||||
const reparentedArrow = this.getShapeById(arrowId) as TLArrowShape
|
||||
const reparentedArrow = this.getShapeById<TLArrowShape>(arrowId)
|
||||
if (!reparentedArrow) throw Error('no reparented arrow')
|
||||
|
||||
const startSibling = this.getNearestSiblingShape(reparentedArrow, startShape)
|
||||
const endSibling = this.getNearestSiblingShape(reparentedArrow, endShape)
|
||||
|
@ -1303,6 +1305,7 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
// const update = this.getShapeUtil(next).onUpdate?.(prev, next)
|
||||
// return update ?? next
|
||||
// }
|
||||
|
||||
@computed
|
||||
private get _allPageStates() {
|
||||
return this.store.query.records('instance_page_state')
|
||||
|
@ -1400,7 +1403,7 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
|
||||
/** @internal */
|
||||
private _shapeDidChange(prev: TLShape, next: TLShape) {
|
||||
if (TLArrowShapeDef.is(next)) {
|
||||
if (this.isShapeOfType(next, TLArrowUtil)) {
|
||||
this._arrowDidUpdate(next)
|
||||
}
|
||||
|
||||
|
@ -3019,8 +3022,8 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
* @readonly
|
||||
* @public
|
||||
*/
|
||||
@computed get shapesArray() {
|
||||
return Array.from(this.shapeIds).map((id) => this.store.get(id)! as TLShape)
|
||||
@computed get shapesArray(): TLShape[] {
|
||||
return Array.from(this.shapeIds).map((id) => this.store.get(id)!)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -3074,7 +3077,7 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
* @public
|
||||
* @readonly
|
||||
*/
|
||||
@computed get selectedShapes() {
|
||||
@computed get selectedShapes(): TLShape[] {
|
||||
const { selectedIds } = this.pageState
|
||||
return compact(selectedIds.map((id) => this.store.get(id)))
|
||||
}
|
||||
|
@ -3093,11 +3096,32 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
* @public
|
||||
* @readonly
|
||||
*/
|
||||
@computed get onlySelectedShape() {
|
||||
@computed get onlySelectedShape(): TLShape | null {
|
||||
const { selectedShapes } = this
|
||||
return selectedShapes.length === 1 ? selectedShapes[0] : null
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether a shape matches the type of a TLShapeUtil.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* const isArrowShape = isShapeOfType(someShape, TLArrowUtil)
|
||||
* ```
|
||||
*
|
||||
* @param util - the TLShapeUtil constructor to test against
|
||||
* @param shape - the shape to test
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
isShapeOfType<T extends TLUnknownShape>(
|
||||
shape: TLUnknownShape,
|
||||
util: { new (...args: any): TLShapeUtil<T>; type: string }
|
||||
): shape is T {
|
||||
return shape.type === util.type
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a shape by its id.
|
||||
*
|
||||
|
@ -4024,12 +4048,12 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
|
||||
let shapes = dedupe(
|
||||
ids
|
||||
.map((id) => this.getShapeById(id) as TLShape)
|
||||
.map((id) => this.getShapeById(id)!)
|
||||
.sort(sortByIndex)
|
||||
.flatMap((shape) => {
|
||||
const allShapes = [shape]
|
||||
this.visitDescendants(shape.id, (descendant) => {
|
||||
allShapes.push(this.getShapeById(descendant) as TLShape)
|
||||
allShapes.push(this.getShapeById(descendant)!)
|
||||
})
|
||||
return allShapes
|
||||
})
|
||||
|
@ -4040,14 +4064,14 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
|
||||
shape = structuredClone(shape) as typeof shape
|
||||
|
||||
if (TLArrowShapeDef.is(shape)) {
|
||||
if (this.isShapeOfType(shape, TLArrowUtil)) {
|
||||
const startBindingId =
|
||||
shape.props.start.type === 'binding' ? shape.props.start.boundShapeId : undefined
|
||||
|
||||
const endBindingId =
|
||||
shape.props.end.type === 'binding' ? shape.props.end.boundShapeId : undefined
|
||||
|
||||
const info = this.getShapeUtilByDef(TLArrowShapeDef).getArrowInfo(shape)
|
||||
const info = this.getShapeUtil(TLArrowUtil).getArrowInfo(shape)
|
||||
|
||||
if (shape.props.start.type === 'binding') {
|
||||
if (!shapes.some((s) => s.id === startBindingId)) {
|
||||
|
@ -4223,8 +4247,8 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
if (rootShapeIds.length === 1) {
|
||||
const rootShape = shapes.find((s) => s.id === rootShapeIds[0])!
|
||||
if (
|
||||
TLFrameShapeDef.is(parent) &&
|
||||
TLFrameShapeDef.is(rootShape) &&
|
||||
this.isShapeOfType(parent, TLFrameUtil) &&
|
||||
this.isShapeOfType(rootShape, TLFrameUtil) &&
|
||||
rootShape.props.w === parent?.props.w &&
|
||||
rootShape.props.h === parent?.props.h
|
||||
) {
|
||||
|
@ -4249,7 +4273,7 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
|
||||
const rootShapes: TLShape[] = []
|
||||
|
||||
const newShapes: TLShapePartial[] = shapes.map((shape): TLShape => {
|
||||
const newShapes: TLShape[] = shapes.map((shape): TLShape => {
|
||||
let newShape: TLShape
|
||||
|
||||
if (preserveIds) {
|
||||
|
@ -4280,7 +4304,7 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
index = getIndexAbove(index)
|
||||
}
|
||||
|
||||
if (TLArrowShapeDef.is(newShape)) {
|
||||
if (this.isShapeOfType(newShape, TLArrowUtil)) {
|
||||
if (newShape.props.start.type === 'binding') {
|
||||
const mappedId = idMap.get(newShape.props.start.boundShapeId)
|
||||
newShape.props.start = mappedId
|
||||
|
@ -4374,7 +4398,7 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
}
|
||||
|
||||
for (let i = 0; i < newShapes.length; i++) {
|
||||
const shape = newShapes[i] as TLShape
|
||||
const shape = newShapes[i]
|
||||
const result = this.store.schema.migratePersistedRecord(shape, content.schema)
|
||||
if (result.type === 'success') {
|
||||
newShapes[i] = result.value as TLShape
|
||||
|
@ -4435,7 +4459,7 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
while (
|
||||
this.getShapesAtPoint(point).some(
|
||||
(shape) =>
|
||||
TLFrameShapeDef.is(shape) &&
|
||||
this.isShapeOfType(shape, TLFrameUtil) &&
|
||||
shape.props.w === onlyRoot.props.w &&
|
||||
shape.props.h === onlyRoot.props.h
|
||||
)
|
||||
|
@ -4589,7 +4613,7 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
const shapeRecordsToCreate: TLShape[] = []
|
||||
|
||||
for (const partial of partials) {
|
||||
const util = this.getShapeUtil(partial as TLShape)
|
||||
const util = this.getShapeUtil(partial)
|
||||
|
||||
// If an index is not explicitly provided, then add the
|
||||
// shapes to the top of their parents' children; using the
|
||||
|
@ -4850,7 +4874,7 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
|
||||
return newRecord ?? prev
|
||||
})
|
||||
) as TLShape[]
|
||||
)
|
||||
|
||||
const updates = Object.fromEntries(updated.map((shape) => [shape.id, shape]))
|
||||
|
||||
|
@ -5535,9 +5559,9 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
|
||||
/* ------------------- SubCommands ------------------ */
|
||||
async getSvg(
|
||||
ids: TLShapeId[] = (this.selectedIds.length
|
||||
ids: TLShapeId[] = this.selectedIds.length
|
||||
? this.selectedIds
|
||||
: Object.keys(this.shapeIds)) as TLShapeId[],
|
||||
: (Object.keys(this.shapeIds) as TLShapeId[]),
|
||||
opts = {} as Partial<{
|
||||
scale: number
|
||||
background: boolean
|
||||
|
@ -6152,15 +6176,17 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
|
||||
if (!shapes.length) return this
|
||||
|
||||
shapes = shapes
|
||||
.map((shape) => {
|
||||
if (shape.type === 'group') {
|
||||
return this.getSortedChildIds(shape.id).map((id) => this.getShapeById(id))
|
||||
}
|
||||
shapes = compact(
|
||||
shapes
|
||||
.map((shape) => {
|
||||
if (shape.type === 'group') {
|
||||
return this.getSortedChildIds(shape.id).map((id) => this.getShapeById(id))
|
||||
}
|
||||
|
||||
return shape
|
||||
})
|
||||
.flat() as TLShape[]
|
||||
return shape
|
||||
})
|
||||
.flat()
|
||||
)
|
||||
|
||||
const scaleOriginPage = Box2d.Common(compact(shapes.map((id) => this.getPageBounds(id)))).center
|
||||
|
||||
|
@ -6214,7 +6240,7 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
const shapes = compact(ids.map((id) => this.getShapeById(id))).filter((shape) => {
|
||||
if (!shape) return false
|
||||
|
||||
if (TLArrowShapeDef.is(shape)) {
|
||||
if (this.isShapeOfType(shape, TLArrowUtil)) {
|
||||
if (shape.props.start.type === 'binding' || shape.props.end.type === 'binding') {
|
||||
return false
|
||||
}
|
||||
|
@ -6346,7 +6372,7 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
.filter((shape) => {
|
||||
if (!shape) return false
|
||||
|
||||
if (TLArrowShapeDef.is(shape)) {
|
||||
if (this.isShapeOfType(shape, TLArrowUtil)) {
|
||||
if (shape.props.start.type === 'binding' || shape.props.end.type === 'binding') {
|
||||
return false
|
||||
}
|
||||
|
@ -6462,7 +6488,7 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
const translateStartChange = this.getShapeUtil(shape).onTranslateStart?.({
|
||||
...shape,
|
||||
...change,
|
||||
} as TLShape)
|
||||
})
|
||||
|
||||
if (translateStartChange) {
|
||||
changes.push({ ...change, ...translateStartChange })
|
||||
|
@ -7567,8 +7593,8 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
|
||||
let newShape: TLShape = deepCopy(shape)
|
||||
|
||||
if (TLArrowShapeDef.is(shape) && TLArrowShapeDef.is(newShape)) {
|
||||
const info = this.getShapeUtilByDef(TLArrowShapeDef).getArrowInfo(shape)
|
||||
if (this.isShapeOfType(shape, TLArrowUtil) && this.isShapeOfType(newShape, TLArrowUtil)) {
|
||||
const info = this.getShapeUtil(TLArrowUtil).getArrowInfo(shape)
|
||||
let newStartShapeId: TLShapeId | undefined = undefined
|
||||
let newEndShapeId: TLShapeId | undefined = undefined
|
||||
|
||||
|
@ -7767,7 +7793,7 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
id: shape.id,
|
||||
type: shape.type,
|
||||
props,
|
||||
} as TLShape
|
||||
}
|
||||
}),
|
||||
ephemeral
|
||||
)
|
||||
|
@ -7790,7 +7816,7 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
if (boundsA.width !== boundsB.width) {
|
||||
didChange = true
|
||||
|
||||
if (TLTextShapeDef.is(shape)) {
|
||||
if (this.isShapeOfType(shape, TLTextUtil)) {
|
||||
switch (shape.props.align) {
|
||||
case 'middle': {
|
||||
change.x = currentShape.x + (boundsA.width - boundsB.width) / 2
|
||||
|
@ -8848,7 +8874,7 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
const groups: TLGroupShape[] = []
|
||||
|
||||
shapes.forEach((shape) => {
|
||||
if (TLGroupShapeDef.is(shape)) {
|
||||
if (this.isShapeOfType(shape, TLGroupUtil)) {
|
||||
groups.push(shape)
|
||||
} else {
|
||||
idsToSelect.add(shape.id)
|
||||
|
@ -8858,10 +8884,10 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
if (groups.length === 0) return this
|
||||
|
||||
this.batch(() => {
|
||||
let group: TLShape
|
||||
let group: TLGroupShape
|
||||
|
||||
for (let i = 0, n = groups.length; i < n; i++) {
|
||||
group = groups[i] as TLGroupShape
|
||||
group = groups[i]
|
||||
const childIds = this.getSortedChildIds(group.id)
|
||||
|
||||
for (let j = 0, n = childIds.length; j < n; j++) {
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
import { TLArrowShape, TLShape, TLShapeId, TLStore } from '@tldraw/tlschema'
|
||||
import { Computed, RESET_VALUE, computed, isUninitialized } from 'signia'
|
||||
import { TLArrowShapeDef } from '../shapeutils/TLArrowUtil/TLArrowUtil'
|
||||
|
||||
export type TLArrowBindingsIndex = Record<
|
||||
TLShapeId,
|
||||
undefined | { arrowId: TLShapeId; handleId: 'start' | 'end' }[]
|
||||
>
|
||||
|
||||
function isArrowType(shape: any): shape is TLArrowShape {
|
||||
return shape.type === 'arrow'
|
||||
}
|
||||
|
||||
export const arrowBindingsIndex = (store: TLStore): Computed<TLArrowBindingsIndex> => {
|
||||
const shapeHistory = store.query.filterHistory('shape')
|
||||
const arrowQuery = store.query.records('shape', () => ({ type: { eq: 'arrow' as const } }))
|
||||
|
@ -79,7 +83,7 @@ export const arrowBindingsIndex = (store: TLStore): Computed<TLArrowBindingsInde
|
|||
|
||||
for (const changes of diff) {
|
||||
for (const newShape of Object.values(changes.added)) {
|
||||
if (TLArrowShapeDef.is(newShape)) {
|
||||
if (isArrowType(newShape)) {
|
||||
const { start, end } = newShape.props
|
||||
if (start.type === 'binding') {
|
||||
addBinding(start.boundShapeId, newShape.id, 'start')
|
||||
|
@ -91,7 +95,7 @@ export const arrowBindingsIndex = (store: TLStore): Computed<TLArrowBindingsInde
|
|||
}
|
||||
|
||||
for (const [prev, next] of Object.values(changes.updated) as [TLShape, TLShape][]) {
|
||||
if (!TLArrowShapeDef.is(prev) || !TLArrowShapeDef.is(next)) continue
|
||||
if (!isArrowType(prev) || !isArrowType(next)) continue
|
||||
|
||||
for (const handle of ['start', 'end'] as const) {
|
||||
const prevTerminal = prev.props[handle]
|
||||
|
@ -116,7 +120,7 @@ export const arrowBindingsIndex = (store: TLStore): Computed<TLArrowBindingsInde
|
|||
}
|
||||
|
||||
for (const prev of Object.values(changes.removed)) {
|
||||
if (TLArrowShapeDef.is(prev)) {
|
||||
if (isArrowType(prev)) {
|
||||
const { start, end } = prev.props
|
||||
if (start.type === 'binding') {
|
||||
removingBinding(start.boundShapeId, prev.id, 'start')
|
||||
|
|
|
@ -17,7 +17,7 @@ import { compact, dedupe, deepCopy } from '@tldraw/utils'
|
|||
import { atom, computed, EMPTY_ARRAY } from 'signia'
|
||||
import { uniqueId } from '../../utils/data'
|
||||
import type { App } from '../App'
|
||||
import { getSplineForLineShape, TLLineShapeDef } from '../shapeutils/TLLineUtil/TLLineUtil'
|
||||
import { getSplineForLineShape, TLLineUtil } from '../shapeutils/TLLineUtil/TLLineUtil'
|
||||
|
||||
export type PointsSnapLine = {
|
||||
id: string
|
||||
|
@ -249,7 +249,7 @@ export class SnapManager {
|
|||
const processParent = (parentId: TLParentId) => {
|
||||
const children = this.app.getSortedChildIds(parentId)
|
||||
for (const id of children) {
|
||||
const shape = this.app.getShapeById(id) as TLShape
|
||||
const shape = this.app.getShapeById(id)
|
||||
if (!shape) continue
|
||||
if (shape.type === 'arrow') continue
|
||||
if (selectedIds.includes(id)) continue
|
||||
|
@ -495,7 +495,7 @@ export class SnapManager {
|
|||
// and then pass them to the snap function as 'additionalOutlines'
|
||||
|
||||
// First, let's find which handle we're dragging
|
||||
const util = this.app.getShapeUtilByDef(TLLineShapeDef)
|
||||
const util = this.app.getShapeUtil(TLLineUtil)
|
||||
const handles = util.handles(line).sort(sortByIndex)
|
||||
if (handles.length < 3) return { nudge: new Vec2d(0, 0) }
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import { TAU } from '@tldraw/primitives'
|
|||
import { createCustomShapeId, TLArrowShape, TLArrowTerminal, TLShapeId } from '@tldraw/tlschema'
|
||||
import { assert } from '@tldraw/utils'
|
||||
import { TestApp } from '../../../test/TestApp'
|
||||
import { TLArrowShapeDef } from './TLArrowUtil'
|
||||
import { TLArrowUtil } from './TLArrowUtil'
|
||||
|
||||
let app: TestApp
|
||||
|
||||
|
@ -299,7 +299,7 @@ describe('Other cases when arrow are moved', () => {
|
|||
|
||||
app.setSelectedTool('arrow').pointerDown(1000, 1000).pointerMove(50, 350).pointerUp(50, 350)
|
||||
let arrow = app.shapesArray[app.shapesArray.length - 1]
|
||||
assert(TLArrowShapeDef.is(arrow))
|
||||
assert(app.isShapeOfType(arrow, TLArrowUtil))
|
||||
assert(arrow.props.end.type === 'binding')
|
||||
expect(arrow.props.end.boundShapeId).toBe(ids.box3)
|
||||
|
||||
|
@ -308,7 +308,7 @@ describe('Other cases when arrow are moved', () => {
|
|||
|
||||
// arrow should still be bound to box3
|
||||
arrow = app.getShapeById(arrow.id)!
|
||||
assert(TLArrowShapeDef.is(arrow))
|
||||
assert(app.isShapeOfType(arrow, TLArrowUtil))
|
||||
assert(arrow.props.end.type === 'binding')
|
||||
expect(arrow.props.end.boundShapeId).toBe(ids.box3)
|
||||
})
|
||||
|
|
|
@ -11,8 +11,6 @@ import {
|
|||
VecLike,
|
||||
} from '@tldraw/primitives'
|
||||
import {
|
||||
arrowShapeTypeMigrations,
|
||||
arrowShapeTypeValidator,
|
||||
TLArrowheadType,
|
||||
TLArrowShape,
|
||||
TLColorType,
|
||||
|
@ -27,7 +25,6 @@ import { deepCopy, last, minBy } from '@tldraw/utils'
|
|||
import * as React from 'react'
|
||||
import { computed, EMPTY_ARRAY } from 'signia'
|
||||
import { SVGContainer } from '../../../components/SVGContainer'
|
||||
import { defineShape } from '../../../config/TLShapeDefinition'
|
||||
import { ARROW_LABEL_FONT_SIZES, FONT_FAMILIES, TEXT_PROPS } from '../../../constants'
|
||||
import { createTextSvgElementFromSpans } from '../shared/createTextSvgElementFromSpans'
|
||||
import { getPerfectDashProps } from '../shared/getPerfectDashProps'
|
||||
|
@ -60,7 +57,7 @@ let globalRenderIndex = 0
|
|||
|
||||
/** @public */
|
||||
export class TLArrowUtil extends TLShapeUtil<TLArrowShape> {
|
||||
static type = 'arrow'
|
||||
static override type = 'arrow'
|
||||
|
||||
override canEdit = () => true
|
||||
override canBind = () => false
|
||||
|
@ -1135,11 +1132,3 @@ function getArrowheadSvgPath(
|
|||
function isPrecise(normalizedAnchor: Vec2dModel) {
|
||||
return normalizedAnchor.x !== 0.5 || normalizedAnchor.y !== 0.5
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export const TLArrowShapeDef = defineShape<TLArrowShape, TLArrowUtil>({
|
||||
type: 'arrow',
|
||||
getShapeUtil: () => TLArrowUtil,
|
||||
validator: arrowShapeTypeValidator,
|
||||
migrations: arrowShapeTypeMigrations,
|
||||
})
|
||||
|
|
|
@ -1,15 +1,7 @@
|
|||
import { toDomPrecision } from '@tldraw/primitives'
|
||||
import {
|
||||
bookmarkShapeTypeMigrations,
|
||||
bookmarkShapeTypeValidator,
|
||||
TLAsset,
|
||||
TLAssetId,
|
||||
TLBookmarkAsset,
|
||||
TLBookmarkShape,
|
||||
} from '@tldraw/tlschema'
|
||||
import { TLAsset, TLAssetId, TLBookmarkAsset, TLBookmarkShape } from '@tldraw/tlschema'
|
||||
import { debounce, getHashForString } from '@tldraw/utils'
|
||||
import { HTMLContainer } from '../../../components/HTMLContainer'
|
||||
import { defineShape } from '../../../config/TLShapeDefinition'
|
||||
import {
|
||||
DEFAULT_BOOKMARK_HEIGHT,
|
||||
DEFAULT_BOOKMARK_WIDTH,
|
||||
|
@ -20,13 +12,13 @@ import {
|
|||
stopEventPropagation,
|
||||
truncateStringWithEllipsis,
|
||||
} from '../../../utils/dom'
|
||||
import { HyperlinkButton } from '../shared/HyperlinkButton'
|
||||
import { TLBoxUtil } from '../TLBoxUtil'
|
||||
import { OnBeforeCreateHandler, OnBeforeUpdateHandler } from '../TLShapeUtil'
|
||||
import { HyperlinkButton } from '../shared/HyperlinkButton'
|
||||
|
||||
/** @public */
|
||||
export class TLBookmarkUtil extends TLBoxUtil<TLBookmarkShape> {
|
||||
static type = 'bookmark'
|
||||
static override type = 'bookmark'
|
||||
|
||||
override canResize = () => false
|
||||
|
||||
|
@ -191,11 +183,3 @@ export class TLBookmarkUtil extends TLBoxUtil<TLBookmarkShape> {
|
|||
}
|
||||
}, 500)
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export const TLBookmarkShapeDef = defineShape<TLBookmarkShape, TLBookmarkUtil>({
|
||||
type: 'bookmark',
|
||||
getShapeUtil: () => TLBookmarkUtil,
|
||||
validator: bookmarkShapeTypeValidator,
|
||||
migrations: bookmarkShapeTypeMigrations,
|
||||
})
|
||||
|
|
|
@ -9,15 +9,9 @@ import {
|
|||
Vec2d,
|
||||
VecLike,
|
||||
} from '@tldraw/primitives'
|
||||
import {
|
||||
drawShapeTypeMigrations,
|
||||
drawShapeTypeValidator,
|
||||
TLDrawShape,
|
||||
TLDrawShapeSegment,
|
||||
} from '@tldraw/tlschema'
|
||||
import { TLDrawShape, TLDrawShapeSegment } from '@tldraw/tlschema'
|
||||
import { last, rng } from '@tldraw/utils'
|
||||
import { SVGContainer } from '../../../components/SVGContainer'
|
||||
import { defineShape } from '../../../config/TLShapeDefinition'
|
||||
import { getSvgPathFromStroke, getSvgPathFromStrokePoints } from '../../../utils/svg'
|
||||
import { getShapeFillSvg, ShapeFill } from '../shared/ShapeFill'
|
||||
import { TLExportColors } from '../shared/TLExportColors'
|
||||
|
@ -27,7 +21,7 @@ import { getDrawShapeStrokeDashArray, getFreehandOptions, getPointsFromSegments
|
|||
|
||||
/** @public */
|
||||
export class TLDrawUtil extends TLShapeUtil<TLDrawShape> {
|
||||
static type = 'draw'
|
||||
static override type = 'draw'
|
||||
|
||||
hideResizeHandles = (shape: TLDrawShape) => this.getIsDot(shape)
|
||||
hideRotateHandle = (shape: TLDrawShape) => this.getIsDot(shape)
|
||||
|
@ -310,14 +304,6 @@ export class TLDrawUtil extends TLShapeUtil<TLDrawShape> {
|
|||
}
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export const TLDrawShapeDef = defineShape<TLDrawShape, TLDrawUtil>({
|
||||
type: 'draw',
|
||||
getShapeUtil: () => TLDrawUtil,
|
||||
migrations: drawShapeTypeMigrations,
|
||||
validator: drawShapeTypeValidator,
|
||||
})
|
||||
|
||||
function getDot(point: VecLike, sw: number) {
|
||||
const r = (sw + 1) * 0.5
|
||||
return `M ${point.x} ${point.y} m -${r}, 0 a ${r},${r} 0 1,0 ${r * 2},0 a ${r},${r} 0 1,0 -${
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
/* eslint-disable react-hooks/rules-of-hooks */
|
||||
import { toDomPrecision } from '@tldraw/primitives'
|
||||
import {
|
||||
embedShapeTypeMigrations,
|
||||
embedShapeTypeValidator,
|
||||
TLEmbedShape,
|
||||
tlEmbedShapePermissionDefaults,
|
||||
TLEmbedShapePermissions,
|
||||
|
@ -10,10 +8,9 @@ import {
|
|||
import * as React from 'react'
|
||||
import { useMemo } from 'react'
|
||||
import { useValue } from 'signia-react'
|
||||
import { DefaultSpinner } from '../../../components/DefaultSpinner'
|
||||
import { HTMLContainer } from '../../../components/HTMLContainer'
|
||||
import { defineShape } from '../../../config/TLShapeDefinition'
|
||||
import { ROTATING_SHADOWS } from '../../../constants'
|
||||
import { useEditorComponents } from '../../../hooks/useEditorComponents'
|
||||
import { useIsEditing } from '../../../hooks/useIsEditing'
|
||||
import { rotateBoxShadow } from '../../../utils/dom'
|
||||
import { getEmbedInfo, getEmbedInfoUnsafely } from '../../../utils/embeds'
|
||||
|
@ -30,7 +27,7 @@ const getSandboxPermissions = (permissions: TLEmbedShapePermissions) => {
|
|||
|
||||
/** @public */
|
||||
export class TLEmbedUtil extends TLBoxUtil<TLEmbedShape> {
|
||||
static type = 'embed'
|
||||
static override type = 'embed'
|
||||
|
||||
override canUnmount: TLShapeUtilFlag<TLEmbedShape> = () => false
|
||||
override canResize = (shape: TLEmbedShape) => {
|
||||
|
@ -84,8 +81,6 @@ export class TLEmbedUtil extends TLBoxUtil<TLEmbedShape> {
|
|||
const isEditing = useIsEditing(shape.id)
|
||||
const embedInfo = useMemo(() => getEmbedInfoUnsafely(url), [url])
|
||||
|
||||
const { Spinner } = useEditorComponents()
|
||||
|
||||
const isHoveringWhileEditingSameShape = useValue(
|
||||
'is hovering',
|
||||
() => {
|
||||
|
@ -150,11 +145,11 @@ export class TLEmbedUtil extends TLBoxUtil<TLEmbedShape> {
|
|||
background: embedInfo?.definition.backgroundColor,
|
||||
}}
|
||||
/>
|
||||
) : Spinner ? (
|
||||
) : (
|
||||
<g transform={`translate(${(w - 38) / 2}, ${(h - 38) / 2})`}>
|
||||
<Spinner />
|
||||
<DefaultSpinner />
|
||||
</g>
|
||||
) : null}
|
||||
)}
|
||||
</HTMLContainer>
|
||||
)
|
||||
}
|
||||
|
@ -230,11 +225,3 @@ function Gist({
|
|||
/>
|
||||
)
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export const TLEmbedShapeDef = defineShape<TLEmbedShape, TLEmbedUtil>({
|
||||
type: 'embed',
|
||||
getShapeUtil: () => TLEmbedUtil,
|
||||
validator: embedShapeTypeValidator,
|
||||
migrations: embedShapeTypeMigrations,
|
||||
})
|
||||
|
|
|
@ -1,14 +1,7 @@
|
|||
import { canolicalizeRotation, SelectionEdge, toDomPrecision } from '@tldraw/primitives'
|
||||
import {
|
||||
frameShapeTypeValidator,
|
||||
TLFrameShape,
|
||||
TLShape,
|
||||
TLShapeId,
|
||||
TLShapeType,
|
||||
} from '@tldraw/tlschema'
|
||||
import { TLFrameShape, TLShape, TLShapeId, TLShapeType } from '@tldraw/tlschema'
|
||||
import { last } from '@tldraw/utils'
|
||||
import { SVGContainer } from '../../../components/SVGContainer'
|
||||
import { defineShape } from '../../../config/TLShapeDefinition'
|
||||
import { defaultEmptyAs } from '../../../utils/string'
|
||||
import { createTextSvgElementFromSpans } from '../shared/createTextSvgElementFromSpans'
|
||||
import { TLExportColors } from '../shared/TLExportColors'
|
||||
|
@ -18,7 +11,7 @@ import { FrameHeading } from './components/FrameHeading'
|
|||
|
||||
/** @public */
|
||||
export class TLFrameUtil extends TLBoxUtil<TLFrameShape> {
|
||||
static type = 'frame'
|
||||
static override type = 'frame'
|
||||
|
||||
override canBind = () => true
|
||||
|
||||
|
@ -211,10 +204,3 @@ export class TLFrameUtil extends TLBoxUtil<TLFrameShape> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export const TLFrameShapeDef = defineShape<TLFrameShape, TLFrameUtil>({
|
||||
type: 'frame',
|
||||
getShapeUtil: () => TLFrameUtil,
|
||||
validator: frameShapeTypeValidator,
|
||||
})
|
||||
|
|
|
@ -12,15 +12,8 @@ import {
|
|||
Vec2d,
|
||||
VecLike,
|
||||
} from '@tldraw/primitives'
|
||||
import {
|
||||
geoShapeTypeMigrations,
|
||||
geoShapeTypeValidator,
|
||||
TLDashType,
|
||||
TLGeoShape,
|
||||
TLGeoShapeProps,
|
||||
} from '@tldraw/tlschema'
|
||||
import { TLDashType, TLGeoShape, TLGeoShapeProps } from '@tldraw/tlschema'
|
||||
import { SVGContainer } from '../../../components/SVGContainer'
|
||||
import { defineShape } from '../../../config/TLShapeDefinition'
|
||||
import { FONT_FAMILIES, LABEL_FONT_SIZES, TEXT_PROPS } from '../../../constants'
|
||||
import { App } from '../../App'
|
||||
import { createTextSvgElementFromSpans } from '../shared/createTextSvgElementFromSpans'
|
||||
|
@ -48,7 +41,7 @@ const MIN_SIZE_WITH_LABEL = 17 * 3
|
|||
|
||||
/** @public */
|
||||
export class TLGeoUtil extends TLBoxUtil<TLGeoShape> {
|
||||
static type = 'geo'
|
||||
static override type = 'geo'
|
||||
|
||||
canEdit = () => true
|
||||
|
||||
|
@ -1002,11 +995,3 @@ function getCheckBoxLines(w: number, h: number) {
|
|||
[new Vec2d(ox + size * 0.45, oy + size * 0.82), new Vec2d(ox + size * 0.82, oy + size * 0.22)],
|
||||
]
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export const TLGeoShapeDef = defineShape<TLGeoShape, TLGeoUtil>({
|
||||
type: 'geo',
|
||||
getShapeUtil: () => TLGeoUtil,
|
||||
validator: geoShapeTypeValidator,
|
||||
migrations: geoShapeTypeMigrations,
|
||||
})
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import { Box2d, Matrix2d } from '@tldraw/primitives'
|
||||
import { TLGroupShape, Vec2dModel, groupShapeTypeValidator } from '@tldraw/tlschema'
|
||||
import { TLGroupShape, Vec2dModel } from '@tldraw/tlschema'
|
||||
import { SVGContainer } from '../../../components/SVGContainer'
|
||||
import { defineShape } from '../../../config/TLShapeDefinition'
|
||||
import { OnChildrenChangeHandler, TLShapeUtil } from '../TLShapeUtil'
|
||||
import { DashedOutlineBox } from '../shared/DashedOutlineBox'
|
||||
|
||||
/** @public */
|
||||
export class TLGroupUtil extends TLShapeUtil<TLGroupShape> {
|
||||
static type = 'group'
|
||||
static override type = 'group'
|
||||
|
||||
hideSelectionBoundsBg = () => false
|
||||
hideSelectionBoundsFg = () => true
|
||||
|
@ -104,10 +103,3 @@ export class TLGroupUtil extends TLShapeUtil<TLGroupShape> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export const TLGroupShapeDef = defineShape<TLGroupShape, TLGroupUtil>({
|
||||
type: 'group',
|
||||
getShapeUtil: () => TLGroupUtil,
|
||||
validator: groupShapeTypeValidator,
|
||||
})
|
||||
|
|
|
@ -1,17 +1,11 @@
|
|||
/* eslint-disable react-hooks/rules-of-hooks */
|
||||
import { Vec2d, toDomPrecision } from '@tldraw/primitives'
|
||||
import {
|
||||
TLImageShape,
|
||||
TLShapePartial,
|
||||
imageShapeTypeMigrations,
|
||||
imageShapeTypeValidator,
|
||||
} from '@tldraw/tlschema'
|
||||
import { TLImageShape, TLShapePartial } from '@tldraw/tlschema'
|
||||
import { deepCopy } from '@tldraw/utils'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useValue } from 'signia-react'
|
||||
import { DefaultSpinner } from '../../../components/DefaultSpinner'
|
||||
import { HTMLContainer } from '../../../components/HTMLContainer'
|
||||
import { defineShape } from '../../../config/TLShapeDefinition'
|
||||
import { useEditorComponents } from '../../../hooks/useEditorComponents'
|
||||
import { useIsCropping } from '../../../hooks/useIsCropping'
|
||||
import { usePrefersReducedMotion } from '../../../utils/dom'
|
||||
import { TLBoxUtil } from '../TLBoxUtil'
|
||||
|
@ -55,7 +49,7 @@ async function getDataURIFromURL(url: string): Promise<string> {
|
|||
|
||||
/** @public */
|
||||
export class TLImageUtil extends TLBoxUtil<TLImageShape> {
|
||||
static type = 'image'
|
||||
static override type = 'image'
|
||||
|
||||
override isAspectRatioLocked = () => true
|
||||
override canCrop = () => true
|
||||
|
@ -77,7 +71,6 @@ export class TLImageUtil extends TLBoxUtil<TLImageShape> {
|
|||
const isCropping = useIsCropping(shape.id)
|
||||
const prefersReducedMotion = usePrefersReducedMotion()
|
||||
const [staticFrameSrc, setStaticFrameSrc] = useState('')
|
||||
const { Spinner } = useEditorComponents()
|
||||
|
||||
const { w, h } = shape.props
|
||||
const asset = shape.props.assetId ? this.app.getAssetById(shape.props.assetId) : undefined
|
||||
|
@ -148,11 +141,11 @@ export class TLImageUtil extends TLBoxUtil<TLImageShape> {
|
|||
}}
|
||||
draggable={false}
|
||||
/>
|
||||
) : Spinner ? (
|
||||
) : (
|
||||
<g transform={`translate(${(w - 38) / 2}, ${(h - 38) / 2})`}>
|
||||
<Spinner />
|
||||
<DefaultSpinner />
|
||||
</g>
|
||||
) : null}
|
||||
)}
|
||||
{asset?.props.isAnimated && !shape.props.playing && (
|
||||
<div className="tl-image__tg">GIF</div>
|
||||
)}
|
||||
|
@ -270,14 +263,6 @@ export class TLImageUtil extends TLBoxUtil<TLImageShape> {
|
|||
}
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export const TLImageShapeDef = defineShape<TLImageShape, TLImageUtil>({
|
||||
type: 'image',
|
||||
getShapeUtil: () => TLImageUtil,
|
||||
validator: imageShapeTypeValidator,
|
||||
migrations: imageShapeTypeMigrations,
|
||||
})
|
||||
|
||||
/**
|
||||
* When an image is cropped we need to translate the image to show the portion withing the cropped
|
||||
* area. We do this by translating the image by the negative of the top left corner of the crop
|
||||
|
|
|
@ -169,7 +169,7 @@ describe('Misc', () => {
|
|||
const boxID = createCustomShapeId('box1')
|
||||
app.createShapes([{ id: boxID, type: 'geo', x: 500, y: 150, props: { w: 100, h: 50 } }])
|
||||
|
||||
const box = app.getShapeById(boxID)! as TLGeoShape
|
||||
const box = app.getShapeById<TLGeoShape>(boxID)!
|
||||
const line = app.getShapeById<TLLineShape>(id)!
|
||||
|
||||
app.select(boxID, id)
|
||||
|
|
|
@ -9,10 +9,9 @@ import {
|
|||
intersectLineSegmentPolyline,
|
||||
pointNearToPolyline,
|
||||
} from '@tldraw/primitives'
|
||||
import { TLHandle, TLLineShape, lineShapeTypeValidator } from '@tldraw/tlschema'
|
||||
import { TLHandle, TLLineShape } from '@tldraw/tlschema'
|
||||
import { deepCopy } from '@tldraw/utils'
|
||||
import { SVGContainer } from '../../../components/SVGContainer'
|
||||
import { defineShape } from '../../../config/TLShapeDefinition'
|
||||
import { WeakMapCache } from '../../../utils/WeakMapCache'
|
||||
import { OnHandleChangeHandler, OnResizeHandler, TLShapeUtil } from '../TLShapeUtil'
|
||||
import { ShapeFill } from '../shared/ShapeFill'
|
||||
|
@ -27,7 +26,7 @@ const handlesCache = new WeakMapCache<TLLineShape['props'], TLHandle[]>()
|
|||
|
||||
/** @public */
|
||||
export class TLLineUtil extends TLShapeUtil<TLLineShape> {
|
||||
static type = 'line'
|
||||
static override type = 'line'
|
||||
|
||||
override hideResizeHandles = () => true
|
||||
override hideRotateHandle = () => true
|
||||
|
@ -335,13 +334,6 @@ export class TLLineUtil extends TLShapeUtil<TLLineShape> {
|
|||
}
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export const TLLineShapeDef = defineShape<TLLineShape, TLLineUtil>({
|
||||
type: 'line',
|
||||
getShapeUtil: () => TLLineUtil,
|
||||
validator: lineShapeTypeValidator,
|
||||
})
|
||||
|
||||
/** @public */
|
||||
export function getSplineForLineShape(shape: TLLineShape) {
|
||||
return splinesCache.get(shape.props, () => {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { Box2d, toDomPrecision, Vec2d } from '@tldraw/primitives'
|
||||
import { noteShapeTypeMigrations, noteShapeTypeValidator, TLNoteShape } from '@tldraw/tlschema'
|
||||
import { defineShape } from '../../../config/TLShapeDefinition'
|
||||
import { TLNoteShape } from '@tldraw/tlschema'
|
||||
import { FONT_FAMILIES, LABEL_FONT_SIZES, TEXT_PROPS } from '../../../constants'
|
||||
import { App } from '../../App'
|
||||
import { createTextSvgElementFromSpans } from '../shared/createTextSvgElementFromSpans'
|
||||
|
@ -13,7 +12,7 @@ const NOTE_SIZE = 200
|
|||
|
||||
/** @public */
|
||||
export class TLNoteUtil extends TLShapeUtil<TLNoteShape> {
|
||||
static type = 'note'
|
||||
static override type = 'note'
|
||||
|
||||
canEdit = () => true
|
||||
hideResizeHandles = () => true
|
||||
|
@ -197,14 +196,6 @@ export class TLNoteUtil extends TLShapeUtil<TLNoteShape> {
|
|||
}
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export const TLNoteShapeDef = defineShape<TLNoteShape, TLNoteUtil>({
|
||||
getShapeUtil: () => TLNoteUtil,
|
||||
type: 'note',
|
||||
validator: noteShapeTypeValidator,
|
||||
migrations: noteShapeTypeMigrations,
|
||||
})
|
||||
|
||||
function getGrowY(app: App, shape: TLNoteShape, prevGrowY = 0) {
|
||||
const PADDING = 17
|
||||
|
||||
|
|
|
@ -31,9 +31,11 @@ export interface TLShapeUtilConstructor<
|
|||
export type TLShapeUtilFlag<T> = (shape: T) => boolean
|
||||
|
||||
/** @public */
|
||||
export abstract class TLShapeUtil<T extends TLUnknownShape> {
|
||||
export abstract class TLShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
||||
constructor(public app: App, public readonly type: T['type']) {}
|
||||
|
||||
static type: string
|
||||
|
||||
/**
|
||||
* Check if a shape is of this type.
|
||||
*
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
/* eslint-disable react-hooks/rules-of-hooks */
|
||||
import { Box2d, toDomPrecision, Vec2d } from '@tldraw/primitives'
|
||||
import { textShapeTypeMigrations, textShapeTypeValidator, TLTextShape } from '@tldraw/tlschema'
|
||||
import { TLTextShape } from '@tldraw/tlschema'
|
||||
import { HTMLContainer } from '../../../components/HTMLContainer'
|
||||
import { defineShape } from '../../../config/TLShapeDefinition'
|
||||
import { FONT_FAMILIES, FONT_SIZES, TEXT_PROPS } from '../../../constants'
|
||||
import { stopEventPropagation } from '../../../utils/dom'
|
||||
import { WeakMapCache } from '../../../utils/WeakMapCache'
|
||||
|
@ -19,7 +18,7 @@ const sizeCache = new WeakMapCache<TLTextShape['props'], { height: number; width
|
|||
|
||||
/** @public */
|
||||
export class TLTextUtil extends TLShapeUtil<TLTextShape> {
|
||||
static type = 'text'
|
||||
static override type = 'text'
|
||||
|
||||
canEdit = () => true
|
||||
|
||||
|
@ -369,14 +368,6 @@ export class TLTextUtil extends TLShapeUtil<TLTextShape> {
|
|||
}
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export const TLTextShapeDef = defineShape<TLTextShape, TLTextUtil>({
|
||||
type: 'text',
|
||||
getShapeUtil: () => TLTextUtil,
|
||||
validator: textShapeTypeValidator,
|
||||
migrations: textShapeTypeMigrations,
|
||||
})
|
||||
|
||||
function getTextSize(app: App, props: TLTextShape['props']) {
|
||||
const { font, text, autoSize, size, w } = props
|
||||
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import { toDomPrecision } from '@tldraw/primitives'
|
||||
import { TLVideoShape, videoShapeTypeMigrations, videoShapeTypeValidator } from '@tldraw/tlschema'
|
||||
import { TLVideoShape } from '@tldraw/tlschema'
|
||||
import * as React from 'react'
|
||||
import { track } from 'signia-react'
|
||||
import { DefaultSpinner } from '../../../components/DefaultSpinner'
|
||||
import { HTMLContainer } from '../../../components/HTMLContainer'
|
||||
import { defineShape } from '../../../config/TLShapeDefinition'
|
||||
import { useEditorComponents } from '../../../hooks/useEditorComponents'
|
||||
import { useIsEditing } from '../../../hooks/useIsEditing'
|
||||
import { usePrefersReducedMotion } from '../../../utils/dom'
|
||||
import { TLBoxUtil } from '../TLBoxUtil'
|
||||
|
@ -12,7 +11,7 @@ import { HyperlinkButton } from '../shared/HyperlinkButton'
|
|||
|
||||
/** @public */
|
||||
export class TLVideoUtil extends TLBoxUtil<TLVideoShape> {
|
||||
static type = 'video'
|
||||
static override type = 'video'
|
||||
|
||||
override canEdit = () => true
|
||||
override isAspectRatioLocked = () => true
|
||||
|
@ -49,14 +48,6 @@ export class TLVideoUtil extends TLBoxUtil<TLVideoShape> {
|
|||
}
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export const TLVideoShapeDef = defineShape<TLVideoShape, TLVideoUtil>({
|
||||
type: 'video',
|
||||
getShapeUtil: () => TLVideoUtil,
|
||||
validator: videoShapeTypeValidator,
|
||||
migrations: videoShapeTypeMigrations,
|
||||
})
|
||||
|
||||
// Function from v1, could be improved bu explicitly using this.model.time (?)
|
||||
function serializeVideo(id: string): string {
|
||||
const splitId = id.split(':')[1]
|
||||
|
@ -74,7 +65,6 @@ const TLVideoUtilComponent = track(function TLVideoUtilComponent(props: {
|
|||
shape: TLVideoShape
|
||||
videoUtil: TLVideoUtil
|
||||
}) {
|
||||
const { Spinner } = useEditorComponents()
|
||||
const { shape, videoUtil } = props
|
||||
const showControls = videoUtil.app.getBounds(shape).w * videoUtil.app.zoomLevel >= 110
|
||||
const asset = shape.props.assetId ? videoUtil.app.getAssetById(shape.props.assetId) : null
|
||||
|
@ -202,11 +192,11 @@ const TLVideoUtilComponent = track(function TLVideoUtilComponent(props: {
|
|||
>
|
||||
<source src={asset.props.src} />
|
||||
</video>
|
||||
) : Spinner ? (
|
||||
) : (
|
||||
<g transform={`translate(${(w - 38) / 2}, ${(h - 38) / 2})`}>
|
||||
<Spinner />
|
||||
<DefaultSpinner />
|
||||
</g>
|
||||
) : null}
|
||||
)}
|
||||
</div>
|
||||
</HTMLContainer>
|
||||
{'url' in shape.props && shape.props.url && (
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { createShapeId, TLArrowShape, TLShapeType } from '@tldraw/tlschema'
|
||||
import { TLArrowShapeDef } from '../../../shapeutils/TLArrowUtil/TLArrowUtil'
|
||||
import { TLArrowUtil } from '../../../shapeutils/TLArrowUtil/TLArrowUtil'
|
||||
import { TLEventHandlers } from '../../../types/event-types'
|
||||
import { StateNode } from '../../StateNode'
|
||||
import { TLArrowTool } from '../TLArrowTool'
|
||||
|
@ -48,8 +48,8 @@ export class Pointing extends StateNode {
|
|||
},
|
||||
])
|
||||
|
||||
const util = this.app.getShapeUtilByDef(TLArrowShapeDef)
|
||||
const shape = this.app.getShapeById(id) as TLArrowShape
|
||||
const util = this.app.getShapeUtil(TLArrowUtil)
|
||||
const shape = this.app.getShapeById<TLArrowShape>(id)
|
||||
if (!shape) return
|
||||
|
||||
const handles = util.handles?.(shape)
|
||||
|
@ -96,7 +96,7 @@ export class Pointing extends StateNode {
|
|||
}
|
||||
|
||||
if (!this.didTimeout) {
|
||||
const util = this.app.getShapeUtilByDef(TLArrowShapeDef)
|
||||
const util = this.app.getShapeUtil(TLArrowUtil)
|
||||
const shape = this.app.getShapeById<TLArrowShape>(this.shape.id)
|
||||
|
||||
if (!shape) return
|
||||
|
|
|
@ -92,7 +92,7 @@ export class Pointing extends StateNode {
|
|||
])
|
||||
|
||||
const shape = this.app.getShapeById<TLBoxLike>(id)!
|
||||
const { w, h } = this.app.getShapeUtil<TLBoxLike>(shape).defaultProps()
|
||||
const { w, h } = this.app.getShapeUtil(shape).defaultProps() as TLBoxLike['props']
|
||||
const delta = this.app.getDeltaInParentSpace(shape, new Vec2d(w / 2, h / 2))
|
||||
|
||||
this.app.updateShapes([
|
||||
|
|
|
@ -9,7 +9,7 @@ import {
|
|||
import { last, structuredClone } from '@tldraw/utils'
|
||||
import { DRAG_DISTANCE } from '../../../../constants'
|
||||
import { uniqueId } from '../../../../utils/data'
|
||||
import { TLDrawShapeDef } from '../../../shapeutils/TLDrawUtil/TLDrawUtil'
|
||||
import { TLDrawUtil } from '../../../shapeutils/TLDrawUtil/TLDrawUtil'
|
||||
import { TLEventHandlers, TLPointerEventInfo } from '../../../types/event-types'
|
||||
|
||||
import { StateNode } from '../../StateNode'
|
||||
|
@ -21,7 +21,7 @@ export class Drawing extends StateNode {
|
|||
|
||||
initialShape?: TLDrawShape
|
||||
|
||||
util = this.app.getShapeUtilByDef(TLDrawShapeDef)
|
||||
util = this.app.getShapeUtil(TLDrawUtil)
|
||||
|
||||
isPen = false
|
||||
|
||||
|
@ -157,7 +157,7 @@ export class Drawing extends StateNode {
|
|||
this.lastRecordedPoint = originPagePoint.clone()
|
||||
|
||||
if (this.initialShape) {
|
||||
const shape = this.app.getShapeById(this.initialShape.id) as TLDrawShape
|
||||
const shape = this.app.getShapeById<TLDrawShape>(this.initialShape.id)
|
||||
|
||||
if (shape && this.segmentMode === 'straight') {
|
||||
// Connect dots
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { assert } from '@tldraw/utils'
|
||||
import { TLNoteShapeDef } from '../../../shapeutils/TLNoteUtil/TLNoteUtil'
|
||||
import { TLNoteShape } from '@tldraw/tlschema'
|
||||
import { TLNoteUtil } from '../../../shapeutils/TLNoteUtil/TLNoteUtil'
|
||||
import { TLEventHandlers, TLInterruptEvent, TLPointerEventInfo } from '../../../types/event-types'
|
||||
import { StateNode } from '../../StateNode'
|
||||
|
||||
|
@ -97,9 +97,8 @@ export class Pointing extends StateNode {
|
|||
true
|
||||
)
|
||||
|
||||
const util = this.app.getShapeUtilByDef(TLNoteShapeDef)
|
||||
const shape = this.app.getShapeById(id)!
|
||||
assert(TLNoteShapeDef.is(shape))
|
||||
const util = this.app.getShapeUtil(TLNoteUtil)
|
||||
const shape = this.app.getShapeById<TLNoteShape>(id)!
|
||||
const bounds = util.bounds(shape)
|
||||
|
||||
// Center the text around the created point
|
||||
|
|
|
@ -77,7 +77,7 @@ export class Cropping extends StateNode {
|
|||
const { shape, cursorHandleOffset } = this.snapshot
|
||||
|
||||
if (!shape) return
|
||||
const util = this.app.getShapeUtil(shape) as TLImageUtil
|
||||
const util = this.app.getShapeUtil(TLImageUtil)
|
||||
if (!util) return
|
||||
|
||||
const props = shape.props as TLImageShapeProps
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
import { TLUnknownShape } from '@tldraw/tlschema'
|
||||
import { Migrations, StoreValidator } from '@tldraw/tlstore'
|
||||
import { App } from '../app/App'
|
||||
import { TLShapeUtil, TLShapeUtilConstructor } from '../app/shapeutils/TLShapeUtil'
|
||||
|
||||
/** @public */
|
||||
export interface TLShapeDef<
|
||||
ShapeType extends TLUnknownShape,
|
||||
ShapeUtil extends TLShapeUtil<ShapeType> = TLShapeUtil<ShapeType>
|
||||
> {
|
||||
readonly type: ShapeType['type']
|
||||
readonly createShapeUtils: (app: App) => ShapeUtil
|
||||
readonly is: (shape: TLUnknownShape) => shape is ShapeType
|
||||
readonly validator?: StoreValidator<ShapeType>
|
||||
readonly migrations: Migrations
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export type TLUnknownShapeDef = TLShapeDef<TLUnknownShape, TLShapeUtil<TLUnknownShape>>
|
||||
|
||||
/** @public */
|
||||
export function defineShape<
|
||||
ShapeType extends TLUnknownShape,
|
||||
ShapeUtil extends TLShapeUtil<ShapeType> = TLShapeUtil<ShapeType>
|
||||
>({
|
||||
type,
|
||||
getShapeUtil,
|
||||
validator,
|
||||
migrations = { currentVersion: 0, firstVersion: 0, migrators: {} },
|
||||
}: {
|
||||
type: ShapeType['type']
|
||||
getShapeUtil: () => TLShapeUtilConstructor<ShapeType, ShapeUtil>
|
||||
validator?: StoreValidator<ShapeType>
|
||||
migrations?: Migrations
|
||||
}): TLShapeDef<ShapeType, ShapeUtil> {
|
||||
if (!validator && process.env.NODE_ENV === 'development') {
|
||||
console.warn(
|
||||
`No validator provided for shape type ${type}! Validators are highly recommended for use in production.`
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
type,
|
||||
createShapeUtils: (app: App) => {
|
||||
const ShapeUtil = getShapeUtil()
|
||||
return new ShapeUtil(app, type)
|
||||
},
|
||||
is: (shape: TLUnknownShape): shape is ShapeType => shape.type === type,
|
||||
validator,
|
||||
migrations,
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import {
|
||||
CLIENT_FIXUP_SCRIPT,
|
||||
MigrationsForShapes,
|
||||
TLDOCUMENT_ID,
|
||||
TLInstance,
|
||||
TLInstanceId,
|
||||
|
@ -8,65 +9,147 @@ import {
|
|||
TLShape,
|
||||
TLStore,
|
||||
TLStoreProps,
|
||||
TLUnknownShape,
|
||||
TLUser,
|
||||
TLUserId,
|
||||
ValidatorsForShapes,
|
||||
arrowShapeTypeMigrations,
|
||||
arrowShapeTypeValidator,
|
||||
bookmarkShapeTypeMigrations,
|
||||
bookmarkShapeTypeValidator,
|
||||
createTLSchema,
|
||||
drawShapeTypeMigrations,
|
||||
drawShapeTypeValidator,
|
||||
embedShapeTypeMigrations,
|
||||
embedShapeTypeValidator,
|
||||
frameShapeTypeMigrations,
|
||||
frameShapeTypeValidator,
|
||||
geoShapeTypeMigrations,
|
||||
geoShapeTypeValidator,
|
||||
groupShapeTypeMigrations,
|
||||
groupShapeTypeValidator,
|
||||
imageShapeTypeMigrations,
|
||||
imageShapeTypeValidator,
|
||||
lineShapeTypeMigrations,
|
||||
lineShapeTypeValidator,
|
||||
noteShapeTypeMigrations,
|
||||
noteShapeTypeValidator,
|
||||
textShapeTypeMigrations,
|
||||
textShapeTypeValidator,
|
||||
videoShapeTypeMigrations,
|
||||
videoShapeTypeValidator,
|
||||
} from '@tldraw/tlschema'
|
||||
import { RecordType, Store, StoreSchema, StoreSnapshot } from '@tldraw/tlstore'
|
||||
import {
|
||||
Migrations,
|
||||
RecordType,
|
||||
Store,
|
||||
StoreSchema,
|
||||
StoreSnapshot,
|
||||
defineMigrations,
|
||||
} from '@tldraw/tlstore'
|
||||
import { T } from '@tldraw/tlvalidate'
|
||||
import { Signal } from 'signia'
|
||||
import { TLArrowShapeDef } from '../app/shapeutils/TLArrowUtil/TLArrowUtil'
|
||||
import { TLBookmarkShapeDef } from '../app/shapeutils/TLBookmarkUtil/TLBookmarkUtil'
|
||||
import { TLDrawShapeDef } from '../app/shapeutils/TLDrawUtil/TLDrawUtil'
|
||||
import { TLEmbedShapeDef } from '../app/shapeutils/TLEmbedUtil/TLEmbedUtil'
|
||||
import { TLFrameShapeDef } from '../app/shapeutils/TLFrameUtil/TLFrameUtil'
|
||||
import { TLGeoShapeDef } from '../app/shapeutils/TLGeoUtil/TLGeoUtil'
|
||||
import { TLGroupShapeDef } from '../app/shapeutils/TLGroupUtil/TLGroupUtil'
|
||||
import { TLImageShapeDef } from '../app/shapeutils/TLImageUtil/TLImageUtil'
|
||||
import { TLLineShapeDef } from '../app/shapeutils/TLLineUtil/TLLineUtil'
|
||||
import { TLNoteShapeDef } from '../app/shapeutils/TLNoteUtil/TLNoteUtil'
|
||||
import { TLTextShapeDef } from '../app/shapeutils/TLTextUtil/TLTextUtil'
|
||||
import { TLVideoShapeDef } from '../app/shapeutils/TLVideoUtil/TLVideoUtil'
|
||||
import { TLArrowUtil } from '../app/shapeutils/TLArrowUtil/TLArrowUtil'
|
||||
import { TLBookmarkUtil } from '../app/shapeutils/TLBookmarkUtil/TLBookmarkUtil'
|
||||
import { TLDrawUtil } from '../app/shapeutils/TLDrawUtil/TLDrawUtil'
|
||||
import { TLEmbedUtil } from '../app/shapeutils/TLEmbedUtil/TLEmbedUtil'
|
||||
import { TLFrameUtil } from '../app/shapeutils/TLFrameUtil/TLFrameUtil'
|
||||
import { TLGeoUtil } from '../app/shapeutils/TLGeoUtil/TLGeoUtil'
|
||||
import { TLGroupUtil } from '../app/shapeutils/TLGroupUtil/TLGroupUtil'
|
||||
import { TLImageUtil } from '../app/shapeutils/TLImageUtil/TLImageUtil'
|
||||
import { TLLineUtil } from '../app/shapeutils/TLLineUtil/TLLineUtil'
|
||||
import { TLNoteUtil } from '../app/shapeutils/TLNoteUtil/TLNoteUtil'
|
||||
import { TLShapeUtilConstructor } from '../app/shapeutils/TLShapeUtil'
|
||||
import { TLTextUtil } from '../app/shapeutils/TLTextUtil/TLTextUtil'
|
||||
import { TLVideoUtil } from '../app/shapeutils/TLVideoUtil/TLVideoUtil'
|
||||
import { StateNodeConstructor } from '../app/statechart/StateNode'
|
||||
import { TLShapeDef, TLUnknownShapeDef } from './TLShapeDefinition'
|
||||
|
||||
type CustomShapeInfo<T extends TLUnknownShape> = {
|
||||
util: TLShapeUtilConstructor<any>
|
||||
validator?: { validate: (record: T) => T }
|
||||
migrations?: Migrations
|
||||
}
|
||||
|
||||
type UtilsForShapes<T extends TLUnknownShape> = Record<T['type'], TLShapeUtilConstructor<any>>
|
||||
|
||||
type TldrawEditorConfigOptions<T extends TLUnknownShape = TLShape> = {
|
||||
tools?: readonly StateNodeConstructor[]
|
||||
shapes?: { [K in T['type']]: CustomShapeInfo<T> }
|
||||
/** @internal */
|
||||
derivePresenceState?: (store: TLStore) => Signal<TLInstancePresence | null>
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export class TldrawEditorConfig {
|
||||
static readonly default = new TldrawEditorConfig({})
|
||||
|
||||
readonly storeSchema: StoreSchema<TLRecord, TLStoreProps>
|
||||
readonly shapes: readonly TLUnknownShapeDef[]
|
||||
readonly TLShape: RecordType<TLShape, 'type' | 'props' | 'index' | 'parentId'>
|
||||
readonly tools: readonly StateNodeConstructor[]
|
||||
|
||||
constructor(args: {
|
||||
shapes?: readonly TLShapeDef<any, any>[]
|
||||
tools?: readonly StateNodeConstructor[]
|
||||
allowUnknownShapes?: boolean
|
||||
/** @internal */
|
||||
derivePresenceState?: (store: TLStore) => Signal<TLInstancePresence | null>
|
||||
}) {
|
||||
const { shapes = [], tools = [], allowUnknownShapes = false, derivePresenceState } = args
|
||||
readonly shapeUtils: UtilsForShapes<TLShape>
|
||||
readonly shapeValidators: ValidatorsForShapes<TLShape>
|
||||
readonly shapeMigrations: MigrationsForShapes<TLShape>
|
||||
|
||||
constructor(opts: TldrawEditorConfigOptions) {
|
||||
const { shapes = [], tools = [], derivePresenceState } = opts
|
||||
|
||||
this.tools = tools
|
||||
|
||||
this.shapes = [
|
||||
TLArrowShapeDef,
|
||||
TLBookmarkShapeDef,
|
||||
TLDrawShapeDef,
|
||||
TLEmbedShapeDef,
|
||||
TLFrameShapeDef,
|
||||
TLGeoShapeDef,
|
||||
TLGroupShapeDef,
|
||||
TLImageShapeDef,
|
||||
TLLineShapeDef,
|
||||
TLNoteShapeDef,
|
||||
TLTextShapeDef,
|
||||
TLVideoShapeDef,
|
||||
...shapes,
|
||||
]
|
||||
this.shapeUtils = {
|
||||
arrow: TLArrowUtil,
|
||||
bookmark: TLBookmarkUtil,
|
||||
draw: TLDrawUtil,
|
||||
embed: TLEmbedUtil,
|
||||
frame: TLFrameUtil,
|
||||
geo: TLGeoUtil,
|
||||
group: TLGroupUtil,
|
||||
image: TLImageUtil,
|
||||
line: TLLineUtil,
|
||||
note: TLNoteUtil,
|
||||
text: TLTextUtil,
|
||||
video: TLVideoUtil,
|
||||
}
|
||||
|
||||
this.shapeMigrations = {
|
||||
arrow: arrowShapeTypeMigrations,
|
||||
bookmark: bookmarkShapeTypeMigrations,
|
||||
draw: drawShapeTypeMigrations,
|
||||
embed: embedShapeTypeMigrations,
|
||||
frame: frameShapeTypeMigrations,
|
||||
geo: geoShapeTypeMigrations,
|
||||
group: groupShapeTypeMigrations,
|
||||
image: imageShapeTypeMigrations,
|
||||
line: lineShapeTypeMigrations,
|
||||
note: noteShapeTypeMigrations,
|
||||
text: textShapeTypeMigrations,
|
||||
video: videoShapeTypeMigrations,
|
||||
}
|
||||
|
||||
this.shapeValidators = {
|
||||
arrow: arrowShapeTypeValidator,
|
||||
bookmark: bookmarkShapeTypeValidator,
|
||||
draw: drawShapeTypeValidator,
|
||||
embed: embedShapeTypeValidator,
|
||||
frame: frameShapeTypeValidator,
|
||||
geo: geoShapeTypeValidator,
|
||||
group: groupShapeTypeValidator,
|
||||
image: imageShapeTypeValidator,
|
||||
line: lineShapeTypeValidator,
|
||||
note: noteShapeTypeValidator,
|
||||
text: textShapeTypeValidator,
|
||||
video: videoShapeTypeValidator,
|
||||
}
|
||||
|
||||
for (const [type, shape] of Object.entries(shapes)) {
|
||||
this.shapeUtils[type] = shape.util
|
||||
this.shapeMigrations[type] = shape.migrations ?? defineMigrations({})
|
||||
this.shapeValidators[type] = shape.validator ?? T.any
|
||||
}
|
||||
|
||||
this.storeSchema = createTLSchema({
|
||||
allowUnknownShapes,
|
||||
customShapeDefs: shapes,
|
||||
shapeMigrations: this.shapeMigrations,
|
||||
shapeValidators: this.shapeValidators,
|
||||
derivePresenceState,
|
||||
})
|
||||
|
||||
|
|
|
@ -406,7 +406,7 @@ describe('flipping rotated shapes', () => {
|
|||
const getStartAndEndPoints = (id: TLShapeId) => {
|
||||
const transform = app.getPageTransformById(id)
|
||||
if (!transform) throw new Error('no transform')
|
||||
const arrow = app.getShapeById(id) as TLArrowShape
|
||||
const arrow = app.getShapeById<TLArrowShape>(id)!
|
||||
if (arrow.props.start.type !== 'point' || arrow.props.end.type !== 'point')
|
||||
throw new Error('not a point')
|
||||
const start = Matrix2d.applyToPoint(transform, arrow.props.start)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { createCustomShapeId } from '@tldraw/tlschema'
|
||||
import { TLGeoShapeDef } from '../../app/shapeutils/TLGeoUtil/TLGeoUtil'
|
||||
import { TLGeoUtil } from '../../app/shapeutils/TLGeoUtil/TLGeoUtil'
|
||||
import { TestApp } from '../TestApp'
|
||||
|
||||
let app: TestApp
|
||||
|
@ -43,7 +43,7 @@ beforeEach(() => {
|
|||
describe('app.rotateShapes', () => {
|
||||
it('Rotates shapes and fires events', () => {
|
||||
// Set start / change / end events on only the geo shape
|
||||
const util = app.getShapeUtilByDef(TLGeoShapeDef)
|
||||
const util = app.getShapeUtil(TLGeoUtil)
|
||||
|
||||
// Bad! who did this (did I do this)
|
||||
const fnStart = jest.fn()
|
||||
|
|
|
@ -22,7 +22,7 @@ beforeEach(() => {
|
|||
},
|
||||
},
|
||||
])
|
||||
shape = app.getShapeById(id) as TLGeoShape
|
||||
shape = app.getShapeById<TLGeoShape>(id)!
|
||||
})
|
||||
|
||||
describe('Resize box', () => {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { createCustomShapeId } from '@tldraw/tlschema'
|
||||
import { TLFrameShapeDef } from '../app/shapeutils/TLFrameUtil/TLFrameUtil'
|
||||
import { TLGeoShapeDef } from '../app/shapeutils/TLGeoUtil/TLGeoUtil'
|
||||
import { TLFrameUtil } from '../app/shapeutils/TLFrameUtil/TLFrameUtil'
|
||||
import { TLGeoUtil } from '../app/shapeutils/TLGeoUtil/TLGeoUtil'
|
||||
import { TestApp } from './TestApp'
|
||||
|
||||
let app: TestApp
|
||||
|
@ -56,7 +56,7 @@ beforeEach(() => {
|
|||
describe('When interacting with a shape...', () => {
|
||||
it('fires rotate events', () => {
|
||||
// Set start / change / end events on only the geo shape
|
||||
const util = app.getShapeUtilByDef(TLFrameShapeDef)
|
||||
const util = app.getShapeUtil(TLFrameUtil)
|
||||
|
||||
const fnStart = jest.fn()
|
||||
util.onRotateStart = fnStart
|
||||
|
@ -89,12 +89,12 @@ describe('When interacting with a shape...', () => {
|
|||
})
|
||||
|
||||
it('cleans up events', () => {
|
||||
const util = app.getShapeUtilByDef(TLGeoShapeDef)
|
||||
const util = app.getShapeUtil(TLGeoUtil)
|
||||
expect(util.onRotateStart).toBeUndefined()
|
||||
})
|
||||
|
||||
it('fires double click handler event', () => {
|
||||
const util = app.getShapeUtilByDef(TLGeoShapeDef)
|
||||
const util = app.getShapeUtil(TLGeoUtil)
|
||||
|
||||
const fnStart = jest.fn()
|
||||
util.onDoubleClick = fnStart
|
||||
|
@ -105,7 +105,7 @@ describe('When interacting with a shape...', () => {
|
|||
})
|
||||
|
||||
it('Fires resisizing events', () => {
|
||||
const util = app.getShapeUtilByDef(TLFrameShapeDef)
|
||||
const util = app.getShapeUtil(TLFrameUtil)
|
||||
|
||||
const fnStart = jest.fn()
|
||||
util.onResizeStart = fnStart
|
||||
|
@ -142,7 +142,7 @@ describe('When interacting with a shape...', () => {
|
|||
})
|
||||
|
||||
it('Fires translating events', () => {
|
||||
const util = app.getShapeUtilByDef(TLFrameShapeDef)
|
||||
const util = app.getShapeUtil(TLFrameUtil)
|
||||
|
||||
const fnStart = jest.fn()
|
||||
util.onTranslateStart = fnStart
|
||||
|
@ -170,7 +170,7 @@ describe('When interacting with a shape...', () => {
|
|||
})
|
||||
|
||||
it('Uses the shape utils onClick handler', () => {
|
||||
const util = app.getShapeUtilByDef(TLFrameShapeDef)
|
||||
const util = app.getShapeUtil(TLFrameUtil)
|
||||
|
||||
const fnClick = jest.fn()
|
||||
util.onClick = fnClick
|
||||
|
@ -184,7 +184,7 @@ describe('When interacting with a shape...', () => {
|
|||
})
|
||||
|
||||
it('Uses the shape utils onClick handler', () => {
|
||||
const util = app.getShapeUtilByDef(TLFrameShapeDef)
|
||||
const util = app.getShapeUtil(TLFrameUtil)
|
||||
|
||||
const fnClick = jest.fn((shape: any) => {
|
||||
return {
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
import { TLBookmarkShape } from '@tldraw/tlschema'
|
||||
import {
|
||||
TLBookmarkShapeDef,
|
||||
TLBookmarkUtil,
|
||||
} from '../../app/shapeutils/TLBookmarkUtil/TLBookmarkUtil'
|
||||
import { TLBookmarkUtil } from '../../app/shapeutils/TLBookmarkUtil/TLBookmarkUtil'
|
||||
import { TestApp } from '../TestApp'
|
||||
|
||||
let app: TestApp
|
||||
|
@ -74,14 +71,14 @@ describe('The URL formatter', () => {
|
|||
},
|
||||
])
|
||||
|
||||
const a = app.getShapeById(ids.a) as TLBookmarkShape
|
||||
const b = app.getShapeById(ids.b) as TLBookmarkShape
|
||||
const c = app.getShapeById(ids.c) as TLBookmarkShape
|
||||
const d = app.getShapeById(ids.d) as TLBookmarkShape
|
||||
const e = app.getShapeById(ids.e) as TLBookmarkShape
|
||||
const f = app.getShapeById(ids.f) as TLBookmarkShape
|
||||
const a = app.getShapeById<TLBookmarkShape>(ids.a)!
|
||||
const b = app.getShapeById<TLBookmarkShape>(ids.b)!
|
||||
const c = app.getShapeById<TLBookmarkShape>(ids.c)!
|
||||
const d = app.getShapeById<TLBookmarkShape>(ids.d)!
|
||||
const e = app.getShapeById<TLBookmarkShape>(ids.e)!
|
||||
const f = app.getShapeById<TLBookmarkShape>(ids.f)!
|
||||
|
||||
const util = app.getShapeUtilByDef(TLBookmarkShapeDef)
|
||||
const util = app.getShapeUtil(TLBookmarkUtil)
|
||||
expect(util.getHumanReadableAddress(a)).toBe('www.github.com')
|
||||
expect(util.getHumanReadableAddress(b)).toBe('www.github.com')
|
||||
expect(util.getHumanReadableAddress(c)).toBe('www.github.com/TodePond')
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { assert } from '@tldraw/utils'
|
||||
import { TLLineShapeDef } from '../../app/shapeutils/TLLineUtil/TLLineUtil'
|
||||
import { TLLineUtil } from '../../app/shapeutils/TLLineUtil/TLLineUtil'
|
||||
import { TestApp } from '../TestApp'
|
||||
|
||||
let app: TestApp
|
||||
|
@ -124,7 +124,7 @@ describe('When extending the line with the shift-key in tool-lock mode', () => {
|
|||
.pointerUp(20, 10)
|
||||
|
||||
const line = app.shapesArray[app.shapesArray.length - 1]
|
||||
assert(TLLineShapeDef.is(line))
|
||||
assert(app.isShapeOfType(line, TLLineUtil))
|
||||
const handles = Object.values(line.props.handles)
|
||||
expect(handles.length).toBe(3)
|
||||
})
|
||||
|
@ -141,7 +141,7 @@ describe('When extending the line with the shift-key in tool-lock mode', () => {
|
|||
.pointerUp(30, 10)
|
||||
|
||||
const line = app.shapesArray[app.shapesArray.length - 1]
|
||||
assert(TLLineShapeDef.is(line))
|
||||
assert(app.isShapeOfType(line, TLLineUtil))
|
||||
const handles = Object.values(line.props.handles)
|
||||
expect(handles.length).toBe(3)
|
||||
})
|
||||
|
@ -159,7 +159,7 @@ describe('When extending the line with the shift-key in tool-lock mode', () => {
|
|||
.pointerUp(30, 10)
|
||||
|
||||
const line = app.shapesArray[app.shapesArray.length - 1]
|
||||
assert(TLLineShapeDef.is(line))
|
||||
assert(app.isShapeOfType(line, TLLineUtil))
|
||||
const handles = Object.values(line.props.handles)
|
||||
expect(handles.length).toBe(3)
|
||||
})
|
||||
|
@ -179,7 +179,7 @@ describe('When extending the line with the shift-key in tool-lock mode', () => {
|
|||
.pointerUp(30, 10)
|
||||
|
||||
const line = app.shapesArray[app.shapesArray.length - 1]
|
||||
assert(TLLineShapeDef.is(line))
|
||||
assert(app.isShapeOfType(line, TLLineUtil))
|
||||
const handles = Object.values(line.props.handles)
|
||||
expect(handles.length).toBe(3)
|
||||
})
|
||||
|
@ -201,7 +201,7 @@ describe('When extending the line with the shift-key in tool-lock mode', () => {
|
|||
.pointerUp(40, 10)
|
||||
|
||||
const line = app.shapesArray[app.shapesArray.length - 1]
|
||||
assert(TLLineShapeDef.is(line))
|
||||
assert(app.isShapeOfType(line, TLLineUtil))
|
||||
const handles = Object.values(line.props.handles)
|
||||
expect(handles.length).toBe(3)
|
||||
})
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { createCustomShapeId, TLArrowShape } from '@tldraw/tlschema'
|
||||
import { TLFrameShapeDef } from '../../app/shapeutils/TLFrameUtil/TLFrameUtil'
|
||||
import { TLFrameUtil } from '../../app/shapeutils/TLFrameUtil/TLFrameUtil'
|
||||
import { TestApp } from '../TestApp'
|
||||
|
||||
let app: TestApp
|
||||
|
@ -33,7 +33,7 @@ describe('creating frames', () => {
|
|||
app.setSelectedTool('frame')
|
||||
app.pointerDown(100, 100).pointerUp(100, 100)
|
||||
expect(app.onlySelectedShape?.type).toBe('frame')
|
||||
const { w, h } = app.getShapeUtilByDef(TLFrameShapeDef).defaultProps()
|
||||
const { w, h } = app.getShapeUtil(TLFrameUtil).defaultProps()
|
||||
expect(app.getPageBounds(app.onlySelectedShape!)).toMatchObject({
|
||||
x: 100 - w / 2,
|
||||
y: 100 - h / 2,
|
||||
|
|
|
@ -10,8 +10,8 @@ import {
|
|||
TLShapePartial,
|
||||
} from '@tldraw/tlschema'
|
||||
import { assert, compact } from '@tldraw/utils'
|
||||
import { TLArrowShapeDef } from '../../app/shapeutils/TLArrowUtil/TLArrowUtil'
|
||||
import { TLGroupShapeDef, TLGroupUtil } from '../../app/shapeutils/TLGroupUtil/TLGroupUtil'
|
||||
import { TLArrowUtil } from '../../app/shapeutils/TLArrowUtil/TLArrowUtil'
|
||||
import { TLGroupUtil } from '../../app/shapeutils/TLGroupUtil/TLGroupUtil'
|
||||
import { TLArrowTool } from '../../app/statechart/TLArrowTool/TLArrowTool'
|
||||
import { TLDrawTool } from '../../app/statechart/TLDrawTool/TLDrawTool'
|
||||
import { TLEraserTool } from '../../app/statechart/TLEraserTool/TLEraserTool'
|
||||
|
@ -1671,7 +1671,7 @@ describe('moving handles within a group', () => {
|
|||
target: 'handle',
|
||||
shape: arrow,
|
||||
handle: app
|
||||
.getShapeUtilByDef(TLArrowShapeDef)
|
||||
.getShapeUtil(TLArrowUtil)
|
||||
.handles(arrow)
|
||||
.find((h) => h.id === 'end'),
|
||||
})
|
||||
|
@ -1890,7 +1890,7 @@ describe('Group opacity', () => {
|
|||
app.setProp('opacity', '0.5')
|
||||
app.groupShapes()
|
||||
const group = app.getShapeById(onlySelectedId())!
|
||||
assert(TLGroupShapeDef.is(group))
|
||||
assert(app.isShapeOfType(group, TLGroupUtil))
|
||||
expect(group.props.opacity).toBe('1')
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import { Box2d, Vec2d, VecLike } from '@tldraw/primitives'
|
||||
import { TLShapeId, TLShapePartial, Vec2dModel, createCustomShapeId } from '@tldraw/tlschema'
|
||||
import { defineMigrations } from '@tldraw/tlstore'
|
||||
import { GapsSnapLine, PointsSnapLine, SnapLine } from '../../app/managers/SnapManager'
|
||||
import { TLShapeUtil } from '../../app/shapeutils/TLShapeUtil'
|
||||
import { defineShape } from '../../config/TLShapeDefinition'
|
||||
import { TldrawEditorConfig } from '../../config/TldrawEditorConfig'
|
||||
import { TestApp } from '../TestApp'
|
||||
|
||||
|
@ -12,8 +10,8 @@ import { getSnapLines } from '../testutils/getSnapLines'
|
|||
type __TopLeftSnapOnlyShape = any
|
||||
|
||||
class __TopLeftSnapOnlyShapeUtil extends TLShapeUtil<__TopLeftSnapOnlyShape> {
|
||||
type = '__test_top_left_snap_only' as const
|
||||
static type = '__test_top_left_snap_only' as const
|
||||
static override type = '__test_top_left_snap_only' as const
|
||||
|
||||
defaultProps(): __TopLeftSnapOnlyShape['props'] {
|
||||
return { width: 10, height: 10 }
|
||||
}
|
||||
|
@ -41,14 +39,14 @@ class __TopLeftSnapOnlyShapeUtil extends TLShapeUtil<__TopLeftSnapOnlyShape> {
|
|||
return [Vec2d.From({ x: shape.x, y: shape.y })]
|
||||
}
|
||||
}
|
||||
const __TopLeftSnapOnlyShapeDef = defineShape<__TopLeftSnapOnlyShape, __TopLeftSnapOnlyShapeUtil>({
|
||||
type: '__test_top_left_snap_only',
|
||||
getShapeUtil: () => __TopLeftSnapOnlyShapeUtil,
|
||||
validator: { validate: (record) => record as __TopLeftSnapOnlyShape },
|
||||
migrations: defineMigrations({}),
|
||||
})
|
||||
|
||||
const configWithCustomShape = new TldrawEditorConfig({ shapes: [__TopLeftSnapOnlyShapeDef] })
|
||||
const configWithCustomShape = new TldrawEditorConfig({
|
||||
shapes: {
|
||||
__test_top_left_snap_only: {
|
||||
util: __TopLeftSnapOnlyShapeUtil,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
let app: TestApp
|
||||
|
||||
|
|
|
@ -2,16 +2,14 @@ import { Box2d, Vec2d, VecLike } from '@tldraw/primitives'
|
|||
import {
|
||||
TLAsset,
|
||||
TLAssetId,
|
||||
TLAssetShape,
|
||||
TLBookmarkAsset,
|
||||
TLImageShape,
|
||||
TLShape,
|
||||
TLShapePartial,
|
||||
TLVideoShape,
|
||||
Vec2dModel,
|
||||
createShapeId,
|
||||
} from '@tldraw/tlschema'
|
||||
import { compact, getHashForString, isNonNullish } from '@tldraw/utils'
|
||||
import { compact, getHashForString } from '@tldraw/utils'
|
||||
import uniq from 'lodash.uniq'
|
||||
import { App } from '../app/App'
|
||||
import { MAX_ASSET_HEIGHT, MAX_ASSET_WIDTH } from '../constants'
|
||||
|
@ -328,7 +326,7 @@ export async function createShapesFromFiles(
|
|||
|
||||
const shapeUpdates = await Promise.all(
|
||||
files.map(async (file, i) => {
|
||||
const shape = results[i] as TLShapePartial<TLImageShape | TLVideoShape>
|
||||
const shape = results[i]
|
||||
if (!shape) return
|
||||
|
||||
const asset = newAssetsForFiles.get(file)
|
||||
|
@ -344,7 +342,7 @@ export async function createShapesFromFiles(
|
|||
shape.props.assetId = existing.id
|
||||
}
|
||||
|
||||
return shape as TLShape
|
||||
return shape
|
||||
}
|
||||
|
||||
existing = app.getAssetBySrc(asset.props!.src!)
|
||||
|
@ -354,7 +352,7 @@ export async function createShapesFromFiles(
|
|||
shape.props.assetId = existing.id
|
||||
}
|
||||
|
||||
return shape as TLAssetShape
|
||||
return shape
|
||||
}
|
||||
|
||||
// Create a new model for the new source file
|
||||
|
@ -366,7 +364,7 @@ export async function createShapesFromFiles(
|
|||
})
|
||||
)
|
||||
|
||||
const filteredUpdates = shapeUpdates.filter(isNonNullish)
|
||||
const filteredUpdates = compact(shapeUpdates)
|
||||
|
||||
app.createAssets(compact([...newAssetsForFiles.values()]))
|
||||
app.createShapes(filteredUpdates)
|
||||
|
|
|
@ -24,6 +24,7 @@ import {
|
|||
} from '@tldraw/tlschema'
|
||||
import { transact } from 'signia'
|
||||
import { App } from '../app/App'
|
||||
import { TLArrowUtil } from '../app/shapeutils/TLArrowUtil/TLArrowUtil'
|
||||
import { MAX_SHAPES_PER_PAGE } from '../constants'
|
||||
|
||||
const TLDRAW_V1_VERSION = 15.5
|
||||
|
@ -515,8 +516,7 @@ export function buildFromV1Document(app: App, document: LegacyTldrawDocument) {
|
|||
}
|
||||
|
||||
const v2ShapeId = v1ShapeIdsToV2ShapeIds.get(v1Shape.id)!
|
||||
const v2ShapeStale = app.getShapeById<TLArrowShape>(v2ShapeId)!
|
||||
const util = app.getShapeUtil(v2ShapeStale)
|
||||
const util = app.getShapeUtil(TLArrowUtil)
|
||||
|
||||
// dumb but necessary
|
||||
app.inputs.ctrlKey = false
|
||||
|
|
|
@ -13,7 +13,6 @@ import { Store } from '@tldraw/tlstore';
|
|||
import { StoreSchema } from '@tldraw/tlstore';
|
||||
import { StoreSchemaOptions } from '@tldraw/tlstore';
|
||||
import { StoreSnapshot } from '@tldraw/tlstore';
|
||||
import { StoreValidator } from '@tldraw/tlstore';
|
||||
import { T } from '@tldraw/tlvalidate';
|
||||
|
||||
// @internal (undocumented)
|
||||
|
@ -105,9 +104,9 @@ export function createShapeValidator<Type extends string, Props extends object>(
|
|||
}>;
|
||||
|
||||
// @public (undocumented)
|
||||
export function createTLSchema({ customShapeDefs, allowUnknownShapes, derivePresenceState, }: {
|
||||
customShapeDefs?: readonly CustomShapeTypeInfo[];
|
||||
allowUnknownShapes?: boolean;
|
||||
export function createTLSchema({ shapeMigrations, shapeValidators, derivePresenceState, }: {
|
||||
shapeValidators: ValidatorsForShapes<TLShape>;
|
||||
shapeMigrations: MigrationsForShapes<TLShape>;
|
||||
derivePresenceState?: (store: TLStore) => Signal<null | TLInstancePresence>;
|
||||
}): StoreSchema<TLRecord, TLStoreProps>;
|
||||
|
||||
|
@ -117,13 +116,6 @@ export const cursorTypeValidator: T.Validator<string>;
|
|||
// @public (undocumented)
|
||||
export const cursorValidator: T.Validator<TLCursor>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type CustomShapeTypeInfo = {
|
||||
type: string;
|
||||
migrations?: Migrations;
|
||||
validator?: StoreValidator<TLShape>;
|
||||
};
|
||||
|
||||
// @internal (undocumented)
|
||||
export const dashValidator: T.Validator<"dashed" | "dotted" | "draw" | "solid">;
|
||||
|
||||
|
@ -344,6 +336,9 @@ export function fixupRecord(oldRecord: TLRecord): {
|
|||
// @internal (undocumented)
|
||||
export const fontValidator: T.Validator<"draw" | "mono" | "sans" | "serif">;
|
||||
|
||||
// @public (undocumented)
|
||||
export const frameShapeTypeMigrations: Migrations;
|
||||
|
||||
// @public (undocumented)
|
||||
export const frameShapeTypeValidator: T.Validator<TLFrameShape>;
|
||||
|
||||
|
@ -356,12 +351,18 @@ export const geoShapeTypeValidator: T.Validator<TLGeoShape>;
|
|||
// @internal (undocumented)
|
||||
export const geoValidator: T.Validator<"arrow-down" | "arrow-left" | "arrow-right" | "arrow-up" | "check-box" | "diamond" | "ellipse" | "hexagon" | "octagon" | "oval" | "pentagon" | "rectangle" | "rhombus-2" | "rhombus" | "star" | "trapezoid" | "triangle" | "x-box">;
|
||||
|
||||
// @public (undocumented)
|
||||
export const groupShapeTypeMigrations: Migrations;
|
||||
|
||||
// @public (undocumented)
|
||||
export const groupShapeTypeValidator: T.Validator<TLGroupShape>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const handleTypeValidator: T.Validator<TLHandle>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const iconShapeTypeMigrations: Migrations;
|
||||
|
||||
// @public (undocumented)
|
||||
export const iconShapeTypeValidator: T.Validator<TLIconShape>;
|
||||
|
||||
|
@ -404,9 +405,15 @@ export function isShape(record?: BaseRecord<string>): record is TLShape;
|
|||
// @public (undocumented)
|
||||
export function isShapeId(id?: string): id is TLShapeId;
|
||||
|
||||
// @public (undocumented)
|
||||
export const lineShapeTypeMigrations: Migrations;
|
||||
|
||||
// @public (undocumented)
|
||||
export const lineShapeTypeValidator: T.Validator<TLLineShape>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type MigrationsForShapes<T extends TLUnknownShape> = Record<T['type'], Migrations>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const noteShapeTypeMigrations: Migrations;
|
||||
|
||||
|
@ -1371,6 +1378,11 @@ export const userPresenceTypeValidator: T.Validator<TLUserPresence>;
|
|||
// @public (undocumented)
|
||||
export const userTypeValidator: T.Validator<TLUser>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type ValidatorsForShapes<T extends TLUnknownShape> = Record<T['type'], {
|
||||
validate: (record: T) => T;
|
||||
}>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface Vec2dModel {
|
||||
// (undocumented)
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
import {
|
||||
Migrations,
|
||||
StoreSchema,
|
||||
StoreValidator,
|
||||
createRecordType,
|
||||
defineMigrations,
|
||||
} from '@tldraw/tlstore'
|
||||
import { Migrations, StoreSchema, createRecordType, defineMigrations } from '@tldraw/tlstore'
|
||||
import { T } from '@tldraw/tlvalidate'
|
||||
import { Signal } from 'signia'
|
||||
import { TLRecord } from './TLRecord'
|
||||
|
@ -17,68 +11,32 @@ import { TLInstance } from './records/TLInstance'
|
|||
import { TLInstancePageState } from './records/TLInstancePageState'
|
||||
import { TLInstancePresence } from './records/TLInstancePresence'
|
||||
import { TLPage } from './records/TLPage'
|
||||
import { TLShape, rootShapeTypeMigrations } from './records/TLShape'
|
||||
import { TLShape, TLUnknownShape, rootShapeTypeMigrations } from './records/TLShape'
|
||||
import { TLUser } from './records/TLUser'
|
||||
import { TLUserDocument } from './records/TLUserDocument'
|
||||
import { TLUserPresence } from './records/TLUserPresence'
|
||||
import { storeMigrations } from './schema'
|
||||
import { arrowShapeTypeMigrations, arrowShapeTypeValidator } from './shapes/TLArrowShape'
|
||||
import { bookmarkShapeTypeMigrations, bookmarkShapeTypeValidator } from './shapes/TLBookmarkShape'
|
||||
import { drawShapeTypeMigrations, drawShapeTypeValidator } from './shapes/TLDrawShape'
|
||||
import { embedShapeTypeMigrations, embedShapeTypeValidator } from './shapes/TLEmbedShape'
|
||||
import { frameShapeTypeMigrations, frameShapeTypeValidator } from './shapes/TLFrameShape'
|
||||
import { geoShapeTypeMigrations, geoShapeTypeValidator } from './shapes/TLGeoShape'
|
||||
import { groupShapeTypeMigrations, groupShapeTypeValidator } from './shapes/TLGroupShape'
|
||||
import { imageShapeTypeMigrations, imageShapeTypeValidator } from './shapes/TLImageShape'
|
||||
import { lineShapeTypeMigrations, lineShapeTypeValidator } from './shapes/TLLineShape'
|
||||
import { noteShapeTypeMigrations, noteShapeTypeValidator } from './shapes/TLNoteShape'
|
||||
import { textShapeTypeMigrations, textShapeTypeValidator } from './shapes/TLTextShape'
|
||||
import { videoShapeTypeMigrations, videoShapeTypeValidator } from './shapes/TLVideoShape'
|
||||
|
||||
const CORE_SHAPE_DEFS: readonly CustomShapeTypeInfo[] = [
|
||||
{ type: 'draw', migrations: drawShapeTypeMigrations, validator: drawShapeTypeValidator },
|
||||
{ type: 'text', migrations: textShapeTypeMigrations, validator: textShapeTypeValidator },
|
||||
{ type: 'line', migrations: lineShapeTypeMigrations, validator: lineShapeTypeValidator },
|
||||
{ type: 'arrow', migrations: arrowShapeTypeMigrations, validator: arrowShapeTypeValidator },
|
||||
{ type: 'image', migrations: imageShapeTypeMigrations, validator: imageShapeTypeValidator },
|
||||
{ type: 'video', migrations: videoShapeTypeMigrations, validator: videoShapeTypeValidator },
|
||||
{ type: 'geo', migrations: geoShapeTypeMigrations, validator: geoShapeTypeValidator },
|
||||
{ type: 'note', migrations: noteShapeTypeMigrations, validator: noteShapeTypeValidator },
|
||||
{ type: 'group', migrations: groupShapeTypeMigrations, validator: groupShapeTypeValidator },
|
||||
{
|
||||
type: 'bookmark',
|
||||
migrations: bookmarkShapeTypeMigrations,
|
||||
validator: bookmarkShapeTypeValidator,
|
||||
},
|
||||
{ type: 'frame', migrations: frameShapeTypeMigrations, validator: frameShapeTypeValidator },
|
||||
{ type: 'embed', migrations: embedShapeTypeMigrations, validator: embedShapeTypeValidator },
|
||||
]
|
||||
|
||||
/** @public */
|
||||
export type CustomShapeTypeInfo = {
|
||||
type: string
|
||||
migrations?: Migrations
|
||||
validator?: StoreValidator<TLShape>
|
||||
}
|
||||
export type ValidatorsForShapes<T extends TLUnknownShape> = Record<
|
||||
T['type'],
|
||||
{ validate: (record: T) => T }
|
||||
>
|
||||
|
||||
/** @public */
|
||||
export type MigrationsForShapes<T extends TLUnknownShape> = Record<T['type'], Migrations>
|
||||
|
||||
/** @public */
|
||||
export function createTLSchema({
|
||||
customShapeDefs,
|
||||
allowUnknownShapes,
|
||||
shapeMigrations,
|
||||
shapeValidators,
|
||||
derivePresenceState,
|
||||
}: {
|
||||
customShapeDefs?: readonly CustomShapeTypeInfo[]
|
||||
allowUnknownShapes?: boolean
|
||||
shapeValidators: ValidatorsForShapes<TLShape>
|
||||
shapeMigrations: MigrationsForShapes<TLShape>
|
||||
derivePresenceState?: (store: TLStore) => Signal<TLInstancePresence | null>
|
||||
}) {
|
||||
const allShapeDefs = [...CORE_SHAPE_DEFS, ...(customShapeDefs ?? [])]
|
||||
const typeSet = new Set<string>()
|
||||
for (const shapeDef of allShapeDefs) {
|
||||
if (typeSet.has(shapeDef.type)) {
|
||||
throw new Error(`Shape type ${shapeDef.type} is already defined`)
|
||||
}
|
||||
typeSet.add(shapeDef.type)
|
||||
}
|
||||
// Removed check to see whether a shape type has already been defined
|
||||
|
||||
const shapeTypeMigrations = defineMigrations({
|
||||
currentVersion: rootShapeTypeMigrations.currentVersion,
|
||||
|
@ -86,20 +44,18 @@ export function createTLSchema({
|
|||
migrators: rootShapeTypeMigrations.migrators,
|
||||
subTypeKey: 'type',
|
||||
subTypeMigrations: Object.fromEntries(
|
||||
allShapeDefs.map((def) => [def.type, def.migrations ?? {}])
|
||||
) as Record<string, Migrations>,
|
||||
Object.entries(shapeMigrations) as [TLShape['type'], Migrations][]
|
||||
),
|
||||
})
|
||||
|
||||
let shapeValidator = T.union('type', {
|
||||
...Object.fromEntries(allShapeDefs.map((def) => [def.type, def.validator ?? (T.any as any)])),
|
||||
}) as T.UnionValidator<'type', any, any>
|
||||
if (allowUnknownShapes) {
|
||||
shapeValidator = shapeValidator.validateUnknownVariants((shape) => shape as any)
|
||||
}
|
||||
const shapeTypeValidator = T.union(
|
||||
'type',
|
||||
Object.fromEntries(Object.entries(shapeValidators) as [TLShape['type'], T.Validator<any>][])
|
||||
)
|
||||
|
||||
const shapeRecord = createRecordType<TLShape>('shape', {
|
||||
migrations: shapeTypeMigrations,
|
||||
validator: T.model('shape', shapeValidator),
|
||||
validator: T.model('shape', shapeTypeValidator),
|
||||
scope: 'document',
|
||||
}).withDefaultProperties(() => ({ x: 0, y: 0, rotation: 0, isLocked: false }))
|
||||
|
||||
|
|
|
@ -24,8 +24,11 @@ export {
|
|||
type TLVideoAsset,
|
||||
} from './assets/TLVideoAsset'
|
||||
export { createAssetValidator, type TLBaseAsset } from './assets/asset-validation'
|
||||
export { createTLSchema } from './createTLSchema'
|
||||
export type { CustomShapeTypeInfo } from './createTLSchema'
|
||||
export {
|
||||
createTLSchema,
|
||||
type MigrationsForShapes,
|
||||
type ValidatorsForShapes,
|
||||
} from './createTLSchema'
|
||||
export { defaultDerivePresenceState } from './defaultDerivePresenceState'
|
||||
export { CLIENT_FIXUP_SCRIPT, fixupRecord } from './fixup'
|
||||
export { type Box2dModel, type Vec2dModel } from './geometry-types'
|
||||
|
@ -121,6 +124,7 @@ export {
|
|||
type TLEmbedShapeProps,
|
||||
} from './shapes/TLEmbedShape'
|
||||
export {
|
||||
frameShapeTypeMigrations,
|
||||
frameShapeTypeValidator,
|
||||
type TLFrameShape,
|
||||
type TLFrameShapeProps,
|
||||
|
@ -132,11 +136,13 @@ export {
|
|||
type TLGeoShapeProps,
|
||||
} from './shapes/TLGeoShape'
|
||||
export {
|
||||
groupShapeTypeMigrations,
|
||||
groupShapeTypeValidator,
|
||||
type TLGroupShape,
|
||||
type TLGroupShapeProps,
|
||||
} from './shapes/TLGroupShape'
|
||||
export {
|
||||
iconShapeTypeMigrations,
|
||||
iconShapeTypeValidator,
|
||||
type TLIconShape,
|
||||
type TLIconShapeProps,
|
||||
|
@ -149,6 +155,7 @@ export {
|
|||
type TLImageShapeProps,
|
||||
} from './shapes/TLImageShape'
|
||||
export {
|
||||
lineShapeTypeMigrations,
|
||||
lineShapeTypeValidator,
|
||||
type TLLineShape,
|
||||
type TLLineShapeProps,
|
||||
|
|
|
@ -33,13 +33,13 @@ export type TLShape =
|
|||
| TLFrameShape
|
||||
| TLGeoShape
|
||||
| TLGroupShape
|
||||
| TLIconShape
|
||||
| TLImageShape
|
||||
| TLLineShape
|
||||
| TLNoteShape
|
||||
| TLTextShape
|
||||
| TLVideoShape
|
||||
| TLUnknownShape
|
||||
| TLIconShape
|
||||
|
||||
/** @public */
|
||||
export type TLShapeType = TLShape['type']
|
||||
|
|
|
@ -42,7 +42,7 @@ export type ComputedCache<Data, R extends BaseRecord> = {
|
|||
// @public
|
||||
export function createRecordType<R extends BaseRecord>(typeName: R['typeName'], config: {
|
||||
migrations?: Migrations;
|
||||
validator: StoreValidator<R>;
|
||||
validator?: StoreValidator<R>;
|
||||
scope: Scope;
|
||||
}): RecordType<R, keyof Omit<R, 'id' | 'typeName'>>;
|
||||
|
||||
|
|
|
@ -218,8 +218,7 @@ export function createRecordType<R extends BaseRecord>(
|
|||
typeName: R['typeName'],
|
||||
config: {
|
||||
migrations?: Migrations
|
||||
// todo: optional validations
|
||||
validator: StoreValidator<R>
|
||||
validator?: StoreValidator<R>
|
||||
scope: Scope
|
||||
}
|
||||
): RecordType<R, keyof Omit<R, 'id' | 'typeName'>> {
|
||||
|
|
|
@ -152,7 +152,7 @@ export class StoreSchema<R extends BaseRecord, P = unknown> {
|
|||
const persistedSubTypeVersion =
|
||||
'subTypeVersions' in persistedType
|
||||
? persistedType.subTypeVersions[record[ourType.migrations.subTypeKey as keyof R] as string]
|
||||
: null
|
||||
: undefined
|
||||
|
||||
// if ourSubTypeMigrations is undefined then we don't have access to the migrations for this subtype
|
||||
// that is almost certainly because we are running on the server and this type was supplied by a 3rd party.
|
||||
|
@ -165,7 +165,7 @@ export class StoreSchema<R extends BaseRecord, P = unknown> {
|
|||
// if the persistedSubTypeVersion is undefined then the record was either created after the schema
|
||||
// was persisted, or it was created in a different place to where the schema was persisted.
|
||||
// either way we don't know what to do with it safely, so let's return failure.
|
||||
if (persistedSubTypeVersion == null) {
|
||||
if (persistedSubTypeVersion === undefined) {
|
||||
return { type: 'error', reason: MigrationFailureReason.IncompatibleSubtype }
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useApp } from '@tldraw/editor'
|
||||
import { TLBookmarkUtil, TLShape, useApp } from '@tldraw/editor'
|
||||
import { useCallback, useRef, useState } from 'react'
|
||||
import { track } from 'signia-react'
|
||||
import { DialogProps } from '../hooks/useDialogsProvider'
|
||||
|
@ -19,12 +19,30 @@ function valiateUrl(url: string) {
|
|||
return false
|
||||
}
|
||||
|
||||
export const EditLinkDialog = track(function EditLink({ onClose }: DialogProps) {
|
||||
export const EditLinkDialog = track(function EditLinkDialog({ onClose }: DialogProps) {
|
||||
const app = useApp()
|
||||
const msg = useTranslation()
|
||||
|
||||
const selectedShape = app.onlySelectedShape
|
||||
|
||||
if (!(selectedShape && 'url' in selectedShape.props)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<EditLinkDialogInner
|
||||
onClose={onClose}
|
||||
selectedShape={selectedShape as TLShape & { props: { url: string } }}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
export const EditLinkDialogInner = track(function EditLinkDialogInner({
|
||||
onClose,
|
||||
selectedShape,
|
||||
}: DialogProps & { selectedShape: TLShape & { props: { url: string } } }) {
|
||||
const app = useApp()
|
||||
const msg = useTranslation()
|
||||
|
||||
const [validState, setValid] = useState(valiateUrl(selectedShape?.props.url))
|
||||
|
||||
const rInitialValue = useRef(selectedShape?.props.url)
|
||||
|
@ -64,13 +82,13 @@ export const EditLinkDialog = track(function EditLink({ onClose }: DialogProps)
|
|||
|
||||
const shape = app.selectedShapes[0]
|
||||
|
||||
if (shape) {
|
||||
if (shape && 'url' in shape.props) {
|
||||
const current = shape.props.url
|
||||
const next = validState
|
||||
? validState === 'needs protocol'
|
||||
? 'https://' + value
|
||||
: value
|
||||
: shape.type === 'bookmark'
|
||||
: app.isShapeOfType(shape, TLBookmarkUtil)
|
||||
? rInitialValue.current
|
||||
: ''
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import {
|
|||
FONT_SIZES,
|
||||
INDENT,
|
||||
TEXT_PROPS,
|
||||
TLTextShapeDef,
|
||||
TLTextUtil,
|
||||
createShapeId,
|
||||
} from '@tldraw/editor'
|
||||
import { VecLike } from '@tldraw/primitives'
|
||||
|
@ -64,7 +64,7 @@ function stripTrailingWhitespace(text: string): string {
|
|||
*/
|
||||
export async function pastePlainText(app: App, text: string, point?: VecLike) {
|
||||
const p = point ?? (app.inputs.shiftKey ? app.inputs.currentPagePoint : app.viewportPageCenter)
|
||||
const defaultProps = app.getShapeUtilByDef(TLTextShapeDef).defaultProps()
|
||||
const defaultProps = app.getShapeUtil(TLTextUtil).defaultProps()
|
||||
|
||||
const textToPaste = stripTrailingWhitespace(
|
||||
stripCommonMinimumIndentation(replaceTabsWithSpaces(text))
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { App, TLArrowShapeDef, useApp } from '@tldraw/editor'
|
||||
import { App, TLArrowUtil, useApp } from '@tldraw/editor'
|
||||
import { assert, exhaustiveSwitchError } from '@tldraw/utils'
|
||||
import { useValue } from 'signia-react'
|
||||
import { ActionItem } from './useActions'
|
||||
|
@ -136,10 +136,10 @@ function shapesWithUnboundArrows(app: App) {
|
|||
|
||||
return selectedShapes.filter((shape) => {
|
||||
if (!shape) return false
|
||||
if (TLArrowShapeDef.is(shape) && shape.props.start.type === 'binding') {
|
||||
if (app.isShapeOfType(shape, TLArrowUtil) && shape.props.start.type === 'binding') {
|
||||
return false
|
||||
}
|
||||
if (TLArrowShapeDef.is(shape) && shape.props.end.type === 'binding') {
|
||||
if (app.isShapeOfType(shape, TLArrowUtil) && shape.props.end.type === 'binding') {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
|
|
@ -5,11 +5,12 @@ import {
|
|||
DEFAULT_BOOKMARK_WIDTH,
|
||||
getEmbedInfo,
|
||||
openWindow,
|
||||
TLBookmarkShapeDef,
|
||||
TLEmbedShapeDef,
|
||||
TLBookmarkUtil,
|
||||
TLEmbedUtil,
|
||||
TLShapeId,
|
||||
TLShapePartial,
|
||||
TLTextShape,
|
||||
TLTextUtil,
|
||||
useApp,
|
||||
} from '@tldraw/editor'
|
||||
import { approximately, Box2d, TAU, Vec2d } from '@tldraw/primitives'
|
||||
|
@ -210,19 +211,21 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
|
|||
trackEvent('toggle-auto-size', { source })
|
||||
app.mark()
|
||||
app.updateShapes(
|
||||
app.selectedShapes
|
||||
.filter((shape) => shape && shape.type === 'text' && shape.props.autoSize === false)
|
||||
.map((shape: TLTextShape) => {
|
||||
return {
|
||||
id: shape.id,
|
||||
type: shape.type,
|
||||
props: {
|
||||
...shape.props,
|
||||
w: 8,
|
||||
autoSize: true,
|
||||
},
|
||||
} as TLTextShape
|
||||
})
|
||||
(
|
||||
app.selectedShapes.filter(
|
||||
(shape) => app.isShapeOfType(shape, TLTextUtil) && shape.props.autoSize === false
|
||||
) as TLTextShape[]
|
||||
).map((shape) => {
|
||||
return {
|
||||
id: shape.id,
|
||||
type: shape.type,
|
||||
props: {
|
||||
...shape.props,
|
||||
w: 8,
|
||||
autoSize: true,
|
||||
},
|
||||
}
|
||||
})
|
||||
)
|
||||
},
|
||||
},
|
||||
|
@ -239,7 +242,7 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
|
|||
return
|
||||
}
|
||||
const shape = app.getShapeById(ids[0])
|
||||
if (!shape || !TLEmbedShapeDef.is(shape)) {
|
||||
if (!shape || !app.isShapeOfType(shape, TLEmbedUtil)) {
|
||||
console.error(warnMsg)
|
||||
return
|
||||
}
|
||||
|
@ -259,7 +262,7 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
|
|||
const createList: TLShapePartial[] = []
|
||||
const deleteList: TLShapeId[] = []
|
||||
for (const shape of shapes) {
|
||||
if (!shape || !TLEmbedShapeDef.is(shape) || !shape.props.url) continue
|
||||
if (!shape || !app.isShapeOfType(shape, TLEmbedUtil) || !shape.props.url) continue
|
||||
|
||||
const newPos = new Vec2d(shape.x, shape.y)
|
||||
newPos.rot(-shape.rotation)
|
||||
|
@ -302,7 +305,7 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
|
|||
const createList: TLShapePartial[] = []
|
||||
const deleteList: TLShapeId[] = []
|
||||
for (const shape of shapes) {
|
||||
if (!TLBookmarkShapeDef.is(shape)) continue
|
||||
if (!app.isShapeOfType(shape, TLBookmarkUtil)) continue
|
||||
|
||||
const { url } = shape.props
|
||||
|
||||
|
|
|
@ -3,12 +3,12 @@ import {
|
|||
getValidHttpURLList,
|
||||
isSvgText,
|
||||
isValidHttpURL,
|
||||
TLArrowShapeDef,
|
||||
TLBookmarkShapeDef,
|
||||
TLArrowUtil,
|
||||
TLBookmarkUtil,
|
||||
TLClipboardModel,
|
||||
TLEmbedShapeDef,
|
||||
TLGeoShapeDef,
|
||||
TLTextShapeDef,
|
||||
TLEmbedUtil,
|
||||
TLGeoUtil,
|
||||
TLTextUtil,
|
||||
useApp,
|
||||
} from '@tldraw/editor'
|
||||
import { VecLike } from '@tldraw/primitives'
|
||||
|
@ -495,10 +495,14 @@ const handleNativeOrMenuCopy = (app: App) => {
|
|||
// Extract the text from the clipboard
|
||||
const textItems = content.shapes
|
||||
.map((shape) => {
|
||||
if (TLTextShapeDef.is(shape) || TLGeoShapeDef.is(shape) || TLArrowShapeDef.is(shape)) {
|
||||
if (
|
||||
app.isShapeOfType(shape, TLTextUtil) ||
|
||||
app.isShapeOfType(shape, TLGeoUtil) ||
|
||||
app.isShapeOfType(shape, TLArrowUtil)
|
||||
) {
|
||||
return shape.props.text
|
||||
}
|
||||
if (TLBookmarkShapeDef.is(shape) || TLEmbedShapeDef.is(shape)) {
|
||||
if (app.isShapeOfType(shape, TLBookmarkUtil) || app.isShapeOfType(shape, TLEmbedUtil)) {
|
||||
return shape.props.url
|
||||
}
|
||||
return null
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { App, getEmbedInfo, TLBookmarkShapeDef, TLEmbedShapeDef, useApp } from '@tldraw/editor'
|
||||
import { App, TLBookmarkUtil, TLEmbedUtil, getEmbedInfo, useApp } from '@tldraw/editor'
|
||||
import React, { useMemo } from 'react'
|
||||
import { track, useValue } from 'signia-react'
|
||||
import {
|
||||
MenuSchema,
|
||||
compactMenuItems,
|
||||
menuCustom,
|
||||
menuGroup,
|
||||
menuItem,
|
||||
MenuSchema,
|
||||
menuSubmenu,
|
||||
showMenuPaste,
|
||||
useAllowGroup,
|
||||
|
@ -63,7 +63,7 @@ export const ContextMenuSchemaProvider = track(function ContextMenuSchemaProvide
|
|||
if (app.selectedIds.length !== 1) return false
|
||||
return app.selectedIds.some((selectedId) => {
|
||||
const shape = app.getShapeById(selectedId)
|
||||
return shape && TLEmbedShapeDef.is(shape) && shape.props.url
|
||||
return shape && app.isShapeOfType(shape, TLEmbedUtil) && shape.props.url
|
||||
})
|
||||
},
|
||||
[]
|
||||
|
@ -74,7 +74,7 @@ export const ContextMenuSchemaProvider = track(function ContextMenuSchemaProvide
|
|||
if (app.selectedIds.length !== 1) return false
|
||||
return app.selectedIds.some((selectedId) => {
|
||||
const shape = app.getShapeById(selectedId)
|
||||
return shape && TLBookmarkShapeDef.is(shape) && getEmbedInfo(shape.props.url)
|
||||
return shape && app.isShapeOfType(shape, TLBookmarkUtil) && getEmbedInfo(shape.props.url)
|
||||
})
|
||||
},
|
||||
[]
|
||||
|
|
|
@ -3,7 +3,7 @@ import {
|
|||
getSvgAsDataUrl,
|
||||
getSvgAsImage,
|
||||
TLExportType,
|
||||
TLFrameShape,
|
||||
TLFrameUtil,
|
||||
TLShapeId,
|
||||
useApp,
|
||||
} from '@tldraw/editor'
|
||||
|
@ -38,8 +38,8 @@ export function useExportAs() {
|
|||
|
||||
if (ids.length === 1) {
|
||||
const first = app.getShapeById(ids[0])!
|
||||
if (first.type === 'frame') {
|
||||
name = (first as TLFrameShape).props.name ?? 'frame'
|
||||
if (app.isShapeOfType(first, TLFrameUtil)) {
|
||||
name = first.props.name ?? 'frame'
|
||||
} else {
|
||||
name = first.id.replace(/:/, '_')
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useApp } from '@tldraw/editor'
|
||||
import { TLTextUtil, useApp } from '@tldraw/editor'
|
||||
import { useValue } from 'signia-react'
|
||||
|
||||
export function useShowAutoSizeToggle() {
|
||||
|
@ -9,7 +9,7 @@ export function useShowAutoSizeToggle() {
|
|||
const { selectedShapes } = app
|
||||
return (
|
||||
selectedShapes.length === 1 &&
|
||||
selectedShapes[0].type === 'text' &&
|
||||
app.isShapeOfType(selectedShapes[0], TLTextUtil) &&
|
||||
selectedShapes[0].props.autoSize === false
|
||||
)
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue