No impure getters pt7 (#2220)

follow up to #2189 

### Change Type

- [x] `patch` — Bug fix
This commit is contained in:
David Sheldrick 2023-11-14 15:20:59 +00:00 committed by GitHub
parent 7186368f0d
commit 464ba43b51
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 459 additions and 124 deletions

View file

@ -569,12 +569,14 @@ export class Editor extends EventEmitter<TLEventMap> {
tags?: Record<string, boolean | number | string>;
extras?: Record<string, unknown>;
}): this;
// @deprecated (undocumented)
get assets(): (TLBookmarkAsset | TLImageAsset | TLVideoAsset)[];
bail(): this;
bailToMark(id: string): this;
batch(fn: () => void): this;
bringForward(shapes: TLShape[] | TLShapeId[]): this;
bringToFront(shapes: TLShape[] | TLShapeId[]): this;
// @deprecated (undocumented)
get cameraState(): "idle" | "moving";
cancel(): this;
cancelDoubleClick(): void;
@ -681,7 +683,9 @@ export class Editor extends EventEmitter<TLEventMap> {
}[];
getAsset(asset: TLAsset | TLAssetId): TLAsset | undefined;
getAssetForExternalContent(info: TLExternalAssetContent): Promise<TLAsset | undefined>;
getAssets(): (TLBookmarkAsset | TLImageAsset | TLVideoAsset)[];
getCamera(): TLCamera;
getCameraState(): "idle" | "moving";
getCanRedo(): boolean;
getCanUndo(): boolean;
getContainer: () => HTMLElement;
@ -709,11 +713,24 @@ export class Editor extends EventEmitter<TLEventMap> {
getOpenMenus(): string[];
getOutermostSelectableShape(shape: TLShape | TLShapeId, filter?: (shape: TLShape) => boolean): TLShape;
getPage(page: TLPage | TLPageId): TLPage | undefined;
getPages(): TLPage[];
getPageShapeIds(page: TLPage | TLPageId): Set<TLShapeId>;
getPageStates(): TLInstancePageState[];
getPath(): string;
getPointInParentSpace(shape: TLShape | TLShapeId, point: VecLike): Vec2d;
getPointInShapeSpace(shape: TLShape | TLShapeId, point: VecLike): Vec2d;
getRenderingBounds(): Box2d;
getRenderingBoundsExpanded(): Box2d;
getRenderingShapes(): {
id: TLShapeId;
shape: TLShape;
util: ShapeUtil<TLUnknownShape>;
index: number;
backgroundIndex: number;
opacity: number;
isCulled: boolean;
maskedPageBounds: Box2d | undefined;
}[];
getSelectedShapeAtPoint(point: VecLike): TLShape | undefined;
getSelectedShapeIds(): TLShapeId[];
getSelectedShapes(): TLShape[];
@ -827,6 +844,7 @@ export class Editor extends EventEmitter<TLEventMap> {
// @deprecated (undocumented)
get openMenus(): string[];
packShapes(shapes: TLShape[] | TLShapeId[], gap: number): this;
// @deprecated (undocumented)
get pages(): TLPage[];
// @deprecated (undocumented)
get pageStates(): TLInstancePageState[];
@ -853,9 +871,12 @@ export class Editor extends EventEmitter<TLEventMap> {
type: T;
} : TLExternalContent) => void) | null): this;
renamePage(page: TLPage | TLPageId, name: string, historyOptions?: TLCommandHistoryOptions): this;
// @deprecated (undocumented)
get renderingBounds(): Box2d;
// @deprecated (undocumented)
get renderingBoundsExpanded(): Box2d;
renderingBoundsMargin: number;
// @deprecated (undocumented)
get renderingShapes(): {
id: TLShapeId;
shape: TLShape;

View file

@ -7093,7 +7093,7 @@
{
"kind": "Property",
"canonicalReference": "@tldraw/editor!Editor#assets:member",
"docComment": "/**\n * Get all assets in the editor.\n *\n * @public\n */\n",
"docComment": "/**\n * @deprecated\n *\n * Use `getAssets` instead.\n */\n",
"excerptTokens": [
{
"kind": "Content",
@ -7401,7 +7401,7 @@
{
"kind": "Property",
"canonicalReference": "@tldraw/editor!Editor#cameraState:member",
"docComment": "/**\n * Whether the camera is moving or idle.\n *\n * @public\n */\n",
"docComment": "/**\n * @deprecated\n *\n * Use `getCameraState` instead.\n */\n",
"excerptTokens": [
{
"kind": "Content",
@ -9932,6 +9932,64 @@
"isAbstract": false,
"name": "getAssetForExternalContent"
},
{
"kind": "Method",
"canonicalReference": "@tldraw/editor!Editor#getAssets:member(1)",
"docComment": "/**\n * Get all assets in the editor.\n *\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "getAssets(): "
},
{
"kind": "Content",
"text": "(import(\"@tldraw/tlschema\")."
},
{
"kind": "Reference",
"text": "TLBookmarkAsset",
"canonicalReference": "@tldraw/tlschema!TLBookmarkAsset:type"
},
{
"kind": "Content",
"text": " | "
},
{
"kind": "Reference",
"text": "TLImageAsset",
"canonicalReference": "@tldraw/tlschema!TLImageAsset:type"
},
{
"kind": "Content",
"text": " | "
},
{
"kind": "Reference",
"text": "TLVideoAsset",
"canonicalReference": "@tldraw/tlschema!TLVideoAsset:type"
},
{
"kind": "Content",
"text": ")[]"
},
{
"kind": "Content",
"text": ";"
}
],
"isStatic": false,
"returnTypeTokenRange": {
"startIndex": 1,
"endIndex": 8
},
"releaseTag": "Public",
"isProtected": false,
"overloadIndex": 1,
"parameters": [],
"isOptional": false,
"isAbstract": false,
"name": "getAssets"
},
{
"kind": "Method",
"canonicalReference": "@tldraw/editor!Editor#getCamera:member(1)",
@ -9968,6 +10026,37 @@
"isAbstract": false,
"name": "getCamera"
},
{
"kind": "Method",
"canonicalReference": "@tldraw/editor!Editor#getCameraState:member(1)",
"docComment": "/**\n * Whether the camera is moving or idle.\n *\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "getCameraState(): "
},
{
"kind": "Content",
"text": "\"idle\" | \"moving\""
},
{
"kind": "Content",
"text": ";"
}
],
"isStatic": false,
"returnTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
},
"releaseTag": "Public",
"isProtected": false,
"overloadIndex": 1,
"parameters": [],
"isOptional": false,
"isAbstract": false,
"name": "getCameraState"
},
{
"kind": "Method",
"canonicalReference": "@tldraw/editor!Editor#getCanRedo:member(1)",
@ -11115,6 +11204,42 @@
"isAbstract": false,
"name": "getPage"
},
{
"kind": "Method",
"canonicalReference": "@tldraw/editor!Editor#getPages:member(1)",
"docComment": "/**\n * Info about the project's current pages.\n *\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "getPages(): "
},
{
"kind": "Reference",
"text": "TLPage",
"canonicalReference": "@tldraw/tlschema!TLPage:interface"
},
{
"kind": "Content",
"text": "[]"
},
{
"kind": "Content",
"text": ";"
}
],
"isStatic": false,
"returnTypeTokenRange": {
"startIndex": 1,
"endIndex": 3
},
"releaseTag": "Public",
"isProtected": false,
"overloadIndex": 1,
"parameters": [],
"isOptional": false,
"isAbstract": false,
"name": "getPages"
},
{
"kind": "Method",
"canonicalReference": "@tldraw/editor!Editor#getPageShapeIds:member(1)",
@ -11406,6 +11531,146 @@
"isAbstract": false,
"name": "getPointInShapeSpace"
},
{
"kind": "Method",
"canonicalReference": "@tldraw/editor!Editor#getRenderingBounds:member(1)",
"docComment": "/**\n * The current rendering bounds in the current page space, used for checking which shapes are \"on screen\".\n *\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "getRenderingBounds(): "
},
{
"kind": "Reference",
"text": "Box2d",
"canonicalReference": "@tldraw/editor!Box2d:class"
},
{
"kind": "Content",
"text": ";"
}
],
"isStatic": false,
"returnTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
},
"releaseTag": "Public",
"isProtected": false,
"overloadIndex": 1,
"parameters": [],
"isOptional": false,
"isAbstract": false,
"name": "getRenderingBounds"
},
{
"kind": "Method",
"canonicalReference": "@tldraw/editor!Editor#getRenderingBoundsExpanded:member(1)",
"docComment": "/**\n * The current rendering bounds in the current page space, expanded slightly. Used for determining which shapes to render and which to \"cull\".\n *\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "getRenderingBoundsExpanded(): "
},
{
"kind": "Reference",
"text": "Box2d",
"canonicalReference": "@tldraw/editor!Box2d:class"
},
{
"kind": "Content",
"text": ";"
}
],
"isStatic": false,
"returnTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
},
"releaseTag": "Public",
"isProtected": false,
"overloadIndex": 1,
"parameters": [],
"isOptional": false,
"isAbstract": false,
"name": "getRenderingBoundsExpanded"
},
{
"kind": "Method",
"canonicalReference": "@tldraw/editor!Editor#getRenderingShapes:member(1)",
"docComment": "/**\n * Get the shapes that should be displayed in the current viewport.\n *\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "getRenderingShapes(): "
},
{
"kind": "Content",
"text": "{\n id: "
},
{
"kind": "Reference",
"text": "TLShapeId",
"canonicalReference": "@tldraw/tlschema!TLShapeId:type"
},
{
"kind": "Content",
"text": ";\n shape: "
},
{
"kind": "Reference",
"text": "TLShape",
"canonicalReference": "@tldraw/tlschema!TLShape:type"
},
{
"kind": "Content",
"text": ";\n util: "
},
{
"kind": "Reference",
"text": "ShapeUtil",
"canonicalReference": "@tldraw/editor!ShapeUtil:class"
},
{
"kind": "Content",
"text": "<"
},
{
"kind": "Reference",
"text": "TLUnknownShape",
"canonicalReference": "@tldraw/tlschema!TLUnknownShape:type"
},
{
"kind": "Content",
"text": ">;\n index: number;\n backgroundIndex: number;\n opacity: number;\n isCulled: boolean;\n maskedPageBounds: "
},
{
"kind": "Reference",
"text": "Box2d",
"canonicalReference": "@tldraw/editor!Box2d:class"
},
{
"kind": "Content",
"text": " | undefined;\n }[]"
},
{
"kind": "Content",
"text": ";"
}
],
"isStatic": false,
"returnTypeTokenRange": {
"startIndex": 1,
"endIndex": 12
},
"releaseTag": "Public",
"isProtected": false,
"overloadIndex": 1,
"parameters": [],
"isOptional": false,
"isAbstract": false,
"name": "getRenderingShapes"
},
{
"kind": "Method",
"canonicalReference": "@tldraw/editor!Editor#getSelectedShapeAtPoint:member(1)",
@ -15017,7 +15282,7 @@
{
"kind": "Property",
"canonicalReference": "@tldraw/editor!Editor#pages:member",
"docComment": "/**\n * Info about the project's current pages.\n *\n * @public\n */\n",
"docComment": "/**\n * @deprecated\n *\n * Use `getPages` instead.\n */\n",
"excerptTokens": [
{
"kind": "Content",
@ -15787,7 +16052,7 @@
{
"kind": "Property",
"canonicalReference": "@tldraw/editor!Editor#renderingBounds:member",
"docComment": "/**\n * The current rendering bounds in the current page space, used for checking which shapes are \"on screen\".\n *\n * @public\n */\n",
"docComment": "/**\n * @deprecated\n *\n * Use `getRenderingBounds` instead.\n */\n",
"excerptTokens": [
{
"kind": "Content",
@ -15818,7 +16083,7 @@
{
"kind": "Property",
"canonicalReference": "@tldraw/editor!Editor#renderingBoundsExpanded:member",
"docComment": "/**\n * The current rendering bounds in the current page space, expanded slightly. Used for determining which shapes to render and which to \"cull\".\n *\n * @public\n */\n",
"docComment": "/**\n * @deprecated\n *\n * Use `getRenderingBoundsExpanded` instead.\n */\n",
"excerptTokens": [
{
"kind": "Content",
@ -15879,7 +16144,7 @@
{
"kind": "Property",
"canonicalReference": "@tldraw/editor!Editor#renderingShapes:member",
"docComment": "/**\n * Get the shapes that should be displayed in the current viewport.\n *\n * @public\n */\n",
"docComment": "/**\n * @deprecated\n *\n * Use `getRenderingShapes` instead.\n */\n",
"excerptTokens": [
{
"kind": "Content",

View file

@ -316,7 +316,7 @@ function HandleWrapper({
function ShapesWithSVGs() {
const editor = useEditor()
const renderingShapes = useValue('rendering shapes', () => editor.renderingShapes, [editor])
const renderingShapes = useValue('rendering shapes', () => editor.getRenderingShapes(), [editor])
return (
<>
@ -333,7 +333,7 @@ function ShapesWithSVGs() {
function ShapesToDisplay() {
const editor = useEditor()
const renderingShapes = useValue('rendering shapes', () => editor.renderingShapes, [editor])
const renderingShapes = useValue('rendering shapes', () => editor.getRenderingShapes(), [editor])
return (
<>

View file

@ -30,8 +30,8 @@ export const GeometryDebuggingView = track(function GeometryDebuggingView({
useTick(showClosestPointOnOutline)
const zoomLevel = editor.getZoomLevel()
const renderingShapes = editor.getRenderingShapes()
const {
renderingShapes,
inputs: { currentPagePoint },
} = editor

View file

@ -484,7 +484,7 @@ export class Editor extends EventEmitter<TLEventMap> {
// page was deleted, need to check whether it's the current page and select another one if so
if (this.getInstanceState().currentPageId !== record.id) return
const backupPageId = this.pages.find((p) => p.id !== record.id)?.id
const backupPageId = this.getPages().find((p) => p.id !== record.id)?.id
if (!backupPageId) return
this.store.put([{ ...this.getInstanceState(), currentPageId: backupPageId }])
@ -3188,10 +3188,17 @@ export class Editor extends EventEmitter<TLEventMap> {
*
* @public
*/
@computed get cameraState() {
getCameraState() {
return this._cameraState.get()
}
/**
* @deprecated Use `getCameraState` instead.
*/
get cameraState() {
return this.getCameraState()
}
// Camera state does two things: first, it allows us to subscribe to whether
// the camera is moving or not; and second, it allows us to update the rendering
// shapes on the canvas. Changing the rendering shapes may cause shapes to
@ -3264,7 +3271,7 @@ export class Editor extends EventEmitter<TLEventMap> {
const editingShapeId = this.getEditingShapeId()
const selectedShapeIds = this.getSelectedShapeIds()
const erasingShapeIds = this.getErasingShapeIds()
const renderingBoundsExpanded = this.renderingBoundsExpanded
const renderingBoundsExpanded = this.getRenderingBoundsExpanded()
// If renderingBoundsMargin is set to Infinity, then we won't cull offscreen shapes
const isCullingOffScreenShapes = Number.isFinite(this.renderingBoundsMargin)
@ -3344,7 +3351,7 @@ export class Editor extends EventEmitter<TLEventMap> {
*
* @public
*/
@computed get renderingShapes() {
@computed getRenderingShapes() {
const renderingShapes = this.getUnorderedRenderingShapes(true)
// Its IMPORTANT that the result be sorted by id AND include the index
@ -3360,15 +3367,30 @@ export class Editor extends EventEmitter<TLEventMap> {
return renderingShapes.sort(sortById)
}
/**
* @deprecated Use `getRenderingShapes` instead.
*/
get renderingShapes() {
return this.getRenderingShapes()
}
/**
* The current rendering bounds in the current page space, used for checking which shapes are "on screen".
*
* @public
*/
@computed get renderingBounds() {
getRenderingBounds() {
return this._renderingBounds.get()
}
/**
* @deprecated Use `getRenderingBounds` instead.
*/
get renderingBounds() {
return this.getRenderingBounds()
}
/** @internal */
private readonly _renderingBounds = atom('rendering viewport', new Box2d())
@ -3378,10 +3400,17 @@ export class Editor extends EventEmitter<TLEventMap> {
*
* @public
*/
@computed get renderingBoundsExpanded() {
getRenderingBoundsExpanded() {
return this._renderingBoundsExpanded.get()
}
/**
* @deprecated Use `getRenderingBoundsExpanded` instead.
*/
get renderingBoundsExpanded() {
return this.getRenderingBoundsExpanded()
}
/** @internal */
private readonly _renderingBoundsExpanded = atom('rendering viewport expanded', new Box2d())
@ -3422,8 +3451,7 @@ export class Editor extends EventEmitter<TLEventMap> {
/* --------------------- Pages ---------------------- */
/** @internal */
@computed private get _pages() {
@computed private _getAllPagesQuery() {
return this.store.query.records('page')
}
@ -3432,8 +3460,15 @@ export class Editor extends EventEmitter<TLEventMap> {
*
* @public
*/
@computed get pages(): TLPage[] {
return this._pages.get().sort(sortByIndex)
@computed getPages(): TLPage[] {
return this._getAllPagesQuery().get().sort(sortByIndex)
}
/**
* @deprecated Use `getPages` instead.
*/
get pages() {
return this.getPages()
}
/**
@ -3646,8 +3681,8 @@ export class Editor extends EventEmitter<TLEventMap> {
'createPage',
(page: Partial<TLPage>) => {
if (this.getInstanceState().isReadonly) return null
if (this.pages.length >= MAX_PAGES) return null
const { pages } = this
if (this.getPages().length >= MAX_PAGES) return null
const pages = this.getPages()
const name = getIncrementedName(
page.name ?? 'Page',
@ -3689,7 +3724,7 @@ export class Editor extends EventEmitter<TLEventMap> {
this.store.put([newPage, newCamera, newTabPageState])
},
undo: ({ newPage, newTabPageState, newCamera }) => {
if (this.pages.length === 1) return
if (this.getPages().length === 1) return
this.store.remove([newTabPageState.id, newPage.id, newCamera.id])
},
}
@ -3717,7 +3752,7 @@ export class Editor extends EventEmitter<TLEventMap> {
'delete_page',
(id: TLPageId) => {
if (this.getInstanceState().isReadonly) return null
const { pages } = this
const pages = this.getPages()
if (pages.length === 1) return null
const deletedPage = this.getPage(id)
@ -3735,7 +3770,7 @@ export class Editor extends EventEmitter<TLEventMap> {
},
{
do: ({ deletedPage, deletedPageStates }) => {
const { pages } = this
const pages = this.getPages()
if (pages.length === 1) return
if (deletedPage.id === this.currentPageId) {
@ -3765,7 +3800,7 @@ export class Editor extends EventEmitter<TLEventMap> {
* @public
*/
duplicatePage(page: TLPageId | TLPage, createId: TLPageId = PageRecordType.createId()): this {
if (this.pages.length >= MAX_PAGES) return this
if (this.getPages().length >= MAX_PAGES) return this
const id = typeof page === 'string' ? page : page.id
const freshPage = this.getPage(id) // get the most recent version of the page anyway
if (!freshPage) return this
@ -3774,7 +3809,7 @@ export class Editor extends EventEmitter<TLEventMap> {
const content = this.getContentFromCurrentPage(this.getSortedChildIdsForParent(freshPage.id))
this.batch(() => {
const { pages } = this
const pages = this.getPages()
const index = getIndexBetween(freshPage.index, pages[pages.indexOf(freshPage) + 1]?.index)
// create the page (also creates the pagestate and camera for the new page)
@ -3816,7 +3851,7 @@ export class Editor extends EventEmitter<TLEventMap> {
/* --------------------- Assets --------------------- */
/** @internal */
@computed private get _assets() {
@computed private _getAllAssetsQuery() {
return this.store.query.records('asset')
}
@ -3825,8 +3860,15 @@ export class Editor extends EventEmitter<TLEventMap> {
*
* @public
*/
getAssets() {
return this._getAllAssetsQuery().get()
}
/**
* @deprecated Use `getAssets` instead.
*/
get assets() {
return this._assets.get()
return this.getAssets()
}
/**
@ -3972,7 +4014,7 @@ export class Editor extends EventEmitter<TLEventMap> {
/* --------------------- Shapes --------------------- */
@computed
private get _shapeGeometryCache(): ComputedCache<Geometry2d, TLShape> {
private _getShapeGeometryCache(): ComputedCache<Geometry2d, TLShape> {
return this.store.createComputedCache(
'bounds',
(shape) => this.getShapeUtil(shape).getGeometry(shape),
@ -3994,11 +4036,11 @@ export class Editor extends EventEmitter<TLEventMap> {
* @public
*/
getShapeGeometry<T extends Geometry2d>(shape: TLShape | TLShapeId): T {
return this._shapeGeometryCache.get(typeof shape === 'string' ? shape : shape.id)! as T
return this._getShapeGeometryCache().get(typeof shape === 'string' ? shape : shape.id)! as T
}
/** @internal */
@computed private get _shapeOutlineSegmentsCache(): ComputedCache<Vec2d[][], TLShape> {
@computed private _getShapeOutlineSegmentsCache(): ComputedCache<Vec2d[][], TLShape> {
return this.store.createComputedCache('outline-segments', (shape) => {
return this.getShapeUtil(shape).getOutlineSegments(shape)
})
@ -4019,13 +4061,13 @@ export class Editor extends EventEmitter<TLEventMap> {
*/
getShapeOutlineSegments<T extends TLShape>(shape: T | T['id']): Vec2d[][] {
return (
this._shapeOutlineSegmentsCache.get(typeof shape === 'string' ? shape : shape.id) ??
this._getShapeOutlineSegmentsCache().get(typeof shape === 'string' ? shape : shape.id) ??
EMPTY_ARRAY
)
}
/** @internal */
@computed private get _shapeHandlesCache(): ComputedCache<TLHandle[] | undefined, TLShape> {
@computed private _getShapeHandlesCache(): ComputedCache<TLHandle[] | undefined, TLShape> {
return this.store.createComputedCache('handles', (shape) => {
return this.getShapeUtil(shape).getHandles?.(shape)
})
@ -4044,7 +4086,7 @@ export class Editor extends EventEmitter<TLEventMap> {
* @public
*/
getShapeHandles<T extends TLShape>(shape: T | T['id']): TLHandle[] | undefined {
return this._shapeHandlesCache.get(typeof shape === 'string' ? shape : shape.id)
return this._getShapeHandlesCache().get(typeof shape === 'string' ? shape : shape.id)
}
/**
@ -4074,7 +4116,7 @@ export class Editor extends EventEmitter<TLEventMap> {
*
* @internal
*/
@computed private get _shapePageTransformCache(): ComputedCache<Matrix2d, TLShape> {
@computed private _getShapePageTransformCache(): ComputedCache<Matrix2d, TLShape> {
return this.store.createComputedCache<Matrix2d, TLShape>('pageTransformCache', (shape) => {
if (isPageId(shape.parentId)) {
return this.getShapeLocalTransform(shape)
@ -4085,7 +4127,7 @@ export class Editor extends EventEmitter<TLEventMap> {
// In the future we should look at creating a store update mechanism that understands and preserves
// ordering.
const parentTransform =
this._shapePageTransformCache.get(shape.parentId) ?? Matrix2d.Identity()
this._getShapePageTransformCache().get(shape.parentId) ?? Matrix2d.Identity()
return Matrix2d.Compose(parentTransform, this.getShapeLocalTransform(shape)!)
})
}
@ -4106,7 +4148,7 @@ export class Editor extends EventEmitter<TLEventMap> {
const id = typeof shape === 'string' ? shape : shape.id
const freshShape = this.getShape(id)
if (!freshShape || isPageId(freshShape.parentId)) return Matrix2d.Identity()
return this._shapePageTransformCache.get(freshShape.parentId) ?? Matrix2d.Identity()
return this._getShapePageTransformCache().get(freshShape.parentId) ?? Matrix2d.Identity()
}
/**
@ -4124,13 +4166,13 @@ export class Editor extends EventEmitter<TLEventMap> {
*/
getShapePageTransform(shape: TLShape | TLShapeId): Matrix2d {
const id = typeof shape === 'string' ? shape : this.getShape(shape)!.id
return this._shapePageTransformCache.get(id) ?? Matrix2d.Identity()
return this._getShapePageTransformCache().get(id) ?? Matrix2d.Identity()
}
/** @internal */
@computed private get _shapePageBoundsCache(): ComputedCache<Box2d, TLShape> {
@computed private _getShapePageBoundsCache(): ComputedCache<Box2d, TLShape> {
return this.store.createComputedCache<Box2d, TLShape>('pageBoundsCache', (shape) => {
const pageTransform = this._shapePageTransformCache.get(shape.id)
const pageTransform = this._getShapePageTransformCache().get(shape.id)
if (!pageTransform) return new Box2d()
@ -4156,7 +4198,7 @@ export class Editor extends EventEmitter<TLEventMap> {
* @public
*/
getShapePageBounds(shape: TLShape | TLShapeId): Box2d | undefined {
return this._shapePageBoundsCache.get(typeof shape === 'string' ? shape : shape.id)
return this._getShapePageBoundsCache().get(typeof shape === 'string' ? shape : shape.id)
}
/**
@ -4164,15 +4206,15 @@ export class Editor extends EventEmitter<TLEventMap> {
*
* @internal
*/
@computed private get _shapeClipPathCache(): ComputedCache<string, TLShape> {
@computed private _getShapeClipPathCache(): ComputedCache<string, TLShape> {
return this.store.createComputedCache<string, TLShape>('clipPathCache', (shape) => {
const pageMask = this._shapeMaskCache.get(shape.id)
const pageMask = this._getShapeMaskCache().get(shape.id)
if (!pageMask) return undefined
if (pageMask.length === 0) {
return `polygon(0px 0px, 0px 0px, 0px 0px)`
}
const pageTransform = this._shapePageTransformCache.get(shape.id)
const pageTransform = this._getShapePageTransformCache().get(shape.id)
if (!pageTransform) return undefined
const localMask = Matrix2d.applyToPoints(Matrix2d.Inverse(pageTransform), pageMask)
@ -4197,11 +4239,11 @@ export class Editor extends EventEmitter<TLEventMap> {
* @public
*/
getShapeClipPath(shape: TLShape | TLShapeId): string | undefined {
return this._shapeClipPathCache.get(typeof shape === 'string' ? shape : shape.id)
return this._getShapeClipPathCache().get(typeof shape === 'string' ? shape : shape.id)
}
/** @internal */
@computed private get _shapeMaskCache(): ComputedCache<Vec2d[], TLShape> {
@computed private _getShapeMaskCache(): ComputedCache<Vec2d[], TLShape> {
return this.store.createComputedCache('pageMaskCache', (shape) => {
if (isPageId(shape.parentId)) {
return undefined
@ -4216,7 +4258,9 @@ export class Editor extends EventEmitter<TLEventMap> {
const pageMask = frameAncestors
.map<Vec2d[] | undefined>((s) =>
// Apply the frame transform to the frame outline to get the frame outline in the current page space
this._shapePageTransformCache.get(s.id)!.applyToPoints(this.getShapeGeometry(s).vertices)
this._getShapePageTransformCache()
.get(s.id)!
.applyToPoints(this.getShapeGeometry(s).vertices)
)
.reduce((acc, b) => {
if (!(b && acc)) return undefined
@ -4246,7 +4290,7 @@ export class Editor extends EventEmitter<TLEventMap> {
* @public
*/
getShapeMask(shape: TLShapeId | TLShape): VecLike[] | undefined {
return this._shapeMaskCache.get(typeof shape === 'string' ? shape : shape.id)
return this._getShapeMaskCache().get(typeof shape === 'string' ? shape : shape.id)
}
/**
@ -4266,9 +4310,9 @@ export class Editor extends EventEmitter<TLEventMap> {
*/
getShapeMaskedPageBounds(shape: TLShapeId | TLShape): Box2d | undefined {
if (typeof shape !== 'string') shape = shape.id
const pageBounds = this._shapePageBoundsCache.get(shape)
const pageBounds = this._getShapePageBoundsCache().get(shape)
if (!pageBounds) return
const pageMask = this._shapeMaskCache.get(shape)
const pageMask = this._getShapeMaskCache().get(shape)
if (pageMask) {
if (pageMask.length === 0) return undefined
@ -4711,7 +4755,7 @@ export class Editor extends EventEmitter<TLEventMap> {
*/
getPointInShapeSpace(shape: TLShape | TLShapeId, point: VecLike): Vec2d {
const id = typeof shape === 'string' ? shape : shape.id
return this._shapePageTransformCache.get(id)!.clone().invert().applyToPoint(point)
return this._getShapePageTransformCache().get(id)!.clone().invert().applyToPoint(point)
}
/**
@ -4809,7 +4853,7 @@ export class Editor extends EventEmitter<TLEventMap> {
* @public
*/
@computed get currentPageRenderingShapesSorted(): TLShape[] {
return this.renderingShapes
return this.getRenderingShapes()
.filter(({ isCulled }) => !isCulled)
.sort((a, b) => a.index - b.index)
.map(({ shape }) => shape)

View file

@ -248,7 +248,7 @@ export class SnapManager {
// TODO: make this an incremental derivation
@computed get snappableShapes(): GapNode[] {
const { editor } = this
const { renderingBounds: renderingBounds } = editor
const renderingBounds = editor.getRenderingBounds()
const selectedShapeIds = editor.getSelectedShapeIds()
const snappableShapes: GapNode[] = []

View file

@ -15,7 +15,8 @@ export function BackToContent() {
let showBackToContentPrev = false
const interval = setInterval(() => {
const { renderingShapes, renderingBounds } = editor
const renderingShapes = editor.getRenderingShapes()
const renderingBounds = editor.getRenderingBounds()
// renderingShapes will also include shapes that have the canUnmount flag
// set to true. These shapes will be on the canvas but may not be in the

View file

@ -70,7 +70,9 @@ const CurrentState = track(function CurrentState() {
const ShapeCount = function ShapeCount() {
const editor = useEditor()
const count = useValue('rendering shapes count', () => editor.renderingShapes.length, [editor])
const count = useValue('rendering shapes count', () => editor.getRenderingShapes().length, [
editor,
])
return <div>{count} Shapes</div>
}
@ -177,7 +179,8 @@ const DebugMenuContent = track(function DebugMenuContent({
const selectedShapes = editor.getSelectedShapes()
const shapes = selectedShapes.length === 0 ? editor.renderingShapes : selectedShapes
const shapes =
selectedShapes.length === 0 ? editor.getRenderingShapes() : selectedShapes
const elms = shapes.map(
(shape) => (document.getElementById(shape.id) as HTMLElement)!.parentElement!

View file

@ -7,7 +7,7 @@ import { Button } from './primitives/Button'
export const MoveToPageMenu = track(function MoveToPageMenu() {
const editor = useEditor()
const container = useContainer()
const pages = editor.pages
const pages = editor.getPages()
const currentPageId = editor.currentPageId
const msg = useTranslation()
const { addToast } = useToasts()

View file

@ -21,7 +21,7 @@ export const PageItemSubmenu = track(function PageItemSubmenu({
}: PageItemSubmenuProps) {
const editor = useEditor()
const msg = useTranslation()
const pages = editor.pages
const pages = editor.getPages()
const onDuplicate = useCallback(() => {
editor.mark('creating page')

View file

@ -32,7 +32,7 @@ export const PageMenu = function PageMenu() {
const rSortableContainer = useRef<HTMLDivElement>(null)
const pages = useValue('pages', () => editor.pages, [editor])
const pages = useValue('pages', () => editor.getPages(), [editor])
const currentPage = useValue('currentPage', () => editor.currentPage, [editor])
const currentPageId = useValue('currentPageId', () => editor.currentPageId, [editor])
@ -42,7 +42,7 @@ export const PageMenu = function PageMenu() {
// If the user has reached the max page count, we disable the "add page" button
const maxPageCountReached = useValue(
'maxPageCountReached',
() => editor.pages.length >= MAX_PAGES,
() => editor.getPages().length >= MAX_PAGES,
[editor]
)

View file

@ -3,7 +3,7 @@ import { Editor, getIndexAbove, getIndexBelow, getIndexBetween, TLPageId } from
export const onMovePage = (editor: Editor, id: TLPageId, from: number, to: number) => {
let index: string
const pages = editor.pages
const pages = editor.getPages()
const below = from > to ? pages[to - 1] : pages[to]
const above = from > to ? pages[to] : pages[to + 1]

View file

@ -172,7 +172,8 @@ export function usePrint() {
}
const selectedShapeIds = editor.getSelectedShapeIds()
const { pages, currentPageId } = editor
const { currentPageId } = editor
const pages = editor.getPages()
const preserveAspectRatio = 'xMidYMid meet'

View file

@ -36,13 +36,13 @@ export function buildFromV1Document(editor: Editor, document: LegacyTldrawDocume
// Cancel any interactions / states
editor.cancel().cancel().cancel().cancel()
const firstPageId = editor.pages[0].id
const firstPageId = editor.getPages()[0].id
// Set the current page to the first page
editor.setCurrentPage(firstPageId)
// Delete all pages except first page
for (const page of editor.pages.slice(1)) {
for (const page of editor.getPages().slice(1)) {
editor.deletePage(page.id)
}

View file

@ -60,7 +60,7 @@ describe('createSessionStateSnapshotSignal', () => {
editor.createPage({ name: 'new page' })
expect(isGridMode).toBe(true)
expect(editor.pages.length).toBe(2)
expect(editor.getPages().length).toBe(2)
expect(numPages).toBe(2)
})
})

View file

@ -9,11 +9,11 @@ beforeEach(() => {
it('Creates a page', () => {
const oldPageId = editor.currentPageId
const n = editor.pages.length
const n = editor.getPages().length
editor.mark('creating new page')
editor.createPage({ name: 'Page 1' })
expect(editor.pages.length).toBe(n + 1)
const newPageId = editor.pages[n].id
expect(editor.getPages().length).toBe(n + 1)
const newPageId = editor.getPages()[n].id
// does not move to the new page right away
expect(editor.currentPageId).toBe(oldPageId)
@ -22,11 +22,11 @@ it('Creates a page', () => {
expect(editor.currentPageId).toBe(newPageId)
editor.undo()
expect(editor.pages.length).toBe(n)
expect(editor.getPages().length).toBe(n)
expect(editor.currentPageId).toBe(oldPageId)
editor.redo()
expect(editor.pages.length).toBe(n + 1)
expect(editor.getPages().length).toBe(n + 1)
expect(editor.currentPageId).toBe(newPageId)
})
@ -34,12 +34,12 @@ it("Doesn't create a page if max pages is reached", () => {
for (let i = 0; i < MAX_PAGES + 1; i++) {
editor.createPage({ name: `Test Page ${i}` })
}
expect(editor.pages.length).toBe(MAX_PAGES)
expect(editor.getPages().length).toBe(MAX_PAGES)
})
it('[regression] does not die if every page has the same index', () => {
expect(editor.pages.length).toBe(1)
const page = editor.pages[0]
expect(editor.getPages().length).toBe(1)
const page = editor.getPages()[0]
editor.store.put([
{
...page,
@ -58,8 +58,8 @@ it('[regression] does not die if every page has the same index', () => {
},
])
expect(editor.pages.every((p) => p.index === page.index)).toBe(true)
expect(editor.getPages().every((p) => p.index === page.index)).toBe(true)
editor.createPage({ name: 'My Special Test Page' })
expect(editor.pages.some((p) => p.name === 'My Special Test Page')).toBe(true)
expect(editor.getPages().some((p) => p.name === 'My Special Test Page')).toBe(true)
})

View file

@ -12,44 +12,44 @@ describe('deletePage', () => {
const page2Id = PageRecordType.createId('page2')
editor.createPage({ name: 'New Page 2', id: page2Id })
const pages = editor.pages
const pages = editor.getPages()
expect(pages.length).toBe(2)
editor.deletePage(pages[0].id)
expect(editor.pages.length).toBe(1)
expect(editor.pages[0]).toEqual(pages[1])
expect(editor.getPages().length).toBe(1)
expect(editor.getPages()[0]).toEqual(pages[1])
})
it('is undoable and redoable', () => {
const page2Id = PageRecordType.createId('page2')
editor.mark('before creating page')
editor.createPage({ name: 'New Page 2', id: page2Id })
const pages = editor.pages
const pages = editor.getPages()
expect(pages.length).toBe(2)
editor.mark('before deleting page')
editor.deletePage(pages[0].id)
expect(editor.pages.length).toBe(1)
expect(editor.getPages().length).toBe(1)
editor.undo()
expect(editor.pages.length).toBe(2)
expect(editor.pages).toEqual(pages)
expect(editor.getPages().length).toBe(2)
expect(editor.getPages()).toEqual(pages)
editor.redo()
expect(editor.pages.length).toBe(1)
expect(editor.pages[0]).toEqual(pages[1])
expect(editor.getPages().length).toBe(1)
expect(editor.getPages()[0]).toEqual(pages[1])
})
it('does not allow deleting all pages', () => {
const page2Id = PageRecordType.createId('page2')
editor.mark('before creating page')
editor.createPage({ name: 'New Page 2', id: page2Id })
const pages = editor.pages
const pages = editor.getPages()
editor.deletePage(pages[1].id)
editor.deletePage(pages[0].id)
expect(editor.pages.length).toBe(1)
expect(editor.getPages().length).toBe(1)
editor.deletePage(editor.pages[0].id)
expect(editor.pages.length).toBe(1)
editor.deletePage(editor.getPages()[0].id)
expect(editor.getPages().length).toBe(1)
})
it('switches the page if you are deleting the current page', () => {
const page2Id = PageRecordType.createId('page2')
@ -58,9 +58,9 @@ describe('deletePage', () => {
const currentPageId = editor.currentPageId
editor.deletePage(currentPageId)
expect(editor.pages.length).toBe(1)
expect(editor.getPages().length).toBe(1)
expect(editor.currentPageId).not.toBe(currentPageId)
expect(editor.currentPageId).toBe(editor.pages[0].id)
expect(editor.currentPageId).toBe(editor.getPages()[0].id)
})
it('switches the page if another user or tab deletes the current page', () => {
const currentPageId = editor.currentPageId
@ -72,8 +72,8 @@ describe('deletePage', () => {
editor.store.remove([currentPageId])
})
expect(editor.pages.length).toBe(1)
expect(editor.getPages().length).toBe(1)
expect(editor.currentPageId).not.toBe(currentPageId)
expect(editor.currentPageId).toBe(editor.pages[0].id)
expect(editor.currentPageId).toBe(editor.getPages()[0].id)
})
})

View file

@ -16,18 +16,18 @@ beforeEach(() => {
it('Duplicates a page', () => {
const oldPageId = editor.currentPageId
const camera = { ...editor.getCamera() }
const n = editor.pages.length
const n = editor.getPages().length
expect(editor.currentPageShapes.length).toBe(1)
const existingIds = new Set(editor.pages.map((s) => s.id))
const existingIds = new Set(editor.getPages().map((s) => s.id))
editor.duplicatePage(editor.currentPageId)
// Creates the new page
expect(editor.pages.length).toBe(n + 1)
expect(editor.getPages().length).toBe(n + 1)
// Navigates to the new page
const newPageId = editor.pages.find((p) => !existingIds.has(p.id))!.id
const newPageId = editor.getPages().find((p) => !existingIds.has(p.id))!.id
expect(editor.currentPageId).toBe(newPageId)
// Duplicates the shapes
@ -39,11 +39,11 @@ it('Duplicates a page', () => {
expect(editor.getZoomLevel()).toBe(camera.z)
editor.undo()
expect(editor.pages.length).toBe(n)
expect(editor.getPages().length).toBe(n)
expect(editor.currentPageId).toBe(oldPageId)
editor.redo()
expect(editor.pages.length).toBe(n + 1)
expect(editor.getPages().length).toBe(n + 1)
expect(editor.currentPageId).toBe(newPageId)
})
@ -51,5 +51,5 @@ it("Doesn't duplicate the page if max pages is reached", () => {
for (let i = 0; i < MAX_PAGES; i++) {
editor.duplicatePage(editor.currentPageId)
}
expect(editor.pages.length).toBe(MAX_PAGES)
expect(editor.getPages().length).toBe(MAX_PAGES)
})

View file

@ -9,7 +9,7 @@ beforeEach(() => {
describe('setCurrentPage', () => {
it('sets the current page', () => {
const page1Id = editor.pages[0].id
const page1Id = editor.getPages()[0].id
const page2Id = PageRecordType.createId('page2')
editor.createPage({ name: 'New Page 2', id: page2Id })
@ -18,11 +18,11 @@ describe('setCurrentPage', () => {
editor.setCurrentPage(page2Id)
expect(editor.currentPageId).toEqual(page2Id)
expect(editor.currentPage).toEqual(editor.pages[1])
expect(editor.currentPage).toEqual(editor.getPages()[1])
editor.setCurrentPage(page1Id)
expect(editor.currentPage).toEqual(editor.pages[0])
expect(editor.currentPage).toEqual(editor.getPages()[0])
const page3Id = PageRecordType.createId('page3')
editor.createPage({ name: 'New Page 3', id: page3Id })
@ -30,12 +30,12 @@ describe('setCurrentPage', () => {
editor.setCurrentPage(page3Id)
expect(editor.currentPageId).toEqual(page3Id)
expect(editor.currentPage).toEqual(editor.pages[2])
expect(editor.currentPage).toEqual(editor.getPages()[2])
editor.setCurrentPage(editor.pages[0].id)
editor.setCurrentPage(editor.getPages()[0].id)
expect(editor.currentPageId).toEqual(editor.pages[0].id)
expect(editor.currentPage).toEqual(editor.pages[0])
expect(editor.currentPageId).toEqual(editor.getPages()[0].id)
expect(editor.currentPage).toEqual(editor.getPages()[0])
})
it("adding a page to the store by any means adds tab state for the page if it doesn't already exist", () => {
@ -50,9 +50,9 @@ describe('setCurrentPage', () => {
editor.createPage({ name: 'New Page 2', index: page2Id })
editor.history.clear()
editor.setCurrentPage(editor.pages[1].id)
editor.setCurrentPage(editor.pages[0].id)
editor.setCurrentPage(editor.pages[0].id)
editor.setCurrentPage(editor.getPages()[1].id)
editor.setCurrentPage(editor.getPages()[0].id)
editor.setCurrentPage(editor.getPages()[0].id)
expect(editor.history.numUndos).toBe(1)
})
@ -64,9 +64,9 @@ describe('setCurrentPage', () => {
editor.history.clear()
editor.createShapes([{ type: 'geo', id: boxId, props: { w: 100, h: 100 } }])
editor.undo()
editor.setCurrentPage(editor.pages[1].id)
editor.setCurrentPage(editor.pages[0].id)
editor.setCurrentPage(editor.pages[0].id)
editor.setCurrentPage(editor.getPages()[1].id)
editor.setCurrentPage(editor.getPages()[0].id)
editor.setCurrentPage(editor.getPages()[0].id)
expect(editor.getShape(boxId)).toBeUndefined()
expect(editor.history.numUndos).toBe(1)
editor.redo()

View file

@ -56,14 +56,14 @@ it('updates the rendering viewport when the camera stops moving', () => {
jest.advanceTimersByTime(500)
expect(editor.updateRenderingBounds).toHaveBeenCalledTimes(1)
expect(editor.renderingBounds).toMatchObject({ x: 201, y: 201, w: 1800, h: 900 })
expect(editor.getRenderingBounds()).toMatchObject({ x: 201, y: 201, w: 1800, h: 900 })
expect(editor.getShapePageBounds(ids.A)).toMatchObject({ x: 100, y: 100, w: 100, h: 100 })
})
it('lists shapes in viewport', () => {
const ids = createShapes()
editor.selectNone()
expect(editor.renderingShapes.map(({ id, isCulled }) => [id, isCulled])).toStrictEqual([
expect(editor.getRenderingShapes().map(({ id, isCulled }) => [id, isCulled])).toStrictEqual([
[ids.A, false], // A is within the expanded rendering bounds, so should not be culled; and it's in the regular viewport too, so it's on screen.
[ids.B, false],
[ids.C, false],
@ -74,7 +74,7 @@ it('lists shapes in viewport', () => {
editor.pan({ x: -201, y: -201 })
jest.advanceTimersByTime(500)
expect(editor.renderingShapes.map(({ id, isCulled }) => [id, isCulled])).toStrictEqual([
expect(editor.getRenderingShapes().map(({ id, isCulled }) => [id, isCulled])).toStrictEqual([
[ids.A, false], // A should not be culled, even though it's no longer in the viewport (because it's still in the EXPANDED viewport)
[ids.B, false],
[ids.C, false],
@ -84,7 +84,7 @@ it('lists shapes in viewport', () => {
editor.pan({ x: -100, y: -100 })
jest.advanceTimersByTime(500)
expect(editor.renderingShapes.map(({ id, isCulled }) => [id, isCulled])).toStrictEqual([
expect(editor.getRenderingShapes().map(({ id, isCulled }) => [id, isCulled])).toStrictEqual([
[ids.A, true], // A should be culled now that it's outside of the expanded viewport too
[ids.B, false],
[ids.C, false],
@ -93,7 +93,7 @@ it('lists shapes in viewport', () => {
editor.pan({ x: -900, y: -900 })
jest.advanceTimersByTime(500)
expect(editor.renderingShapes.map(({ id, isCulled }) => [id, isCulled])).toStrictEqual([
expect(editor.getRenderingShapes().map(({ id, isCulled }) => [id, isCulled])).toStrictEqual([
[ids.A, true],
[ids.B, true],
[ids.C, true],
@ -104,7 +104,7 @@ it('lists shapes in viewport', () => {
it('lists shapes in viewport sorted by id with correct indexes & background indexes', () => {
const ids = createShapes()
// Expect the results to be sorted correctly by id
expect(normalizeIndexes(editor.renderingShapes)).toStrictEqual([
expect(normalizeIndexes(editor.getRenderingShapes())).toStrictEqual([
[ids.A, 2, 0],
[ids.B, 3, 1],
[ids.C, 6, 4], // the background of C is above B
@ -116,7 +116,7 @@ it('lists shapes in viewport sorted by id with correct indexes & background inde
editor.sendToBack([ids.B])
// The items should still be sorted by id
expect(normalizeIndexes(editor.renderingShapes)).toStrictEqual([
expect(normalizeIndexes(editor.getRenderingShapes())).toStrictEqual([
[ids.A, 7, 1],
[ids.B, 2, 0],
[ids.C, 5, 3],
@ -138,7 +138,7 @@ it('handles frames in frames', () => {
<TL.geo ref="G" x={100} y={0} w={10} h={10} />,
])
expect(normalizeIndexes(editor.renderingShapes)).toStrictEqual([
expect(normalizeIndexes(editor.getRenderingShapes())).toStrictEqual([
[ids.A, 3, 0],
[ids.B, 4, 1],
[ids.C, 8, 5], // frame B creates a background, so C's background layer is above B's foreground
@ -162,7 +162,7 @@ it('handles groups in frames', () => {
<TL.geo ref="G" x={100} y={0} w={10} h={10} />,
])
expect(normalizeIndexes(editor.renderingShapes)).toStrictEqual([
expect(normalizeIndexes(editor.getRenderingShapes())).toStrictEqual([
[ids.A, 3, 0],
[ids.B, 4, 1],
[ids.C, 9, 5], // frame B creates a background, so C's background layer is above B's foreground
@ -186,7 +186,7 @@ it('handles frames in groups', () => {
<TL.geo ref="G" x={100} y={0} w={10} h={10} />,
])
expect(normalizeIndexes(editor.renderingShapes)).toStrictEqual([
expect(normalizeIndexes(editor.getRenderingShapes())).toStrictEqual([
[ids.A, 6, 0],
[ids.B, 7, 1],
[ids.C, 8, 2], // groups don't create backgrounds, so things within the group stay in order
@ -210,7 +210,7 @@ it('handles groups in groups', () => {
<TL.geo ref="G" x={100} y={0} w={10} h={10} />,
])
expect(normalizeIndexes(editor.renderingShapes)).toStrictEqual([
expect(normalizeIndexes(editor.getRenderingShapes())).toStrictEqual([
// as groups don't create backgrounds, everything is consecutive
[ids.A, 7, 0],
[ids.B, 8, 1],

View file

@ -62,7 +62,7 @@ describe('shapeIdsInCurrentPage', () => {
expect(new Set(editor.currentPageShapeIds)).toEqual(new Set([ids.box4, ids.box5, ids.box6]))
editor.setCurrentPage(editor.pages[0].id)
editor.setCurrentPage(editor.getPages()[0].id)
expect(new Set(editor.currentPageShapeIds)).toEqual(new Set([ids.box1, ids.box2, ids.box3]))
})