kopia lustrzana https://github.com/Tldraw/Tldraw
alex/no-batches: no batches
rodzic
3448e53a64
commit
1ecf54f5f7
|
@ -592,7 +592,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
}): this;
|
||||
bail(): this;
|
||||
bailToMark(id: string): this;
|
||||
batch(fn: () => void, opts?: TLHistoryBatchOptions): this;
|
||||
bringForward(shapes: TLShape[] | TLShapeId[]): this;
|
||||
bringToFront(shapes: TLShape[] | TLShapeId[]): this;
|
||||
cancel(): this;
|
||||
|
@ -893,7 +892,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
shapeUtils: {
|
||||
readonly [K in string]?: ShapeUtil<TLUnknownShape>;
|
||||
};
|
||||
readonly sideEffects: SideEffectManager<this>;
|
||||
readonly sideEffects: SideEffectManager<TLRecord>;
|
||||
slideCamera(opts?: {
|
||||
speed: number;
|
||||
direction: VecLike;
|
||||
|
@ -920,9 +919,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
updateAssets(assets: TLAssetPartial[]): this;
|
||||
updateCurrentPageState(partial: Partial<Omit<TLInstancePageState, 'editingShapeId' | 'focusedGroupId' | 'pageId' | 'selectedShapeIds'>>, historyOptions?: TLHistoryBatchOptions): this;
|
||||
// (undocumented)
|
||||
_updateCurrentPageState: (partial: Partial<Omit<TLInstancePageState, 'selectedShapeIds'>>, historyOptions?: TLHistoryBatchOptions) => void;
|
||||
_updateCurrentPageState: (partial: Partial<Omit<TLInstancePageState, 'selectedShapeIds'>>, options?: TLHistoryBatchOptions) => void;
|
||||
updateDocumentSettings(settings: Partial<TLDocument>): this;
|
||||
updateInstanceState(partial: Partial<Omit<TLInstance, 'currentPageId'>>, historyOptions?: TLHistoryBatchOptions): this;
|
||||
updateInstanceState(partial: Partial<Omit<TLInstance, 'currentPageId'>>, options?: TLHistoryBatchOptions): this;
|
||||
updatePage(partial: RequiredKeys<TLPage, 'id'>): this;
|
||||
// @internal
|
||||
updateRenderingBounds(): this;
|
||||
|
@ -1202,8 +1201,6 @@ export class HistoryManager<R extends UnknownRecord> {
|
|||
// (undocumented)
|
||||
bailToMark: (id: string) => this;
|
||||
// (undocumented)
|
||||
batch: (fn: () => void, opts?: TLHistoryBatchOptions) => this;
|
||||
// (undocumented)
|
||||
clear(): void;
|
||||
// @internal (undocumented)
|
||||
debug(): {
|
||||
|
@ -1213,7 +1210,7 @@ export class HistoryManager<R extends UnknownRecord> {
|
|||
diff: RecordsDiff<R>;
|
||||
isEmpty: boolean;
|
||||
};
|
||||
state: HistoryRecorderState;
|
||||
state: TLHistoryMode;
|
||||
};
|
||||
// (undocumented)
|
||||
readonly dispose: () => void;
|
||||
|
@ -1223,14 +1220,16 @@ export class HistoryManager<R extends UnknownRecord> {
|
|||
getNumUndos(): number;
|
||||
// (undocumented)
|
||||
ignore(fn: () => void): this;
|
||||
// @internal (undocumented)
|
||||
_isInBatch: boolean;
|
||||
// (undocumented)
|
||||
mark: (id?: string) => string;
|
||||
// (undocumented)
|
||||
onBatchComplete: () => void;
|
||||
record(fn: () => void): this;
|
||||
// (undocumented)
|
||||
recordPreservingRedoStack(fn: () => void): this;
|
||||
// (undocumented)
|
||||
redo: () => this | undefined;
|
||||
// (undocumented)
|
||||
runInMode(mode: null | TLHistoryMode | undefined, fn: () => void): this;
|
||||
// @internal (undocumented)
|
||||
stacks: Atom< {
|
||||
undos: Stack<TLHistoryEntry<R>>;
|
||||
|
@ -1745,45 +1744,42 @@ export class SharedStyleMap extends ReadonlySharedStyleMap {
|
|||
export function shortAngleDist(a0: number, a1: number): number;
|
||||
|
||||
// @public
|
||||
export class SideEffectManager<CTX extends {
|
||||
store: TLStore;
|
||||
history: {
|
||||
onBatchComplete: () => void;
|
||||
};
|
||||
}> {
|
||||
constructor(editor: CTX);
|
||||
// (undocumented)
|
||||
editor: CTX;
|
||||
export class SideEffectManager<R extends UnknownRecord> {
|
||||
constructor(store: Store<R>);
|
||||
// @internal
|
||||
register(handlersByType: {
|
||||
[R in TLRecord as R['typeName']]?: {
|
||||
beforeCreate?: TLBeforeCreateHandler<R>;
|
||||
afterCreate?: TLAfterCreateHandler<R>;
|
||||
beforeChange?: TLBeforeChangeHandler<R>;
|
||||
afterChange?: TLAfterChangeHandler<R>;
|
||||
beforeDelete?: TLBeforeDeleteHandler<R>;
|
||||
afterDelete?: TLAfterDeleteHandler<R>;
|
||||
[T in R as T['typeName']]?: {
|
||||
beforeCreate?: TLBeforeCreateHandler<T>;
|
||||
afterCreate?: TLAfterCreateHandler<T>;
|
||||
beforeChange?: TLBeforeChangeHandler<T>;
|
||||
afterChange?: TLAfterChangeHandler<T>;
|
||||
beforeDelete?: TLBeforeDeleteHandler<T>;
|
||||
afterDelete?: TLAfterDeleteHandler<T>;
|
||||
};
|
||||
} & {
|
||||
complete?: TLCompleteHandler;
|
||||
}): () => void;
|
||||
registerAfterChangeHandler<T extends TLRecord['typeName']>(typeName: T, handler: TLAfterChangeHandler<TLRecord & {
|
||||
registerAfterChangeHandler<T extends R['typeName']>(typeName: T, handler: TLAfterChangeHandler<R & {
|
||||
typeName: T;
|
||||
}>): () => void;
|
||||
registerAfterCreateHandler<T extends TLRecord['typeName']>(typeName: T, handler: TLAfterCreateHandler<TLRecord & {
|
||||
registerAfterCreateHandler<T extends R['typeName']>(typeName: T, handler: TLAfterCreateHandler<R & {
|
||||
typeName: T;
|
||||
}>): () => void;
|
||||
registerAfterDeleteHandler<T extends TLRecord['typeName']>(typeName: T, handler: TLAfterDeleteHandler<TLRecord & {
|
||||
registerAfterDeleteHandler<T extends R['typeName']>(typeName: T, handler: TLAfterDeleteHandler<R & {
|
||||
typeName: T;
|
||||
}>): () => void;
|
||||
registerBatchCompleteHandler(handler: TLBatchCompleteHandler): () => void;
|
||||
registerBeforeChangeHandler<T extends TLRecord['typeName']>(typeName: T, handler: TLBeforeChangeHandler<TLRecord & {
|
||||
registerBeforeChangeHandler<T extends R['typeName']>(typeName: T, handler: TLBeforeChangeHandler<R & {
|
||||
typeName: T;
|
||||
}>): () => void;
|
||||
registerBeforeCreateHandler<T extends TLRecord['typeName']>(typeName: T, handler: TLBeforeCreateHandler<TLRecord & {
|
||||
registerBeforeCreateHandler<T extends R['typeName']>(typeName: T, handler: TLBeforeCreateHandler<R & {
|
||||
typeName: T;
|
||||
}>): () => void;
|
||||
registerBeforeDeleteHandler<T extends TLRecord['typeName']>(typeName: T, handler: TLBeforeDeleteHandler<TLRecord & {
|
||||
registerBeforeDeleteHandler<T extends R['typeName']>(typeName: T, handler: TLBeforeDeleteHandler<R & {
|
||||
typeName: T;
|
||||
}>): () => void;
|
||||
registerCompleteHandler(handler: TLCompleteHandler): () => void;
|
||||
// (undocumented)
|
||||
readonly store: Store<R>;
|
||||
}
|
||||
|
||||
export { Signal }
|
||||
|
@ -1945,13 +1941,13 @@ export interface SvgExportDef {
|
|||
export const TAB_ID: string;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLAfterChangeHandler<R extends TLRecord> = (prev: R, next: R, source: 'remote' | 'user') => void;
|
||||
export type TLAfterChangeHandler<R extends UnknownRecord> = (prev: R, next: R, source: 'remote' | 'user') => void;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLAfterCreateHandler<R extends TLRecord> = (record: R, source: 'remote' | 'user') => void;
|
||||
export type TLAfterCreateHandler<R extends UnknownRecord> = (record: R, source: 'remote' | 'user') => void;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLAfterDeleteHandler<R extends TLRecord> = (record: R, source: 'remote' | 'user') => void;
|
||||
export type TLAfterDeleteHandler<R extends UnknownRecord> = (record: R, source: 'remote' | 'user') => void;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLAnimationOptions = Partial<{
|
||||
|
@ -2022,16 +2018,13 @@ export interface TLBaseEventInfo {
|
|||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLBatchCompleteHandler = () => void;
|
||||
export type TLBeforeChangeHandler<R extends UnknownRecord> = (prev: R, next: R, source: 'remote' | 'user') => R;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLBeforeChangeHandler<R extends TLRecord> = (prev: R, next: R, source: 'remote' | 'user') => R;
|
||||
export type TLBeforeCreateHandler<R extends UnknownRecord> = (record: R, source: 'remote' | 'user') => R;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLBeforeCreateHandler<R extends TLRecord> = (record: R, source: 'remote' | 'user') => R;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLBeforeDeleteHandler<R extends TLRecord> = (record: R, source: 'remote' | 'user') => false | void;
|
||||
export type TLBeforeDeleteHandler<R extends UnknownRecord> = (record: R, source: 'remote' | 'user') => false | void;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLBrushProps = {
|
||||
|
@ -2232,8 +2225,6 @@ export interface TLEventMap {
|
|||
mount: [];
|
||||
// (undocumented)
|
||||
tick: [number];
|
||||
// (undocumented)
|
||||
update: [];
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
|
|
Plik diff jest za duży
Load Diff
|
@ -139,7 +139,6 @@ export type {
|
|||
TLAfterChangeHandler,
|
||||
TLAfterCreateHandler,
|
||||
TLAfterDeleteHandler,
|
||||
TLBatchCompleteHandler,
|
||||
TLBeforeChangeHandler,
|
||||
TLBeforeCreateHandler,
|
||||
TLBeforeDeleteHandler,
|
||||
|
|
|
@ -57,6 +57,7 @@ import {
|
|||
sortByIndex,
|
||||
structuredClone,
|
||||
} from '@tldraw/utils'
|
||||
import { Number } from 'core-js'
|
||||
import { EventEmitter } from 'eventemitter3'
|
||||
import { flushSync } from 'react-dom'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
|
@ -445,10 +446,13 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
return nextPageState
|
||||
}
|
||||
|
||||
this.sideEffects = new SideEffectManager(this)
|
||||
this.sideEffects = new SideEffectManager(this.store)
|
||||
|
||||
this.disposables.add(
|
||||
this.sideEffects.registerBatchCompleteHandler(() => {
|
||||
this.sideEffects.register({
|
||||
complete: (source) => {
|
||||
if (source !== 'user') return
|
||||
|
||||
for (const parentId of invalidParents) {
|
||||
invalidParents.delete(parentId)
|
||||
const parent = this.getShape(parentId)
|
||||
|
@ -461,19 +465,23 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
this.updateShapes(changes)
|
||||
}
|
||||
}
|
||||
|
||||
this.emit('update')
|
||||
})
|
||||
)
|
||||
|
||||
this.disposables.add(
|
||||
this.sideEffects.register({
|
||||
},
|
||||
shape: {
|
||||
beforeCreate: (shape, source) => {
|
||||
if (source !== 'user') return shape
|
||||
const modified = this.getShapeUtil(shape).onBeforeCreate?.(shape)
|
||||
return modified ?? shape
|
||||
},
|
||||
afterCreate: (record) => {
|
||||
if (this.isShapeOfType<TLArrowShape>(record, 'arrow')) {
|
||||
arrowDidUpdate(record)
|
||||
}
|
||||
},
|
||||
beforeChange: (prev, next, source) => {
|
||||
if (source !== 'user') return next
|
||||
const modified = this.getShapeUtil(next).onBeforeUpdate?.(prev, next)
|
||||
return modified ?? next
|
||||
},
|
||||
afterChange: (prev, next) => {
|
||||
if (this.isShapeOfType<TLArrowShape>(next, 'arrow')) {
|
||||
arrowDidUpdate(next)
|
||||
|
@ -764,7 +772,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
*
|
||||
* @public
|
||||
*/
|
||||
readonly sideEffects: SideEffectManager<this>
|
||||
readonly sideEffects: SideEffectManager<TLRecord>
|
||||
|
||||
/**
|
||||
* Dispose the editor.
|
||||
|
@ -918,28 +926,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a function in a batch, which will be undone/redone as a single action.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* editor.batch(() => {
|
||||
* editor.selectAll()
|
||||
* editor.deleteShapes(editor.getSelectedShapeIds())
|
||||
* editor.createShapes(myShapes)
|
||||
* editor.selectNone()
|
||||
* })
|
||||
*
|
||||
* editor.undo() // will undo all of the above
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
batch(fn: () => void, opts?: TLHistoryBatchOptions): this {
|
||||
this.history.batch(fn, opts)
|
||||
return this
|
||||
}
|
||||
|
||||
/* --------------------- Arrows --------------------- */
|
||||
// todo: move these to tldraw or replace with a bindings API
|
||||
|
||||
|
@ -1244,9 +1230,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
*/
|
||||
updateInstanceState(
|
||||
partial: Partial<Omit<TLInstance, 'currentPageId'>>,
|
||||
historyOptions?: TLHistoryBatchOptions
|
||||
options?: TLHistoryBatchOptions
|
||||
): this {
|
||||
this._updateInstanceState(partial, { history: 'ignore', ...historyOptions })
|
||||
this._updateInstanceState(partial, { history: 'ignore', ...options })
|
||||
|
||||
if (partial.isChangingStyle !== undefined) {
|
||||
clearTimeout(this._isChangingStyleTimeout)
|
||||
|
@ -1266,14 +1252,14 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
partial: Partial<Omit<TLInstance, 'currentPageId'>>,
|
||||
opts?: TLHistoryBatchOptions
|
||||
) => {
|
||||
this.batch(() => {
|
||||
this.history.runInMode(opts?.history, () => {
|
||||
this.store.put([
|
||||
{
|
||||
...this.getInstanceState(),
|
||||
...partial,
|
||||
},
|
||||
])
|
||||
}, opts)
|
||||
})
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
|
@ -1435,14 +1421,14 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
}
|
||||
_updateCurrentPageState = (
|
||||
partial: Partial<Omit<TLInstancePageState, 'selectedShapeIds'>>,
|
||||
historyOptions?: TLHistoryBatchOptions
|
||||
options?: TLHistoryBatchOptions
|
||||
) => {
|
||||
this.batch(() => {
|
||||
this.history.runInMode(options?.history, () => {
|
||||
this.store.update(partial.id ?? this.getCurrentPageState().id, (state) => ({
|
||||
...state,
|
||||
...partial,
|
||||
}))
|
||||
}, historyOptions)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1479,8 +1465,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
* @public
|
||||
*/
|
||||
setSelectedShapes(shapes: TLShapeId[] | TLShape[]): this {
|
||||
return this.batch(
|
||||
() => {
|
||||
this.history.recordPreservingRedoStack(() => {
|
||||
const ids = shapes.map((shape) => (typeof shape === 'string' ? shape : shape.id))
|
||||
const { selectedShapeIds: prevSelectedShapeIds } = this.getCurrentPageState()
|
||||
const prevSet = new Set(prevSelectedShapeIds)
|
||||
|
@ -1488,9 +1473,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
if (ids.length === prevSet.size && ids.every((id) => prevSet.has(id))) return
|
||||
|
||||
this.store.put([{ ...this.getCurrentPageState(), selectedShapeIds: ids }])
|
||||
},
|
||||
{ history: 'record-preserveRedoStack' }
|
||||
)
|
||||
})
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1747,12 +1731,10 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
|
||||
if (id === this.getFocusedGroupId()) return this
|
||||
|
||||
return this.batch(
|
||||
() => {
|
||||
this.history.recordPreservingRedoStack(() => {
|
||||
this.store.update(this.getCurrentPageState().id, (s) => ({ ...s, focusedGroupId: id }))
|
||||
},
|
||||
{ history: 'record-preserveRedoStack' }
|
||||
)
|
||||
})
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2049,7 +2031,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
return this
|
||||
}
|
||||
|
||||
this.batch(() => {
|
||||
const camera = { ...currentCamera, ...point }
|
||||
this.store.put([camera]) // include id and meta here
|
||||
|
||||
|
@ -2085,7 +2066,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
}
|
||||
|
||||
this._tickCameraState()
|
||||
})
|
||||
|
||||
return this
|
||||
}
|
||||
|
@ -2108,7 +2088,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
setCamera(point: VecLike, animation?: TLAnimationOptions): this {
|
||||
const x = Number.isFinite(point.x) ? point.x : 0
|
||||
const y = Number.isFinite(point.y) ? point.y : 0
|
||||
const z = Number.isFinite(point.z) ? point.z! : this.getZoomLevel()
|
||||
const z = Number.isFinite(point.z!) ? point.z! : this.getZoomLevel()
|
||||
|
||||
// Stop any camera animations
|
||||
this.stopCameraAnimation()
|
||||
|
@ -2620,7 +2600,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
|
||||
if (!presence) return this
|
||||
|
||||
this.batch(() => {
|
||||
// If we're following someone, stop following them
|
||||
if (this.getInstanceState().followingUserId !== null) {
|
||||
this.stopFollowingUser()
|
||||
|
@ -2649,7 +2628,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
highlightedUserIds.splice(index, 1)
|
||||
this.updateInstanceState({ highlightedUserIds })
|
||||
}, COLLABORATOR_IDLE_TIMEOUT)
|
||||
})
|
||||
|
||||
return this
|
||||
}
|
||||
|
@ -3303,10 +3281,11 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
|
||||
this.stopFollowingUser()
|
||||
|
||||
return this.batch(
|
||||
() => this.store.put([{ ...this.getInstanceState(), currentPageId: pageId }]),
|
||||
{ history: 'record-preserveRedoStack' }
|
||||
this.history.recordPreservingRedoStack(() =>
|
||||
this.store.put([{ ...this.getInstanceState(), currentPageId: pageId }])
|
||||
)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -3327,7 +3306,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
const prev = this.getPage(partial.id)
|
||||
if (!prev) return this
|
||||
|
||||
return this.batch(() => this.store.update(partial.id, (page) => ({ ...page, ...partial })))
|
||||
this.store.update(partial.id, (page) => ({ ...page, ...partial }))
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -3344,9 +3325,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
* @public
|
||||
*/
|
||||
createPage(page: Partial<TLPage>): this {
|
||||
this.history.batch(() => {
|
||||
if (this.getInstanceState().isReadonly) return
|
||||
if (this.getPages().length >= MAX_PAGES) return
|
||||
if (this.getInstanceState().isReadonly) return this
|
||||
if (this.getPages().length >= MAX_PAGES) return this
|
||||
const pages = this.getPages()
|
||||
|
||||
const name = getIncrementedName(
|
||||
|
@ -3368,7 +3348,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
})
|
||||
|
||||
this.store.put([newPage])
|
||||
})
|
||||
return this
|
||||
}
|
||||
|
||||
|
@ -3386,13 +3365,12 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
*/
|
||||
deletePage(page: TLPageId | TLPage): this {
|
||||
const id = typeof page === 'string' ? page : page.id
|
||||
this.batch(() => {
|
||||
if (this.getInstanceState().isReadonly) return
|
||||
if (this.getInstanceState().isReadonly) return this
|
||||
const pages = this.getPages()
|
||||
if (pages.length === 1) return
|
||||
if (pages.length === 1) return this
|
||||
|
||||
const deletedPage = this.getPage(id)
|
||||
if (!deletedPage) return
|
||||
if (!deletedPage) return this
|
||||
|
||||
if (id === this.getCurrentPageId()) {
|
||||
const index = pages.findIndex((page) => page.id === id)
|
||||
|
@ -3402,7 +3380,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
|
||||
this.store.remove([deletedPage.id])
|
||||
this.updateRenderingBounds()
|
||||
})
|
||||
return this
|
||||
}
|
||||
|
||||
|
@ -3423,7 +3400,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
const prevCamera = { ...this.getCamera() }
|
||||
const content = this.getContentFromCurrentPage(this.getSortedChildIdsForParent(freshPage.id))
|
||||
|
||||
this.batch(() => {
|
||||
const pages = this.getPages()
|
||||
const index = getIndexBetween(freshPage.index, pages[pages.indexOf(freshPage) + 1]?.index)
|
||||
|
||||
|
@ -3438,7 +3414,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
// If we had content on the previous page, put it on the new page
|
||||
return this.putContentOntoCurrentPage(content)
|
||||
}
|
||||
})
|
||||
|
||||
return this
|
||||
}
|
||||
|
@ -3494,7 +3469,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
createAssets(assets: TLAsset[]): this {
|
||||
if (this.getInstanceState().isReadonly) return this
|
||||
if (assets.length <= 0) return this
|
||||
return this.batch(() => this.store.put(assets))
|
||||
this.store.put(assets)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -3512,14 +3488,14 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
updateAssets(assets: TLAssetPartial[]): this {
|
||||
if (this.getInstanceState().isReadonly) return this
|
||||
if (assets.length <= 0) return this
|
||||
return this.batch(() => {
|
||||
this.store.put(
|
||||
assets.map((partial) => ({
|
||||
...this.store.get(partial.id)!,
|
||||
...partial,
|
||||
}))
|
||||
)
|
||||
})
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -3543,7 +3519,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
: (assets as TLAsset[]).map((a) => a.id)
|
||||
if (ids.length <= 0) return this
|
||||
|
||||
return this.batch(() => this.store.remove(ids))
|
||||
this.store.remove(ids)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -4898,7 +4875,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
*
|
||||
* @param shapes - The shapes (or shape ids) to move.
|
||||
* @param direction - The direction in which to move the shapes.
|
||||
* @param historyOptions - The history options for the change.
|
||||
*/
|
||||
nudgeShapes(shapes: TLShapeId[] | TLShape[], offset: VecLike): this {
|
||||
const ids =
|
||||
|
@ -5081,7 +5057,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
}
|
||||
})
|
||||
|
||||
this.history.batch(() => {
|
||||
const maxShapesReached =
|
||||
shapesToCreate.length + this.getCurrentPageShapeIds().size > MAX_SHAPES_PER_PAGE
|
||||
|
||||
|
@ -5093,10 +5068,10 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
? shapesToCreate.slice(0, MAX_SHAPES_PER_PAGE - this.getCurrentPageShapeIds().size)
|
||||
: shapesToCreate
|
||||
|
||||
const ids = newShapes.map((s) => s.id)
|
||||
const newShapeIds = newShapes.map((s) => s.id)
|
||||
|
||||
this.createShapes(newShapes)
|
||||
this.setSelectedShapes(ids)
|
||||
this.setSelectedShapes(newShapeIds)
|
||||
|
||||
if (offset !== undefined) {
|
||||
// If we've offset the duplicated shapes, check to see whether their new bounds is entirely
|
||||
|
@ -5110,7 +5085,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return this
|
||||
}
|
||||
|
@ -5157,7 +5131,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
|
||||
const fromPageZ = this.getCamera().z
|
||||
|
||||
this.history.batch(() => {
|
||||
// Delete the shapes on the current page
|
||||
this.deleteShapes(ids)
|
||||
|
||||
|
@ -5180,7 +5153,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
// pasted shapes
|
||||
this.setCamera({ ...this.getCamera(), z: fromPageZ })
|
||||
this.centerOnPoint(this.getSelectionRotatedPageBounds()!.center)
|
||||
})
|
||||
|
||||
return this
|
||||
}
|
||||
|
@ -5214,7 +5186,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
}
|
||||
}
|
||||
}
|
||||
this.batch(() => {
|
||||
if (allUnlocked) {
|
||||
this.updateShapes(
|
||||
shapesToToggle.map((shape) => ({ id: shape.id, type: shape.type, isLocked: true }))
|
||||
|
@ -5229,7 +5200,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
shapesToToggle.map((shape) => ({ id: shape.id, type: shape.type, isLocked: true }))
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
return this
|
||||
}
|
||||
|
@ -5368,7 +5338,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
compact(shapesToFlip.map((id) => this.getShapePageBounds(id)))
|
||||
).center
|
||||
|
||||
this.batch(() => {
|
||||
for (const shape of shapesToFlip) {
|
||||
const bounds = this.getShapeGeometry(shape).bounds
|
||||
const initialPageTransform = this.getShapePageTransform(shape.id)
|
||||
|
@ -5386,7 +5355,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
return this
|
||||
}
|
||||
|
@ -5889,7 +5857,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
|
||||
switch (operation) {
|
||||
case 'vertical': {
|
||||
this.batch(() => {
|
||||
for (const shape of shapesToStretch) {
|
||||
const pageRotation = this.getShapePageTransform(shape)!.rotation()
|
||||
if (pageRotation % PI2) continue
|
||||
|
@ -5908,11 +5875,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
scaleAxisRotation: 0,
|
||||
})
|
||||
}
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'horizontal': {
|
||||
this.batch(() => {
|
||||
for (const shape of shapesToStretch) {
|
||||
const bounds = shapeBounds[shape.id]
|
||||
const pageBounds = shapePageBounds[shape.id]
|
||||
|
@ -5931,7 +5896,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
scaleAxisRotation: 0,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
break
|
||||
}
|
||||
|
@ -6254,7 +6218,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
|
||||
const focusedGroupId = this.getFocusedGroupId()
|
||||
|
||||
return this.batch(() => {
|
||||
// 1. Parents
|
||||
|
||||
// Make sure that each partial will become the child of either the
|
||||
|
@ -6376,7 +6339,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
|
||||
// When we create the shape, take in the partial (the props coming into the
|
||||
// function) and merge it with the default props.
|
||||
let shapeRecordToCreate = (
|
||||
const shapeRecordToCreate = (
|
||||
this.store.schema.types.shape as RecordType<
|
||||
TLShape,
|
||||
'type' | 'props' | 'index' | 'parentId'
|
||||
|
@ -6393,12 +6356,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
throw Error('no index!')
|
||||
}
|
||||
|
||||
const next = this.getShapeUtil(shapeRecordToCreate).onBeforeCreate?.(shapeRecordToCreate)
|
||||
|
||||
if (next) {
|
||||
shapeRecordToCreate = next
|
||||
}
|
||||
|
||||
shapeRecordsToCreate.push(shapeRecordToCreate)
|
||||
}
|
||||
|
||||
|
@ -6411,7 +6368,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
})
|
||||
|
||||
this.store.put(shapeRecordsToCreate)
|
||||
})
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
private animatingShapes = new Map<TLShapeId, string>()
|
||||
|
@ -6588,7 +6546,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
|
||||
const highestIndex = shapesWithRootParent[shapesWithRootParent.length - 1]?.index
|
||||
|
||||
this.batch(() => {
|
||||
this.createShapes<TLGroupShape>([
|
||||
{
|
||||
id: groupId,
|
||||
|
@ -6603,7 +6560,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
])
|
||||
this.reparentShapes(sortedShapeIds, groupId)
|
||||
this.select(groupId)
|
||||
})
|
||||
|
||||
return this
|
||||
}
|
||||
|
@ -6651,7 +6607,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
|
||||
if (groups.length === 0) return this
|
||||
|
||||
this.batch(() => {
|
||||
let group: TLGroupShape
|
||||
|
||||
for (let i = 0, n = groups.length; i < n; i++) {
|
||||
|
@ -6667,7 +6622,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
|
||||
this.deleteShapes(groups.map((group) => group.id))
|
||||
this.select(...idsToSelect)
|
||||
})
|
||||
|
||||
return this
|
||||
}
|
||||
|
@ -6728,7 +6682,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
private _updateShapes = (_partials: (TLShapePartial | null | undefined)[]) => {
|
||||
if (this.getInstanceState().isReadonly) return
|
||||
|
||||
this.batch(() => {
|
||||
const updates = []
|
||||
|
||||
let shape: TLShape | undefined
|
||||
|
@ -6749,16 +6702,10 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
updated = applyPartialToShape(shape, partial)
|
||||
if (updated === shape) continue
|
||||
|
||||
//if any shape has an onBeforeUpdate handler, call it and, if the handler returns a
|
||||
// new shape, replace the old shape with the new one. This is used for example when
|
||||
// repositioning a text shape based on its new text content.
|
||||
updated = this.getShapeUtil(shape).onBeforeUpdate?.(shape, updated) ?? updated
|
||||
|
||||
updates.push(updated)
|
||||
}
|
||||
|
||||
this.store.put(updates)
|
||||
})
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
|
@ -6801,7 +6748,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
}
|
||||
|
||||
const deletedIds = [...allIds]
|
||||
return this.batch(() => this.store.remove(deletedIds))
|
||||
this.store.remove(deletedIds)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -7625,7 +7573,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
})
|
||||
)
|
||||
|
||||
this.batch(() => {
|
||||
// Create any assets that need to be created
|
||||
if (assetsToCreate.length > 0) {
|
||||
this.createAssets(assetsToCreate)
|
||||
|
@ -7702,7 +7649,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
return { id: s.id, type: s.type, x: s.x + localDelta.x, y: s.y + localDelta.y }
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
return this
|
||||
}
|
||||
|
@ -8002,7 +7948,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
private _pendingEventsForNextTick: TLEventInfo[] = []
|
||||
|
||||
private _flushEventsForTick = (elapsed: number) => {
|
||||
this.batch(() => {
|
||||
if (this._pendingEventsForNextTick.length > 0) {
|
||||
const events = [...this._pendingEventsForNextTick]
|
||||
this._pendingEventsForNextTick.length = 0
|
||||
|
@ -8014,7 +7959,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
this.root.handleEvent({ type: 'misc', name: 'tick', elapsed })
|
||||
}
|
||||
this.scribbles.tick(elapsed)
|
||||
})
|
||||
}
|
||||
|
||||
private _flushEventForTick = (info: TLEventInfo) => {
|
||||
|
|
|
@ -61,14 +61,12 @@ function createCounterHistoryManager() {
|
|||
}
|
||||
|
||||
const setAge = (age = 35) => {
|
||||
manager.batch(() => _setAge(age), { history: 'record-preserveRedoStack' })
|
||||
manager.recordPreservingRedoStack(() => _setAge(age))
|
||||
}
|
||||
|
||||
const incrementTwice = () => {
|
||||
manager.batch(() => {
|
||||
increment()
|
||||
increment()
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -290,12 +288,12 @@ describe('history options', () => {
|
|||
return { a: store.get(ids.a)!.value as number, b: store.get(ids.b)!.value as number }
|
||||
}
|
||||
|
||||
setA = (n: number, historyOptions?: TLHistoryBatchOptions) => {
|
||||
manager.batch(() => store.update(ids.a, (s) => ({ ...s, value: n })), historyOptions)
|
||||
setA = (n: number, opts?: TLHistoryBatchOptions) => {
|
||||
manager.runInMode(opts?.history, () => store.update(ids.a, (s) => ({ ...s, value: n })))
|
||||
}
|
||||
|
||||
setB = (n: number, historyOptions?: TLHistoryBatchOptions) => {
|
||||
manager.batch(() => store.update(ids.b, (s) => ({ ...s, value: n })), historyOptions)
|
||||
setB = (n: number, opts?: TLHistoryBatchOptions) => {
|
||||
manager.runInMode(opts?.history, () => store.update(ids.b, (s) => ({ ...s, value: n })))
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -398,14 +396,11 @@ describe('history options', () => {
|
|||
|
||||
it('nested ignore', () => {
|
||||
manager.mark()
|
||||
manager.batch(
|
||||
() => {
|
||||
manager.ignore(() => {
|
||||
setA(1)
|
||||
manager.batch(() => setB(1), { history: 'record' })
|
||||
manager.record(() => setB(1))
|
||||
setA(2)
|
||||
},
|
||||
{ history: 'ignore' }
|
||||
)
|
||||
})
|
||||
expect(getState()).toMatchObject({ a: 2, b: 1 })
|
||||
|
||||
// changes to A were ignore, but changes to B were recorded:
|
||||
|
@ -413,13 +408,10 @@ describe('history options', () => {
|
|||
expect(getState()).toMatchObject({ a: 2, b: 0 })
|
||||
|
||||
manager.mark()
|
||||
manager.batch(
|
||||
() => {
|
||||
manager.recordPreservingRedoStack(() => {
|
||||
setA(3)
|
||||
manager.batch(() => setB(2), { history: 'ignore' })
|
||||
},
|
||||
{ history: 'record-preserveRedoStack' }
|
||||
)
|
||||
manager.ignore(() => setB(2))
|
||||
})
|
||||
expect(getState()).toMatchObject({ a: 3, b: 2 })
|
||||
|
||||
// changes to A were recorded, but changes to B were ignore:
|
||||
|
|
|
@ -10,22 +10,16 @@ import {
|
|||
} from '@tldraw/store'
|
||||
import { exhaustiveSwitchError, noop } from '@tldraw/utils'
|
||||
import { uniqueId } from '../../utils/uniqueId'
|
||||
import { TLHistoryBatchOptions, TLHistoryEntry } from '../types/history-types'
|
||||
import { TLHistoryEntry, TLHistoryMode } from '../types/history-types'
|
||||
import { stack } from './Stack'
|
||||
|
||||
enum HistoryRecorderState {
|
||||
Recording = 'recording',
|
||||
RecordingPreserveRedoStack = 'recordingPreserveRedoStack',
|
||||
Paused = 'paused',
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export class HistoryManager<R extends UnknownRecord> {
|
||||
private readonly store: Store<R>
|
||||
|
||||
readonly dispose: () => void
|
||||
|
||||
private state: HistoryRecorderState = HistoryRecorderState.Recording
|
||||
private mode: TLHistoryMode = 'record'
|
||||
private readonly pendingDiff = new PendingDiff<R>()
|
||||
/** @internal */
|
||||
stacks = atom(
|
||||
|
@ -47,18 +41,18 @@ export class HistoryManager<R extends UnknownRecord> {
|
|||
this.dispose = this.store.addHistoryInterceptor((entry, source) => {
|
||||
if (source !== 'user') return
|
||||
|
||||
switch (this.state) {
|
||||
case HistoryRecorderState.Recording:
|
||||
switch (this.mode) {
|
||||
case 'record':
|
||||
this.pendingDiff.apply(entry.changes)
|
||||
this.stacks.update(({ undos }) => ({ undos, redos: stack() }))
|
||||
break
|
||||
case HistoryRecorderState.RecordingPreserveRedoStack:
|
||||
case 'record-preserveRedoStack':
|
||||
this.pendingDiff.apply(entry.changes)
|
||||
break
|
||||
case HistoryRecorderState.Paused:
|
||||
case 'ignore':
|
||||
break
|
||||
default:
|
||||
exhaustiveSwitchError(this.state)
|
||||
exhaustiveSwitchError(this.mode)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -73,8 +67,6 @@ export class HistoryManager<R extends UnknownRecord> {
|
|||
}))
|
||||
}
|
||||
|
||||
onBatchComplete: () => void = () => void null
|
||||
|
||||
getNumUndos() {
|
||||
return this.stacks.get().undos.length + (this.pendingDiff.isEmpty() ? 0 : 1)
|
||||
}
|
||||
|
@ -82,39 +74,34 @@ export class HistoryManager<R extends UnknownRecord> {
|
|||
return this.stacks.get().redos.length
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_isInBatch = false
|
||||
batch = (fn: () => void, opts?: TLHistoryBatchOptions) => {
|
||||
const previousState = this.state
|
||||
this.state = opts?.history ? modeToState[opts.history] : this.state
|
||||
|
||||
try {
|
||||
if (this._isInBatch) {
|
||||
runInMode(mode: TLHistoryMode | undefined | null, fn: () => void) {
|
||||
if (!mode) {
|
||||
fn()
|
||||
return this
|
||||
}
|
||||
|
||||
this._isInBatch = true
|
||||
const previousMode = this.mode
|
||||
this.mode = mode
|
||||
|
||||
try {
|
||||
transact(() => {
|
||||
fn()
|
||||
this.onBatchComplete()
|
||||
})
|
||||
} catch (error) {
|
||||
this.annotateError(error)
|
||||
throw error
|
||||
} finally {
|
||||
this._isInBatch = false
|
||||
}
|
||||
transact(fn)
|
||||
|
||||
return this
|
||||
} finally {
|
||||
this.state = previousState
|
||||
this.mode = previousMode
|
||||
}
|
||||
}
|
||||
|
||||
ignore(fn: () => void) {
|
||||
return this.batch(fn, { history: 'ignore' })
|
||||
return this.runInMode('ignore', fn)
|
||||
}
|
||||
|
||||
record(fn: () => void) {
|
||||
return this.runInMode('record', fn)
|
||||
}
|
||||
|
||||
recordPreservingRedoStack(fn: () => void) {
|
||||
return this.runInMode('record-preserveRedoStack', fn)
|
||||
}
|
||||
|
||||
// History
|
||||
|
@ -125,8 +112,8 @@ export class HistoryManager<R extends UnknownRecord> {
|
|||
pushToRedoStack: boolean
|
||||
toMark?: string
|
||||
}) => {
|
||||
const previousState = this.state
|
||||
this.state = HistoryRecorderState.Paused
|
||||
const previousState = this.mode
|
||||
this.mode = 'ignore'
|
||||
try {
|
||||
let { undos, redos } = this.stacks.get()
|
||||
|
||||
|
@ -183,7 +170,7 @@ export class HistoryManager<R extends UnknownRecord> {
|
|||
this.store.ensureStoreIsUsable()
|
||||
this.stacks.set({ undos, redos })
|
||||
} finally {
|
||||
this.state = previousState
|
||||
this.mode = previousState
|
||||
}
|
||||
|
||||
return this
|
||||
|
@ -196,8 +183,8 @@ export class HistoryManager<R extends UnknownRecord> {
|
|||
}
|
||||
|
||||
redo = () => {
|
||||
const previousState = this.state
|
||||
this.state = HistoryRecorderState.Paused
|
||||
const previousState = this.mode
|
||||
this.mode = 'ignore'
|
||||
try {
|
||||
this.flushPendingDiff()
|
||||
|
||||
|
@ -231,7 +218,7 @@ export class HistoryManager<R extends UnknownRecord> {
|
|||
this.store.ensureStoreIsUsable()
|
||||
this.stacks.set({ undos, redos })
|
||||
} finally {
|
||||
this.state = previousState
|
||||
this.mode = previousState
|
||||
}
|
||||
|
||||
return this
|
||||
|
@ -270,17 +257,11 @@ export class HistoryManager<R extends UnknownRecord> {
|
|||
undos: undos.toArray(),
|
||||
redos: redos.toArray(),
|
||||
pendingDiff: this.pendingDiff.debug(),
|
||||
state: this.state,
|
||||
state: this.mode,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const modeToState = {
|
||||
record: HistoryRecorderState.Recording,
|
||||
'record-preserveRedoStack': HistoryRecorderState.RecordingPreserveRedoStack,
|
||||
ignore: HistoryRecorderState.Paused,
|
||||
} as const
|
||||
|
||||
class PendingDiff<R extends UnknownRecord> {
|
||||
private diff = createEmptyRecordsDiff<R>()
|
||||
private isEmptyAtom = atom('PendingDiff.isEmpty', true)
|
||||
|
|
|
@ -86,7 +86,6 @@ export class ScribbleManager {
|
|||
*/
|
||||
tick = (elapsed: number) => {
|
||||
if (this.scribbleItems.size === 0) return
|
||||
this.editor.batch(() => {
|
||||
this.scribbleItems.forEach((item) => {
|
||||
// let the item get at least eight points before
|
||||
// switching from starting to active
|
||||
|
@ -176,6 +175,5 @@ export class ScribbleManager {
|
|||
}))
|
||||
.slice(-5), // limit to three as a minor sanity check
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,36 +1,38 @@
|
|||
import { TLRecord, TLStore } from '@tldraw/tlschema'
|
||||
import { Store, UnknownRecord } from '@tldraw/store'
|
||||
|
||||
/** @public */
|
||||
export type TLBeforeCreateHandler<R extends TLRecord> = (record: R, source: 'remote' | 'user') => R
|
||||
export type TLBeforeCreateHandler<R extends UnknownRecord> = (
|
||||
record: R,
|
||||
source: 'remote' | 'user'
|
||||
) => R
|
||||
/** @public */
|
||||
export type TLAfterCreateHandler<R extends TLRecord> = (
|
||||
export type TLAfterCreateHandler<R extends UnknownRecord> = (
|
||||
record: R,
|
||||
source: 'remote' | 'user'
|
||||
) => void
|
||||
/** @public */
|
||||
export type TLBeforeChangeHandler<R extends TLRecord> = (
|
||||
export type TLBeforeChangeHandler<R extends UnknownRecord> = (
|
||||
prev: R,
|
||||
next: R,
|
||||
source: 'remote' | 'user'
|
||||
) => R
|
||||
/** @public */
|
||||
export type TLAfterChangeHandler<R extends TLRecord> = (
|
||||
export type TLAfterChangeHandler<R extends UnknownRecord> = (
|
||||
prev: R,
|
||||
next: R,
|
||||
source: 'remote' | 'user'
|
||||
) => void
|
||||
/** @public */
|
||||
export type TLBeforeDeleteHandler<R extends TLRecord> = (
|
||||
export type TLBeforeDeleteHandler<R extends UnknownRecord> = (
|
||||
record: R,
|
||||
source: 'remote' | 'user'
|
||||
) => void | false
|
||||
/** @public */
|
||||
export type TLAfterDeleteHandler<R extends TLRecord> = (
|
||||
export type TLAfterDeleteHandler<R extends UnknownRecord> = (
|
||||
record: R,
|
||||
source: 'remote' | 'user'
|
||||
) => void
|
||||
/** @public */
|
||||
export type TLBatchCompleteHandler = () => void
|
||||
export type TLCompleteHandler = (source: 'remote' | 'user') => void
|
||||
|
||||
/**
|
||||
* The side effect manager (aka a "correct state enforcer") is responsible
|
||||
|
@ -40,17 +42,10 @@ export type TLBatchCompleteHandler = () => void
|
|||
*
|
||||
* @public
|
||||
*/
|
||||
export class SideEffectManager<
|
||||
CTX extends {
|
||||
store: TLStore
|
||||
history: { onBatchComplete: () => void }
|
||||
},
|
||||
> {
|
||||
constructor(public editor: CTX) {
|
||||
editor.store.onBeforeCreate = (record, source) => {
|
||||
const handlers = this._beforeCreateHandlers[
|
||||
record.typeName
|
||||
] as TLBeforeCreateHandler<TLRecord>[]
|
||||
export class SideEffectManager<R extends UnknownRecord> {
|
||||
constructor(public readonly store: Store<R>) {
|
||||
store.onBeforeCreate = (record, source) => {
|
||||
const handlers = this._beforeCreateHandlers[record.typeName as R['typeName']]
|
||||
if (handlers) {
|
||||
let r = record
|
||||
for (const handler of handlers) {
|
||||
|
@ -62,10 +57,8 @@ export class SideEffectManager<
|
|||
return record
|
||||
}
|
||||
|
||||
editor.store.onAfterCreate = (record, source) => {
|
||||
const handlers = this._afterCreateHandlers[
|
||||
record.typeName
|
||||
] as TLAfterCreateHandler<TLRecord>[]
|
||||
store.onAfterCreate = (record, source) => {
|
||||
const handlers = this._afterCreateHandlers[record.typeName as R['typeName']]
|
||||
if (handlers) {
|
||||
for (const handler of handlers) {
|
||||
handler(record, source)
|
||||
|
@ -73,10 +66,8 @@ export class SideEffectManager<
|
|||
}
|
||||
}
|
||||
|
||||
editor.store.onBeforeChange = (prev, next, source) => {
|
||||
const handlers = this._beforeChangeHandlers[
|
||||
next.typeName
|
||||
] as TLBeforeChangeHandler<TLRecord>[]
|
||||
store.onBeforeChange = (prev, next, source) => {
|
||||
const handlers = this._beforeChangeHandlers[next.typeName as R['typeName']]
|
||||
if (handlers) {
|
||||
let r = next
|
||||
for (const handler of handlers) {
|
||||
|
@ -88,8 +79,8 @@ export class SideEffectManager<
|
|||
return next
|
||||
}
|
||||
|
||||
editor.store.onAfterChange = (prev, next, source) => {
|
||||
const handlers = this._afterChangeHandlers[next.typeName] as TLAfterChangeHandler<TLRecord>[]
|
||||
store.onAfterChange = (prev, next, source) => {
|
||||
const handlers = this._afterChangeHandlers[next.typeName as R['typeName']]
|
||||
if (handlers) {
|
||||
for (const handler of handlers) {
|
||||
handler(prev, next, source)
|
||||
|
@ -97,10 +88,8 @@ export class SideEffectManager<
|
|||
}
|
||||
}
|
||||
|
||||
editor.store.onBeforeDelete = (record, source) => {
|
||||
const handlers = this._beforeDeleteHandlers[
|
||||
record.typeName
|
||||
] as TLBeforeDeleteHandler<TLRecord>[]
|
||||
store.onBeforeDelete = (record, source) => {
|
||||
const handlers = this._beforeDeleteHandlers[record.typeName as R['typeName']]
|
||||
if (handlers) {
|
||||
for (const handler of handlers) {
|
||||
if (handler(record, source) === false) {
|
||||
|
@ -110,10 +99,8 @@ export class SideEffectManager<
|
|||
}
|
||||
}
|
||||
|
||||
editor.store.onAfterDelete = (record, source) => {
|
||||
const handlers = this._afterDeleteHandlers[
|
||||
record.typeName
|
||||
] as TLAfterDeleteHandler<TLRecord>[]
|
||||
store.onAfterDelete = (record, source) => {
|
||||
const handlers = this._afterDeleteHandlers[record.typeName as R['typeName']]
|
||||
if (handlers) {
|
||||
for (const handler of handlers) {
|
||||
handler(record, source)
|
||||
|
@ -121,49 +108,56 @@ export class SideEffectManager<
|
|||
}
|
||||
}
|
||||
|
||||
editor.history.onBatchComplete = () => {
|
||||
this._batchCompleteHandlers.forEach((fn) => fn())
|
||||
store.onAfterAtomic = (source) => {
|
||||
const handlers = this._completeHandlers
|
||||
if (handlers) {
|
||||
for (const handler of handlers) {
|
||||
handler(source)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _beforeCreateHandlers: Partial<{
|
||||
[K in TLRecord['typeName']]: TLBeforeCreateHandler<TLRecord & { typeName: K }>[]
|
||||
[K in R['typeName']]: TLBeforeCreateHandler<R & { typeName: K }>[]
|
||||
}> = {}
|
||||
private _afterCreateHandlers: Partial<{
|
||||
[K in TLRecord['typeName']]: TLAfterCreateHandler<TLRecord & { typeName: K }>[]
|
||||
[K in R['typeName']]: TLAfterCreateHandler<R & { typeName: K }>[]
|
||||
}> = {}
|
||||
private _beforeChangeHandlers: Partial<{
|
||||
[K in TLRecord['typeName']]: TLBeforeChangeHandler<TLRecord & { typeName: K }>[]
|
||||
[K in R['typeName']]: TLBeforeChangeHandler<R & { typeName: K }>[]
|
||||
}> = {}
|
||||
private _afterChangeHandlers: Partial<{
|
||||
[K in TLRecord['typeName']]: TLAfterChangeHandler<TLRecord & { typeName: K }>[]
|
||||
[K in R['typeName']]: TLAfterChangeHandler<R & { typeName: K }>[]
|
||||
}> = {}
|
||||
|
||||
private _beforeDeleteHandlers: Partial<{
|
||||
[K in TLRecord['typeName']]: TLBeforeDeleteHandler<TLRecord & { typeName: K }>[]
|
||||
[K in R['typeName']]: TLBeforeDeleteHandler<R & { typeName: K }>[]
|
||||
}> = {}
|
||||
|
||||
private _afterDeleteHandlers: Partial<{
|
||||
[K in TLRecord['typeName']]: TLAfterDeleteHandler<TLRecord & { typeName: K }>[]
|
||||
[K in R['typeName']]: TLAfterDeleteHandler<R & { typeName: K }>[]
|
||||
}> = {}
|
||||
|
||||
private _batchCompleteHandlers: TLBatchCompleteHandler[] = []
|
||||
private _completeHandlers: TLCompleteHandler[] = []
|
||||
|
||||
/**
|
||||
* Internal helper for registering a bunch of side effects at once and keeping them organized.
|
||||
* @internal
|
||||
*/
|
||||
register(handlersByType: {
|
||||
[R in TLRecord as R['typeName']]?: {
|
||||
beforeCreate?: TLBeforeCreateHandler<R>
|
||||
afterCreate?: TLAfterCreateHandler<R>
|
||||
beforeChange?: TLBeforeChangeHandler<R>
|
||||
afterChange?: TLAfterChangeHandler<R>
|
||||
beforeDelete?: TLBeforeDeleteHandler<R>
|
||||
afterDelete?: TLAfterDeleteHandler<R>
|
||||
register(
|
||||
handlersByType: {
|
||||
[T in R as T['typeName']]?: {
|
||||
beforeCreate?: TLBeforeCreateHandler<T>
|
||||
afterCreate?: TLAfterCreateHandler<T>
|
||||
beforeChange?: TLBeforeChangeHandler<T>
|
||||
afterChange?: TLAfterChangeHandler<T>
|
||||
beforeDelete?: TLBeforeDeleteHandler<T>
|
||||
afterDelete?: TLAfterDeleteHandler<T>
|
||||
}
|
||||
}) {
|
||||
} & { complete?: TLCompleteHandler }
|
||||
) {
|
||||
const disposes: (() => void)[] = []
|
||||
if (handlersByType.complete) {
|
||||
this._completeHandlers.push(handlersByType.complete)
|
||||
}
|
||||
for (const [type, handlers] of Object.entries(handlersByType) as any) {
|
||||
if (handlers?.beforeCreate) {
|
||||
disposes.push(this.registerBeforeCreateHandler(type, handlers.beforeCreate))
|
||||
|
@ -216,9 +210,9 @@ export class SideEffectManager<
|
|||
* @param typeName - The type of record to listen for
|
||||
* @param handler - The handler to call
|
||||
*/
|
||||
registerBeforeCreateHandler<T extends TLRecord['typeName']>(
|
||||
registerBeforeCreateHandler<T extends R['typeName']>(
|
||||
typeName: T,
|
||||
handler: TLBeforeCreateHandler<TLRecord & { typeName: T }>
|
||||
handler: TLBeforeCreateHandler<R & { typeName: T }>
|
||||
) {
|
||||
const handlers = this._beforeCreateHandlers[typeName] as TLBeforeCreateHandler<any>[]
|
||||
if (!handlers) this._beforeCreateHandlers[typeName] = []
|
||||
|
@ -246,9 +240,9 @@ export class SideEffectManager<
|
|||
* @param typeName - The type of record to listen for
|
||||
* @param handler - The handler to call
|
||||
*/
|
||||
registerAfterCreateHandler<T extends TLRecord['typeName']>(
|
||||
registerAfterCreateHandler<T extends R['typeName']>(
|
||||
typeName: T,
|
||||
handler: TLAfterCreateHandler<TLRecord & { typeName: T }>
|
||||
handler: TLAfterCreateHandler<R & { typeName: T }>
|
||||
) {
|
||||
const handlers = this._afterCreateHandlers[typeName] as TLAfterCreateHandler<any>[]
|
||||
if (!handlers) this._afterCreateHandlers[typeName] = []
|
||||
|
@ -280,9 +274,9 @@ export class SideEffectManager<
|
|||
* @param typeName - The type of record to listen for
|
||||
* @param handler - The handler to call
|
||||
*/
|
||||
registerBeforeChangeHandler<T extends TLRecord['typeName']>(
|
||||
registerBeforeChangeHandler<T extends R['typeName']>(
|
||||
typeName: T,
|
||||
handler: TLBeforeChangeHandler<TLRecord & { typeName: T }>
|
||||
handler: TLBeforeChangeHandler<R & { typeName: T }>
|
||||
) {
|
||||
const handlers = this._beforeChangeHandlers[typeName] as TLBeforeChangeHandler<any>[]
|
||||
if (!handlers) this._beforeChangeHandlers[typeName] = []
|
||||
|
@ -309,9 +303,9 @@ export class SideEffectManager<
|
|||
* @param typeName - The type of record to listen for
|
||||
* @param handler - The handler to call
|
||||
*/
|
||||
registerAfterChangeHandler<T extends TLRecord['typeName']>(
|
||||
registerAfterChangeHandler<T extends R['typeName']>(
|
||||
typeName: T,
|
||||
handler: TLAfterChangeHandler<TLRecord & { typeName: T }>
|
||||
handler: TLAfterChangeHandler<R & { typeName: T }>
|
||||
) {
|
||||
const handlers = this._afterChangeHandlers[typeName] as TLAfterChangeHandler<any>[]
|
||||
if (!handlers) this._afterChangeHandlers[typeName] = []
|
||||
|
@ -340,9 +334,9 @@ export class SideEffectManager<
|
|||
* @param typeName - The type of record to listen for
|
||||
* @param handler - The handler to call
|
||||
*/
|
||||
registerBeforeDeleteHandler<T extends TLRecord['typeName']>(
|
||||
registerBeforeDeleteHandler<T extends R['typeName']>(
|
||||
typeName: T,
|
||||
handler: TLBeforeDeleteHandler<TLRecord & { typeName: T }>
|
||||
handler: TLBeforeDeleteHandler<R & { typeName: T }>
|
||||
) {
|
||||
const handlers = this._beforeDeleteHandlers[typeName] as TLBeforeDeleteHandler<any>[]
|
||||
if (!handlers) this._beforeDeleteHandlers[typeName] = []
|
||||
|
@ -372,9 +366,9 @@ export class SideEffectManager<
|
|||
* @param typeName - The type of record to listen for
|
||||
* @param handler - The handler to call
|
||||
*/
|
||||
registerAfterDeleteHandler<T extends TLRecord['typeName']>(
|
||||
registerAfterDeleteHandler<T extends R['typeName']>(
|
||||
typeName: T,
|
||||
handler: TLAfterDeleteHandler<TLRecord & { typeName: T }>
|
||||
handler: TLAfterDeleteHandler<R & { typeName: T }>
|
||||
) {
|
||||
const handlers = this._afterDeleteHandlers[typeName] as TLAfterDeleteHandler<any>[]
|
||||
if (!handlers) this._afterDeleteHandlers[typeName] = []
|
||||
|
@ -383,7 +377,7 @@ export class SideEffectManager<
|
|||
}
|
||||
|
||||
/**
|
||||
* Register a handler to be called when a store completes a batch.
|
||||
* Register a handler to be called when the store completes an operation.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
|
@ -394,7 +388,7 @@ export class SideEffectManager<
|
|||
* editor.selectAll()
|
||||
* expect(count).toBe(1)
|
||||
*
|
||||
* editor.batch(() => {
|
||||
* editor.store.atomic(() => {
|
||||
* editor.selectNone()
|
||||
* editor.selectAll()
|
||||
* })
|
||||
|
@ -406,9 +400,9 @@ export class SideEffectManager<
|
|||
*
|
||||
* @public
|
||||
*/
|
||||
registerBatchCompleteHandler(handler: TLBatchCompleteHandler) {
|
||||
this._batchCompleteHandlers.push(handler)
|
||||
return () => remove(this._batchCompleteHandlers, handler)
|
||||
registerCompleteHandler(handler: TLCompleteHandler) {
|
||||
this._completeHandlers.push(handler)
|
||||
return () => remove(this._completeHandlers, handler)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ export interface TLEventMap {
|
|||
mount: []
|
||||
'max-shapes': [{ name: string; pageId: TLPageId; count: number }]
|
||||
change: [HistoryEntry<TLRecord>]
|
||||
update: []
|
||||
crash: [{ error: unknown }]
|
||||
'stop-camera-animation': []
|
||||
'stop-following': []
|
||||
|
|
|
@ -17,11 +17,13 @@ export type TLHistoryEntry<R extends UnknownRecord> = TLHistoryMark | TLHistoryD
|
|||
|
||||
/** @public */
|
||||
export interface TLHistoryBatchOptions {
|
||||
/**
|
||||
history?: TLHistoryMode
|
||||
}
|
||||
|
||||
/**
|
||||
* How should this change interact with the history stack?
|
||||
* - record: Add to the undo stack and clear the redo stack
|
||||
* - record-preserveRedoStack: Add to the undo stack but do not clear the redo stack
|
||||
* - ignore: Do not add to the undo stack or the redo stack
|
||||
*/
|
||||
history?: 'record' | 'record-preserveRedoStack' | 'ignore'
|
||||
}
|
||||
export type TLHistoryMode = 'record' | 'record-preserveRedoStack' | 'ignore'
|
||||
|
|
|
@ -265,6 +265,7 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
|
|||
markAsPossiblyCorrupted(): void;
|
||||
mergeRemoteChanges: (fn: () => void) => void;
|
||||
migrateSnapshot(snapshot: StoreSnapshot<R>): StoreSnapshot<R>;
|
||||
onAfterAtomic?: (source: 'remote' | 'user') => void;
|
||||
onAfterChange?: (prev: R, next: R, source: 'remote' | 'user') => void;
|
||||
onAfterCreate?: (record: R, source: 'remote' | 'user') => void;
|
||||
onAfterDelete?: (prev: R, source: 'remote' | 'user') => void;
|
||||
|
|
|
@ -4108,6 +4108,36 @@
|
|||
"isAbstract": false,
|
||||
"name": "migrateSnapshot"
|
||||
},
|
||||
{
|
||||
"kind": "Property",
|
||||
"canonicalReference": "@tldraw/store!Store#onAfterAtomic:member",
|
||||
"docComment": "/**\n * A callback fired after an atomic operation is completed.\n */\n",
|
||||
"excerptTokens": [
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "onAfterAtomic?: "
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "(source: 'remote' | 'user') => void"
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": ";"
|
||||
}
|
||||
],
|
||||
"isReadonly": false,
|
||||
"isOptional": true,
|
||||
"releaseTag": "Public",
|
||||
"name": "onAfterAtomic",
|
||||
"propertyTypeTokenRange": {
|
||||
"startIndex": 1,
|
||||
"endIndex": 2
|
||||
},
|
||||
"isStatic": false,
|
||||
"isProtected": false,
|
||||
"isAbstract": false
|
||||
},
|
||||
{
|
||||
"kind": "Property",
|
||||
"canonicalReference": "@tldraw/store!Store#onAfterChange:member",
|
||||
|
|
|
@ -340,6 +340,11 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
|
|||
*/
|
||||
onAfterDelete?: (prev: R, source: 'remote' | 'user') => void
|
||||
|
||||
/**
|
||||
* A callback fired after an atomic operation is completed.
|
||||
*/
|
||||
onAfterAtomic?: (source: 'remote' | 'user') => void
|
||||
|
||||
// used to avoid running callbacks when rolling back changes in sync client
|
||||
private _runCallbacks = true
|
||||
|
||||
|
@ -682,6 +687,10 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
|
|||
* @public
|
||||
*/
|
||||
mergeRemoteChanges = (fn: () => void) => {
|
||||
if (this._isInAtomicOp) {
|
||||
throw new Error('Cannot call `mergeRemoteChanges` from within an atomic operation')
|
||||
}
|
||||
|
||||
if (this.isMergingRemoteChanges) {
|
||||
return fn()
|
||||
}
|
||||
|
@ -827,6 +836,9 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
|
|||
}
|
||||
private flushAtomicCallbacks() {
|
||||
let updateDepth = 0
|
||||
let didAnythingHappen = false
|
||||
|
||||
// first, we fire any pending after events:
|
||||
while (this.pendingAfterEvents) {
|
||||
const events = this.pendingAfterEvents
|
||||
this.pendingAfterEvents = null
|
||||
|
@ -840,15 +852,27 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
|
|||
|
||||
for (const { before, after, source } of events.values()) {
|
||||
if (before && after) {
|
||||
didAnythingHappen = true
|
||||
this.onAfterChange?.(before, after, source)
|
||||
} else if (before && !after) {
|
||||
didAnythingHappen = true
|
||||
this.onAfterDelete?.(before, source)
|
||||
} else if (!before && after) {
|
||||
didAnythingHappen = true
|
||||
this.onAfterCreate?.(after, source)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// then we fire the atomic callback
|
||||
if (didAnythingHappen) {
|
||||
this.onAfterAtomic?.(this.isMergingRemoteChanges ? 'remote' : 'user')
|
||||
|
||||
// that might have caused more changes, so we need to flush again:
|
||||
this.flushAtomicCallbacks()
|
||||
}
|
||||
}
|
||||
|
||||
private _isInAtomicOp = false
|
||||
/** @internal */
|
||||
atomic<T>(fn: () => T, runCallbacks = true): T {
|
||||
|
|
|
@ -377,7 +377,6 @@ export function registerDefaultExternalContentHandlers(
|
|||
}
|
||||
}
|
||||
|
||||
editor.batch(() => {
|
||||
if (shouldAlsoCreateAsset) {
|
||||
editor.createAssets([asset])
|
||||
}
|
||||
|
@ -392,7 +391,6 @@ export function registerDefaultExternalContentHandlers(
|
|||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export async function createShapesForAssets(
|
||||
|
@ -459,7 +457,6 @@ export async function createShapesForAssets(
|
|||
}
|
||||
}
|
||||
|
||||
editor.batch(() => {
|
||||
// Create any assets
|
||||
const assetsToCreate = assets.filter((asset) => !editor.getAsset(asset.id))
|
||||
if (assetsToCreate.length) {
|
||||
|
@ -471,7 +468,6 @@ export async function createShapesForAssets(
|
|||
|
||||
// Re-position shapes so that the center of the group is at the provided point
|
||||
centerSelectionAroundPoint(editor, position)
|
||||
})
|
||||
|
||||
return partials.map((p) => p.id)
|
||||
}
|
||||
|
@ -522,10 +518,8 @@ export function createEmptyBookmarkShape(
|
|||
},
|
||||
}
|
||||
|
||||
editor.batch(() => {
|
||||
editor.createShapes([partial]).select(partial.id)
|
||||
centerSelectionAroundPoint(editor, position)
|
||||
})
|
||||
|
||||
return editor.getShape(partial.id) as TLBookmarkShape
|
||||
}
|
||||
|
|
|
@ -180,7 +180,6 @@ const createBookmarkAssetOnUrlChange = debounce(async (editor: Editor, shape: TL
|
|||
return
|
||||
}
|
||||
|
||||
editor.batch(() => {
|
||||
// Create the new asset
|
||||
editor.createAssets([asset])
|
||||
|
||||
|
@ -192,5 +191,4 @@ const createBookmarkAssetOnUrlChange = debounce(async (editor: Editor, shape: TL
|
|||
props: { assetId: asset.id },
|
||||
},
|
||||
])
|
||||
})
|
||||
}, 500)
|
||||
|
|
|
@ -32,9 +32,7 @@ export class DragAndDropManager {
|
|||
|
||||
private setDragTimer(movingShapes: TLShape[], duration: number, cb: () => void) {
|
||||
this.droppingNodeTimer = setTimeout(() => {
|
||||
this.editor.batch(() => {
|
||||
this.handleDrag(this.editor.inputs.currentPagePoint, movingShapes, cb)
|
||||
})
|
||||
this.droppingNodeTimer = null
|
||||
}, duration)
|
||||
}
|
||||
|
|
|
@ -146,7 +146,6 @@ export class PointingShape extends StateNode {
|
|||
labelGeometry.bounds.containsPoint(pointInShapeSpace, 0) &&
|
||||
labelGeometry.hitTestPoint(pointInShapeSpace)
|
||||
) {
|
||||
this.editor.batch(() => {
|
||||
this.editor.mark('editing on pointer up')
|
||||
this.editor.select(selectingShape.id)
|
||||
|
||||
|
@ -159,7 +158,6 @@ export class PointingShape extends StateNode {
|
|||
|
||||
this.editor.setEditingShape(selectingShape.id)
|
||||
this.editor.setCurrentTool('select.editing_shape')
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
@ -294,7 +294,5 @@ function createNShapes(editor: Editor, n: number) {
|
|||
}
|
||||
}
|
||||
|
||||
editor.batch(() => {
|
||||
editor.createShapes(shapesToCreate).setSelectedShapes(shapesToCreate.map((s) => s.id))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -251,13 +251,11 @@ export const DefaultPageMenu = memo(function DefaultPageMenu() {
|
|||
const handleCreatePageClick = useCallback(() => {
|
||||
if (isReadonlyMode) return
|
||||
|
||||
editor.batch(() => {
|
||||
editor.mark('creating page')
|
||||
const newPageId = PageRecordType.createId()
|
||||
editor.createPage({ name: msg('page-menu.new-page-initial-name'), id: newPageId })
|
||||
editor.setCurrentPage(newPageId)
|
||||
setIsEditing(true)
|
||||
})
|
||||
}, [editor, msg, isReadonlyMode])
|
||||
|
||||
return (
|
||||
|
@ -400,10 +398,8 @@ export const DefaultPageMenu = memo(function DefaultPageMenu() {
|
|||
editor.renamePage(page.id, name)
|
||||
}
|
||||
} else {
|
||||
editor.batch(() => {
|
||||
setIsEditing(true)
|
||||
editor.setCurrentPage(page.id)
|
||||
})
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
|
|
@ -78,13 +78,11 @@ function useStyleChangeCallback() {
|
|||
return React.useMemo(
|
||||
() =>
|
||||
function handleStyleChange<T>(style: StyleProp<T>, value: T) {
|
||||
editor.batch(() => {
|
||||
if (editor.isIn('select')) {
|
||||
editor.setStyleForSelectedShapes(style, value)
|
||||
}
|
||||
editor.setStyleForNextShapes(style, value)
|
||||
editor.updateInstanceState({ isChangingStyle: true })
|
||||
})
|
||||
|
||||
trackEvent('set-style', { source: 'style-panel', id: style.id, value: value as string })
|
||||
},
|
||||
|
@ -327,13 +325,11 @@ export function OpacitySlider() {
|
|||
const handleOpacityValueChange = React.useCallback(
|
||||
(value: number) => {
|
||||
const item = tldrawSupportedOpacities[value]
|
||||
editor.batch(() => {
|
||||
if (editor.isIn('select')) {
|
||||
editor.setOpacityForSelectedShapes(item)
|
||||
}
|
||||
editor.setOpacityForNextShapes(item)
|
||||
editor.updateInstanceState({ isChangingStyle: true })
|
||||
})
|
||||
|
||||
trackEvent('set-style', { source: 'style-panel', id: 'opacity', value })
|
||||
},
|
||||
|
|
|
@ -386,7 +386,6 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
|
|||
if (!canApplySelectionAction()) return
|
||||
if (mustGoBackToSelectToolFirst()) return
|
||||
|
||||
editor.batch(() => {
|
||||
trackEvent('convert-to-bookmark', { source })
|
||||
const shapes = editor.getSelectedShapes()
|
||||
|
||||
|
@ -419,7 +418,6 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
|
|||
editor.mark('convert shapes to bookmark')
|
||||
editor.deleteShapes(deleteList)
|
||||
editor.createShapes(createList)
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -431,7 +429,6 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
|
|||
|
||||
trackEvent('convert-to-embed', { source })
|
||||
|
||||
editor.batch(() => {
|
||||
const ids = editor.getSelectedShapeIds()
|
||||
const shapes = compact(ids.map((id) => editor.getShape(id)))
|
||||
|
||||
|
@ -474,7 +471,6 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
|
|||
editor.mark('convert shapes to embed')
|
||||
editor.deleteShapes(deleteList)
|
||||
editor.createShapes(createList)
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -921,14 +917,12 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
|
|||
kbd: '$a',
|
||||
readonlyOk: true,
|
||||
onSelect(source) {
|
||||
editor.batch(() => {
|
||||
if (mustGoBackToSelectToolFirst()) return
|
||||
|
||||
trackEvent('select-all-shapes', { source })
|
||||
|
||||
editor.mark('select all kbd')
|
||||
editor.selectAll()
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -1177,13 +1171,11 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
|
|||
// this needs to be deferred because it causes the menu
|
||||
// UI to unmount which puts us in a dodgy state
|
||||
requestAnimationFrame(() => {
|
||||
editor.batch(() => {
|
||||
trackEvent('toggle-focus-mode', { source })
|
||||
clearDialogs()
|
||||
clearToasts()
|
||||
editor.updateInstanceState({ isFocusMode: !editor.getInstanceState().isFocusMode })
|
||||
})
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -1271,11 +1263,9 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
|
|||
onSelect(source) {
|
||||
const newPageId = PageRecordType.createId()
|
||||
const ids = editor.getSelectedShapeIds()
|
||||
editor.batch(() => {
|
||||
editor.mark('move_shapes_to_page')
|
||||
editor.createPage({ name: msg('page-menu.new-page-initial-name'), id: newPageId })
|
||||
editor.moveShapesToPage(ids, newPageId)
|
||||
})
|
||||
trackEvent('new-page', { source })
|
||||
},
|
||||
},
|
||||
|
@ -1285,14 +1275,12 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
|
|||
kbd: '?t',
|
||||
onSelect(source) {
|
||||
const style = DefaultColorStyle
|
||||
editor.batch(() => {
|
||||
editor.mark('change-color')
|
||||
if (editor.isIn('select')) {
|
||||
editor.setStyleForSelectedShapes(style, 'white')
|
||||
}
|
||||
editor.setStyleForNextShapes(style, 'white')
|
||||
editor.updateInstanceState({ isChangingStyle: true })
|
||||
})
|
||||
trackEvent('set-style', { source, id: style.id, value: 'white' })
|
||||
},
|
||||
},
|
||||
|
|
|
@ -12,7 +12,6 @@ export function useMenuIsOpen(id: string, cb?: (isOpen: boolean) => void) {
|
|||
(isOpen: boolean) => {
|
||||
rIsOpen.current = isOpen
|
||||
|
||||
editor.batch(() => {
|
||||
if (isOpen) {
|
||||
editor.complete()
|
||||
editor.addOpenMenu(id)
|
||||
|
@ -23,7 +22,6 @@ export function useMenuIsOpen(id: string, cb?: (isOpen: boolean) => void) {
|
|||
}
|
||||
|
||||
cb?.(isOpen)
|
||||
})
|
||||
},
|
||||
[editor, id, cb]
|
||||
)
|
||||
|
|
|
@ -101,11 +101,9 @@ export function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {
|
|||
kbd: id === 'rectangle' ? 'r' : id === 'ellipse' ? 'o' : undefined,
|
||||
icon: ('geo-' + id) as TLUiIconType,
|
||||
onSelect(source: TLUiEventSource) {
|
||||
editor.batch(() => {
|
||||
editor.setStyleForNextShapes(GeoShapeGeoStyle, id)
|
||||
editor.setCurrentTool('geo')
|
||||
trackEvent('select-tool', { source, id: `geo-${id}` })
|
||||
})
|
||||
},
|
||||
})),
|
||||
{
|
||||
|
|
|
@ -17,7 +17,6 @@ export function removeFrame(editor: Editor, ids: TLShapeId[]) {
|
|||
if (!frames.length) return
|
||||
|
||||
const allChildren: TLShapeId[] = []
|
||||
editor.batch(() => {
|
||||
frames.map((frame) => {
|
||||
const children = editor.getSortedChildIdsForParent(frame.id)
|
||||
if (children.length) {
|
||||
|
@ -27,7 +26,6 @@ export function removeFrame(editor: Editor, ids: TLShapeId[]) {
|
|||
})
|
||||
editor.setSelectedShapes(allChildren)
|
||||
editor.deleteShapes(ids)
|
||||
})
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
|
@ -66,7 +64,6 @@ export function fitFrameToContent(editor: Editor, id: TLShapeId, opts = {} as {
|
|||
if (dx === 0 && dy === 0 && frame.props.w === w && frame.props.h === h) return
|
||||
|
||||
const diff = new Vec(dx, dy).rot(frame.rotation)
|
||||
editor.batch(() => {
|
||||
const changes: TLShapePartial[] = childIds.map((child) => {
|
||||
const shape = editor.getShape(child)!
|
||||
return {
|
||||
|
@ -89,5 +86,4 @@ export function fitFrameToContent(editor: Editor, id: TLShapeId, opts = {} as {
|
|||
})
|
||||
|
||||
editor.updateShapes(changes)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -31,7 +31,6 @@ const TLDRAW_V1_VERSION = 15.5
|
|||
|
||||
/** @internal */
|
||||
export function buildFromV1Document(editor: Editor, document: LegacyTldrawDocument) {
|
||||
editor.batch(() => {
|
||||
document = migrate(document, TLDRAW_V1_VERSION)
|
||||
// Cancel any interactions / states
|
||||
editor.cancel().cancel().cancel().cancel()
|
||||
|
@ -593,7 +592,6 @@ export function buildFromV1Document(editor: Editor, document: LegacyTldrawDocume
|
|||
if (bounds) {
|
||||
editor.zoomToBounds(bounds, { targetZoom: 1 })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function coerceNumber(n: unknown): number {
|
||||
|
|
Ładowanie…
Reference in New Issue