diff --git a/packages/docs-ui/src/controllers/page-render.controller.ts b/packages/docs-ui/src/controllers/page-render.controller.ts index ede70b92e37..c0cde3b93e3 100644 --- a/packages/docs-ui/src/controllers/page-render.controller.ts +++ b/packages/docs-ui/src/controllers/page-render.controller.ts @@ -19,6 +19,7 @@ import { IUniverInstanceService, LifecycleStages, OnLifecycle, + toDisposable, } from '@univerjs/core'; import type { Documents, IPageRenderConfig } from '@univerjs/engine-render'; import { IRenderManagerService, Rect } from '@univerjs/engine-render'; @@ -68,28 +69,32 @@ export class PageRenderController extends Disposable { const pageSize = docsComponent.getSkeleton()?.getPageSize(); - docsComponent.onPageRenderObservable.add((config: IPageRenderConfig) => { - if (this._editorService.isEditor(unitId)) { - return; - } - - // Draw page borders - const { page, pageLeft, pageTop, ctx } = config; - const { width, pageWidth, height, pageHeight } = page; - - ctx.save(); - - ctx.translate(pageLeft - 0.5, pageTop - 0.5); - Rect.drawWith(ctx, { - width: pageSize?.width ?? pageWidth ?? width, - height: pageSize?.height ?? pageHeight ?? height, - strokeWidth: 1, - stroke: PAGE_STROKE_COLOR, - fill: PAGE_FILL_COLOR, - zIndex: 3, - }); - ctx.restore(); - }); + this.disposeWithMe( + toDisposable( + docsComponent.onPageRenderObservable.add((config: IPageRenderConfig) => { + if (this._editorService.isEditor(unitId)) { + return; + } + + // Draw page borders + const { page, pageLeft, pageTop, ctx } = config; + const { width, pageWidth, height, pageHeight } = page; + + ctx.save(); + + ctx.translate(pageLeft - 0.5, pageTop - 0.5); + Rect.drawWith(ctx, { + width: pageSize?.width ?? pageWidth ?? width, + height: pageSize?.height ?? pageHeight ?? height, + strokeWidth: 1, + stroke: PAGE_STROKE_COLOR, + fill: PAGE_FILL_COLOR, + zIndex: 3, + }); + ctx.restore(); + }) + ) + ); }); } diff --git a/packages/docs-ui/src/views/doc-canvas-view.ts b/packages/docs-ui/src/views/doc-canvas-view.ts index 62b44ccc38a..8847619bc1c 100644 --- a/packages/docs-ui/src/views/doc-canvas-view.ts +++ b/packages/docs-ui/src/views/doc-canvas-view.ts @@ -33,8 +33,6 @@ export class DocCanvasView extends RxDisposable { private _currentDocumentModel!: DocumentDataModel; - private _loadedMap = new Set(); - private readonly _fps$ = new BehaviorSubject(''); readonly fps$ = this._fps$.asObservable(); @@ -79,9 +77,8 @@ export class DocCanvasView extends RxDisposable { this._currentDocumentModel = model; - if (!this._loadedMap.has(unitId)) { + if (!this._renderManagerService.has(unitId)) { this._addNewRender(); - this._loadedMap.add(unitId); } } diff --git a/packages/docs/src/controllers/text-selection.controller.ts b/packages/docs/src/controllers/text-selection.controller.ts index b731b43198f..4b5e38882d9 100644 --- a/packages/docs/src/controllers/text-selection.controller.ts +++ b/packages/docs/src/controllers/text-selection.controller.ts @@ -16,7 +16,7 @@ import type { ICommandInfo, Nullable } from '@univerjs/core'; import { Disposable, ICommandService, IUniverInstanceService, LifecycleStages, OnLifecycle, toDisposable } from '@univerjs/core'; -import type { Documents, IMouseEvent, IPointerEvent } from '@univerjs/engine-render'; +import type { Documents, IMouseEvent, IPointerEvent, RenderComponentType } from '@univerjs/engine-render'; import { CURSOR_TYPE, IRenderManagerService, ITextSelectionRenderManager } from '@univerjs/engine-render'; import { Inject } from '@wendellhu/redi'; @@ -28,7 +28,7 @@ import { TextSelectionManagerService } from '../services/text-selection-manager. @OnLifecycle(LifecycleStages.Rendered, TextSelectionController) export class TextSelectionController extends Disposable { - private _loadedMap = new Set(); + private _loadedMap = new WeakSet(); constructor( @Inject(DocSkeletonManagerService) private readonly _docSkeletonManagerService: DocSkeletonManagerService, @@ -71,9 +71,14 @@ export class TextSelectionController extends Disposable { return; } - if (!this._loadedMap.has(unitId)) { + const docObject = this._getDocObjectById(unitId); + if (docObject == null || docObject.document == null) { + return; + } + + if (!this._loadedMap.has(docObject.document)) { this._initialMain(unitId); - this._loadedMap.add(unitId); + this._loadedMap.add(docObject.document); } } diff --git a/packages/docs/src/services/doc-skeleton-manager.service.ts b/packages/docs/src/services/doc-skeleton-manager.service.ts index 189fd747e01..1fa583fd33f 100644 --- a/packages/docs/src/services/doc-skeleton-manager.service.ts +++ b/packages/docs/src/services/doc-skeleton-manager.service.ts @@ -15,7 +15,7 @@ */ import type { Nullable } from '@univerjs/core'; -import { LocaleService, RxDisposable } from '@univerjs/core'; +import { IUniverInstanceService, LocaleService, RxDisposable } from '@univerjs/core'; import type { DocumentViewModel } from '@univerjs/engine-render'; import { DocumentSkeleton } from '@univerjs/engine-render'; import { Inject } from '@wendellhu/redi'; @@ -48,7 +48,8 @@ export class DocSkeletonManagerService extends RxDisposable { constructor( @Inject(LocaleService) private readonly _localeService: LocaleService, - @Inject(DocViewModelManagerService) private readonly _docViewModelManagerService: DocViewModelManagerService + @Inject(DocViewModelManagerService) private readonly _docViewModelManagerService: DocViewModelManagerService, + @IUniverInstanceService private readonly _currentUniverService: IUniverInstanceService ) { super(); this._initialize(); @@ -82,6 +83,12 @@ export class DocSkeletonManagerService extends RxDisposable { this._setCurrent(docViewModel); }); + + this._currentUniverService.docDisposed$.pipe(takeUntil(this.dispose$)).subscribe((documentModel) => { + this._docSkeletonMap.delete(documentModel.getUnitId()); + + this._currentSkeletonUnitId = this._currentUniverService.getCurrentUniverDocInstance().getUnitId(); + }); } getCurrent(): Nullable { diff --git a/packages/docs/src/services/doc-view-model-manager.service.ts b/packages/docs/src/services/doc-view-model-manager.service.ts index 614c9818f57..f1cbeb6faec 100644 --- a/packages/docs/src/services/doc-view-model-manager.service.ts +++ b/packages/docs/src/services/doc-view-model-manager.service.ts @@ -28,7 +28,6 @@ export interface IDocumentViewModelManagerParam { * The view model manager is used to manage Doc view model. has a one-to-one correspondence with the doc skeleton. */ export class DocViewModelManagerService extends RxDisposable { - private _currentViewModelUnitId: string = ''; private _docViewModelMap: Map = new Map(); private readonly _currentDocViewModel$ = new BehaviorSubject>(null); @@ -56,6 +55,10 @@ export class DocViewModelManagerService extends RxDisposable { this._currentUniverService.getAllUniverDocsInstance().forEach((documentModel) => { this._create(documentModel); }); + + this._currentUniverService.docDisposed$.pipe(takeUntil(this.dispose$)).subscribe((documentModel) => { + this._docViewModelMap.delete(documentModel.getUnitId()); + }); } private _create(documentModel: Nullable) { @@ -68,10 +71,6 @@ export class DocViewModelManagerService extends RxDisposable { this._setCurrent(unitId); } - getCurrent() { - return this._docViewModelMap.get(this._currentViewModelUnitId); - } - getAllModel() { return this._docViewModelMap; } @@ -111,9 +110,7 @@ export class DocViewModelManagerService extends RxDisposable { docViewModel.reset(documentDataModel); } - this._currentViewModelUnitId = unitId; - - this._currentDocViewModel$.next(this.getCurrent()); + this._currentDocViewModel$.next(this._docViewModelMap.get(unitId)); } private _buildDocViewModel(documentDataModel: DocumentDataModel) { diff --git a/packages/engine-render/src/base-object.ts b/packages/engine-render/src/base-object.ts index ec380b3dc38..a6639295232 100644 --- a/packages/engine-render/src/base-object.ts +++ b/packages/engine-render/src/base-object.ts @@ -685,6 +685,7 @@ export abstract class BaseObject { } dispose() { + this.onTransformChangeObservable.clear(); this.onPointerDownObserver.clear(); this.onPointerMoveObserver.clear(); this.onPointerUpObserver.clear(); @@ -702,6 +703,8 @@ export abstract class BaseObject { this.onDisposeObserver.notifyObservers(this); this._makeDirtyMix(); + + this.onDisposeObserver.clear(); } toJson() { diff --git a/packages/engine-render/src/layer.ts b/packages/engine-render/src/layer.ts index f1d725cb539..b9817444e55 100644 --- a/packages/engine-render/src/layer.ts +++ b/packages/engine-render/src/layer.ts @@ -15,7 +15,7 @@ */ import type { Nullable } from '@univerjs/core'; -import { sortRules } from '@univerjs/core'; +import { Disposable, sortRules, toDisposable } from '@univerjs/core'; import { BaseObject } from './base-object'; import { RENDER_CLASS_TYPE } from './basics/const'; @@ -23,7 +23,7 @@ import { Canvas } from './canvas'; import type { UniverRenderingContext } from './context'; import type { ThinScene } from './thin-scene'; -export class Layer { +export class Layer extends Disposable { private _objects: BaseObject[] = []; private _cacheCanvas: Nullable; @@ -36,6 +36,8 @@ export class Layer { private _zIndex: number = 1, private _allowCache: boolean = false ) { + super(); + this.addObjects(objects); if (this._allowCache) { @@ -203,9 +205,13 @@ export class Layer { private _initialCacheCanvas() { this._cacheCanvas = new Canvas(); - this._scene.getEngine().onTransformChangeObservable.add(() => { - this._resizeCacheCanvas(); - }); + this.disposeWithMe( + toDisposable( + this._scene.getEngine().onTransformChangeObservable.add(() => { + this._resizeCacheCanvas(); + }) + ) + ); } private _draw(mainCtx: UniverRenderingContext, isMaxLayer: boolean) { @@ -232,6 +238,8 @@ export class Layer { } dispose() { + super.dispose(); + this.getObjects().forEach((o) => { o.dispose(); }); diff --git a/packages/engine-render/src/render-manager.service.ts b/packages/engine-render/src/render-manager.service.ts index 77dd7bdbbee..32d8c191ad4 100644 --- a/packages/engine-render/src/render-manager.service.ts +++ b/packages/engine-render/src/render-manager.service.ts @@ -43,6 +43,7 @@ export interface IRenderManagerService { create(unitId: Nullable): void; getCurrent(): Nullable; getFirst(): Nullable; + has(unitId: string): boolean; } export type RenderComponentType = SheetComponent | DocComponent | Slide | BaseObject; @@ -166,6 +167,10 @@ export class RenderManagerService implements IRenderManagerService { this._renderMap.delete(unitId); } + has(unitId: string) { + return this._renderMap.has(unitId); + } + setCurrent(unitId: string) { this._currentUnitId = unitId; diff --git a/packages/engine-render/src/scene.-transformer.ts b/packages/engine-render/src/scene.-transformer.ts index e27a8a5367a..6d070f1466d 100644 --- a/packages/engine-render/src/scene.-transformer.ts +++ b/packages/engine-render/src/scene.-transformer.ts @@ -15,7 +15,7 @@ */ import type { IKeyValue, Nullable, Observer } from '@univerjs/core'; -import { Observable } from '@univerjs/core'; +import { Disposable, Observable, toDisposable } from '@univerjs/core'; import type { BaseObject } from './base-object'; import { CURSOR_TYPE } from './basics/const'; @@ -117,7 +117,7 @@ export interface ITransformerConfig { * primitives and shapes. Transforming tool is not changing `width` and `height` properties of nodes * when you resize them. Instead it changes `scaleX` and `scaleY` properties. */ -export class Transformer implements ITransformerConfig { +export class Transformer extends Disposable implements ITransformerConfig { hoverEnabled = false; hoverEnterFunc: Nullable<(e: IPointerEvent | IMouseEvent) => void>; @@ -211,6 +211,7 @@ export class Transformer implements ITransformerConfig { private _scene: ThinScene, config?: ITransformerConfig ) { + super(); this._initialProps(config); } @@ -232,62 +233,73 @@ export class Transformer implements ITransformerConfig { this.hoverLeaveFunc && applyObject.onPointerLeaveObserver.add(this.hoverLeaveFunc); } - applyObject.onPointerDownObserver.add((evt: IPointerEvent | IMouseEvent, state) => { - const { offsetX: evtOffsetX, offsetY: evtOffsetY } = evt; - this._startOffsetX = evtOffsetX; - this._startOffsetY = evtOffsetY; + this.disposeWithMe( + toDisposable( + applyObject.onPointerDownObserver.add((evt: IPointerEvent | IMouseEvent, state) => { + const { offsetX: evtOffsetX, offsetY: evtOffsetY } = evt; + this._startOffsetX = evtOffsetX; + this._startOffsetY = evtOffsetY; - const scene = this._getTopScene(); + const scene = this._getTopScene(); - if (!scene) { - return; - } + if (!scene) { + return; + } - this._addCancelObserver(scene); + this._addCancelObserver(scene); - scene.disableEvent(); + scene.disableEvent(); - const scrollTimer = ScrollTimer.create(scene); - scrollTimer.startScroll(evtOffsetX, evtOffsetY); + const scrollTimer = ScrollTimer.create(scene); + scrollTimer.startScroll(evtOffsetX, evtOffsetY); - const { scrollX, scrollY } = getCurrentScrollXY(scrollTimer); + const { scrollX, scrollY } = getCurrentScrollXY(scrollTimer); - this._viewportScrollX = scrollX; - this._viewportScrollY = scrollY; + this._viewportScrollX = scrollX; + this._viewportScrollY = scrollY; - this._updateActiveObjectList(applyObject, evt); + this._updateActiveObjectList(applyObject, evt); - this._moveObserver = scene.onPointerMoveObserver.add((moveEvt: IPointerEvent | IMouseEvent) => { - const { offsetX: moveOffsetX, offsetY: moveOffsetY } = moveEvt; - this._moving(moveOffsetX, moveOffsetY, scrollTimer); - this._hideControl(); - scrollTimer.scrolling(moveOffsetX, moveOffsetY, () => { - this._moving(moveOffsetX, moveOffsetY, scrollTimer); - }); - }); + this._moveObserver = scene.onPointerMoveObserver.add((moveEvt: IPointerEvent | IMouseEvent) => { + const { offsetX: moveOffsetX, offsetY: moveOffsetY } = moveEvt; + this._moving(moveOffsetX, moveOffsetY, scrollTimer); + this._hideControl(); + scrollTimer.scrolling(moveOffsetX, moveOffsetY, () => { + this._moving(moveOffsetX, moveOffsetY, scrollTimer); + }); + }); - this._upObserver = scene.onPointerUpObserver.add((upEvt: IPointerEvent | IMouseEvent) => { - scene.onPointerMoveObserver.remove(this._moveObserver); - scene.onPointerUpObserver.remove(this._upObserver); - scene.enableEvent(); - this._updateControl(); - scrollTimer.dispose(); - - this.onChangeEndObservable.notifyObservers({ - objects: this._selectedObjectMap, - type: MoveObserverType.MOVE_END, - }); - }); + this._upObserver = scene.onPointerUpObserver.add((upEvt: IPointerEvent | IMouseEvent) => { + scene.onPointerMoveObserver.remove(this._moveObserver); + scene.onPointerUpObserver.remove(this._upObserver); + scene.enableEvent(); + this._updateControl(); + scrollTimer.dispose(); + + this.onChangeEndObservable.notifyObservers({ + objects: this._selectedObjectMap, + type: MoveObserverType.MOVE_END, + }); + }); - state.stopPropagation(); - }); + state.stopPropagation(); + }) + ) + ); return applyObject; } dispose() { + this._moveObserver?.dispose(); + this._upObserver?.dispose(); + + this._cancelFocusObserver?.dispose(); + this._moveObserver = null; this._upObserver = null; + this._cancelFocusObserver = null; + this._transformerControlMap.forEach((control) => { control.dispose(); }); @@ -418,84 +430,92 @@ export class Transformer implements ITransformerConfig { } private _attachEventToAnchor(anchor: Rect, type = TransformerManagerType.RESIZE_LT) { - anchor.onPointerDownObserver.add((evt: IPointerEvent | IMouseEvent, state) => { - const { offsetX: evtOffsetX, offsetY: evtOffsetY } = evt; - this._startOffsetX = evtOffsetX; - this._startOffsetY = evtOffsetY; + this.disposeWithMe( + toDisposable( + anchor.onPointerDownObserver.add((evt: IPointerEvent | IMouseEvent, state) => { + const { offsetX: evtOffsetX, offsetY: evtOffsetY } = evt; + this._startOffsetX = evtOffsetX; + this._startOffsetY = evtOffsetY; - const scene = this._getTopScene(); + const scene = this._getTopScene(); - if (scene == null) { - return; - } + if (scene == null) { + return; + } - scene.disableEvent(); + scene.disableEvent(); - const scrollTimer = ScrollTimer.create(scene); - scrollTimer.startScroll(evtOffsetX, evtOffsetY); + const scrollTimer = ScrollTimer.create(scene); + scrollTimer.startScroll(evtOffsetX, evtOffsetY); - const { scrollX, scrollY } = getCurrentScrollXY(scrollTimer); + const { scrollX, scrollY } = getCurrentScrollXY(scrollTimer); - this._viewportScrollX = scrollX; - this._viewportScrollY = scrollY; + this._viewportScrollX = scrollX; + this._viewportScrollY = scrollY; - const cursor = this._getRotateAnchorCursor(type); + const cursor = this._getRotateAnchorCursor(type); - this._moveObserver = scene.onPointerMoveObserver.add((moveEvt: IPointerEvent | IMouseEvent) => { - const { offsetX: moveOffsetX, offsetY: moveOffsetY } = moveEvt; - this._anchorMoving(type, moveOffsetX, moveOffsetY, scrollTimer); - scrollTimer.scrolling(moveOffsetX, moveOffsetY, () => { - this._anchorMoving(type, moveOffsetX, moveOffsetY, scrollTimer); - }); - scene.setCursor(cursor); - }); + this._moveObserver = scene.onPointerMoveObserver.add((moveEvt: IPointerEvent | IMouseEvent) => { + const { offsetX: moveOffsetX, offsetY: moveOffsetY } = moveEvt; + this._anchorMoving(type, moveOffsetX, moveOffsetY, scrollTimer); + scrollTimer.scrolling(moveOffsetX, moveOffsetY, () => { + this._anchorMoving(type, moveOffsetX, moveOffsetY, scrollTimer); + }); + scene.setCursor(cursor); + }); - this._upObserver = scene.onPointerUpObserver.add((upEvt: IPointerEvent | IMouseEvent) => { - scene.onPointerMoveObserver.remove(this._moveObserver); - scene.onPointerUpObserver.remove(this._upObserver); - scene.enableEvent(); - scene.resetCursor(); - scrollTimer.dispose(); - - this.onChangeEndObservable.notifyObservers({ - objects: this._selectedObjectMap, - type: MoveObserverType.MOVE_END, - }); - }); + this._upObserver = scene.onPointerUpObserver.add((upEvt: IPointerEvent | IMouseEvent) => { + scene.onPointerMoveObserver.remove(this._moveObserver); + scene.onPointerUpObserver.remove(this._upObserver); + scene.enableEvent(); + scene.resetCursor(); + scrollTimer.dispose(); + + this.onChangeEndObservable.notifyObservers({ + objects: this._selectedObjectMap, + type: MoveObserverType.MOVE_END, + }); + }); - state.stopPropagation(); - }); + state.stopPropagation(); + }) + ) + ); } private _attachEventToRotate(rotateControl: Rect) { - rotateControl.onPointerDownObserver.add((evt: IPointerEvent | IMouseEvent, state) => { - const { offsetX: evtOffsetX, offsetY: evtOffsetY } = evt; - this._startOffsetX = evtOffsetX; - this._startOffsetY = evtOffsetY; + this.disposeWithMe( + toDisposable( + rotateControl.onPointerDownObserver.add((evt: IPointerEvent | IMouseEvent, state) => { + const { offsetX: evtOffsetX, offsetY: evtOffsetY } = evt; + this._startOffsetX = evtOffsetX; + this._startOffsetY = evtOffsetY; - const scene = this._getTopScene(); + const scene = this._getTopScene(); - if (scene == null) { - return; - } + if (scene == null) { + return; + } - scene.disableEvent(); + scene.disableEvent(); - this._viewportScrollX = scrollX; - this._viewportScrollY = scrollY; + this._viewportScrollX = scrollX; + this._viewportScrollY = scrollY; - this._moveObserver = scene.onPointerMoveObserver.add((moveEvt: IPointerEvent | IMouseEvent) => { - const { offsetX: moveOffsetX, offsetY: moveOffsetY } = moveEvt; - }); + this._moveObserver = scene.onPointerMoveObserver.add((moveEvt: IPointerEvent | IMouseEvent) => { + const { offsetX: moveOffsetX, offsetY: moveOffsetY } = moveEvt; + }); - this._upObserver = scene.onPointerUpObserver.add((upEvt: IPointerEvent | IMouseEvent) => { - scene.onPointerMoveObserver.remove(this._moveObserver); - scene.onPointerUpObserver.remove(this._upObserver); - scene.enableEvent(); - }); + this._upObserver = scene.onPointerUpObserver.add((upEvt: IPointerEvent | IMouseEvent) => { + scene.onPointerMoveObserver.remove(this._moveObserver); + scene.onPointerUpObserver.remove(this._upObserver); + scene.enableEvent(); + }); - state.stopPropagation(); - }); + state.stopPropagation(); + }) + ) + ); } private _getOutlinePosition(width: number, height: number, scaleX: number, scaleY: number) { @@ -700,13 +720,21 @@ export class Transformer implements ITransformerConfig { } private _attachHover(o: BaseObject, cursorIn: CURSOR_TYPE, cursorOut: CURSOR_TYPE) { - o.onPointerEnterObserver.add(() => { - o.cursor = cursorIn; - }); - - o.onPointerLeaveObserver.add(() => { - o.cursor = cursorOut; - }); + this.disposeWithMe( + toDisposable( + o.onPointerEnterObserver.add(() => { + o.cursor = cursorIn; + }) + ) + ); + + this.disposeWithMe( + toDisposable( + o.onPointerLeaveObserver.add(() => { + o.cursor = cursorOut; + }) + ) + ); } private _clearControl() { diff --git a/packages/engine-render/src/scene.input-manager.ts b/packages/engine-render/src/scene.input-manager.ts index e6ba5cfa6ec..b06e9ec6b46 100644 --- a/packages/engine-render/src/scene.input-manager.ts +++ b/packages/engine-render/src/scene.input-manager.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import type { Nullable, Observer } from '@univerjs/core'; +import { Disposable, type Nullable, type Observer, toDisposable } from '@univerjs/core'; import type { BaseObject } from './base-object'; import { RENDER_CLASS_TYPE } from './basics/const'; @@ -24,7 +24,7 @@ import { Vector2 } from './basics/vector2'; import type { ThinScene } from './thin-scene'; import type { Viewport } from './viewport'; -export class InputManager { +export class InputManager extends Disposable { /** The distance in pixel that you have to move to prevent some events */ static DragMovementThreshold = 2; // in pixels @@ -82,10 +82,11 @@ export class InputManager { private _currentObject: Nullable; constructor(scene: ThinScene) { + super(); this._scene = scene; } - // 处理事件,比如mouseleave,mouseenter的触发。 + // Handle events such as triggering mouseleave and mouseenter. mouseLeaveEnterHandler(evt: IMouseEvent) { const o = this._currentObject; if (o === null || o === undefined) { @@ -295,6 +296,12 @@ export class InputManager { } }); + this.disposeWithMe( + toDisposable( + this._onInputObserver + ) + ); + this._alreadyAttached = true; } diff --git a/packages/engine-render/src/scene.ts b/packages/engine-render/src/scene.ts index bd51c349bfc..a087b9b99e2 100644 --- a/packages/engine-render/src/scene.ts +++ b/packages/engine-render/src/scene.ts @@ -15,7 +15,7 @@ */ import type { IKeyValue, Nullable } from '@univerjs/core'; -import { sortRules, sortRulesByDesc } from '@univerjs/core'; +import { sortRules, sortRulesByDesc, toDisposable } from '@univerjs/core'; import { BehaviorSubject } from 'rxjs'; import type { BaseObject } from './base-object'; @@ -84,9 +84,14 @@ export class Scene extends ThinScene { const parent = this._parent as SceneViewer; parent.addSubScene(this); } - this._parent?.onTransformChangeObservable.add((change: ITransformChangeState) => { - this._setTransForm(); - }); + + this.disposeWithMe( + toDisposable( + this._parent?.onTransformChangeObservable.add((change: ITransformChangeState) => { + this._setTransForm(); + }) + ) + ); } get ancestorScaleX() { @@ -668,6 +673,7 @@ export class Scene extends ThinScene { this.clearLayer(); this.clearViewports(); this.detachControl(); + this.onTransformChangeObservable?.clear(); this._transformer?.dispose(); this.onPointerDownObserver.clear(); this.onPointerMoveObserver.clear(); @@ -679,6 +685,7 @@ export class Scene extends ThinScene { this.onMouseWheelObserver.clear(); this.onKeyDownObservable.clear(); this.onKeyUpObservable.clear(); + super.dispose(); } // Determine the only object selected diff --git a/packages/engine-render/src/shape/base-scroll-bar.ts b/packages/engine-render/src/shape/base-scroll-bar.ts index d9671d2b9aa..505b746a4e5 100644 --- a/packages/engine-render/src/shape/base-scroll-bar.ts +++ b/packages/engine-render/src/shape/base-scroll-bar.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import type { Nullable } from '@univerjs/core'; +import { Disposable, type Nullable } from '@univerjs/core'; import type { Vector2 } from '../basics/vector2'; import type { UniverRenderingContext } from '../context'; @@ -38,7 +38,7 @@ export interface IScrollBarProps { mainScene?: ThinScene; } -export class BaseScrollBar { +export class BaseScrollBar extends Disposable { enableHorizontal: boolean = true; enableVertical: boolean = true; diff --git a/packages/engine-render/src/thin-scene.ts b/packages/engine-render/src/thin-scene.ts index 547cfdaa0fd..b9ccef19cee 100644 --- a/packages/engine-render/src/thin-scene.ts +++ b/packages/engine-render/src/thin-scene.ts @@ -15,7 +15,7 @@ */ import type { EventState, IKeyValue, Nullable, Observer } from '@univerjs/core'; -import { Observable } from '@univerjs/core'; +import { Disposable, Observable } from '@univerjs/core'; import type { BaseObject } from './base-object'; import type { EVENT_TYPE } from './basics/const'; @@ -26,7 +26,7 @@ import { Transform } from './basics/transform'; import type { IViewportBound, Vector2 } from './basics/vector2'; import type { UniverRenderingContext } from './context'; -export abstract class ThinScene { +export abstract class ThinScene extends Disposable { onTransformChangeObservable = new Observable(); onPointerDownObserver = new Observable(); @@ -66,6 +66,7 @@ export abstract class ThinScene { private _evented = true; constructor(sceneKey: string) { + super(); this._sceneKey = sceneKey; } diff --git a/packages/sheets-ui/src/controllers/editor/end-edit.controller.ts b/packages/sheets-ui/src/controllers/editor/end-edit.controller.ts index 19fb8bfe41a..15ccccf7a35 100644 --- a/packages/sheets-ui/src/controllers/editor/end-edit.controller.ts +++ b/packages/sheets-ui/src/controllers/editor/end-edit.controller.ts @@ -31,6 +31,7 @@ import { IUniverInstanceService, LifecycleStages, OnLifecycle, + toDisposable, Tools, } from '@univerjs/core'; import { MoveCursorOperation, MoveSelectionOperation } from '@univerjs/docs'; @@ -109,6 +110,8 @@ export class EndEditController extends Disposable { const { document: documentComponent } = editorObject; documentComponent.onPointerDownObserver.remove(this._cursorChangeObservers); + + super.dispose(); } private _initialize() { @@ -316,11 +319,15 @@ export class EndEditController extends Disposable { const { document: documentComponent } = editorObject; - this._cursorChangeObservers = documentComponent.onPointerDownObserver.add(() => { - if (this._isCursorChange === CursorChange.StartEditor) { - this._isCursorChange = CursorChange.CursorChange; - } - }); + this.disposeWithMe( + toDisposable( + documentComponent.onPointerDownObserver.add(() => { + if (this._isCursorChange === CursorChange.StartEditor) { + this._isCursorChange = CursorChange.CursorChange; + } + }) + ) + ); } private _commandExecutedListener() { diff --git a/packages/sheets-ui/src/controllers/editor/formula-editor.controller.ts b/packages/sheets-ui/src/controllers/editor/formula-editor.controller.ts index 55a484efd8d..6871cbe6b0c 100644 --- a/packages/sheets-ui/src/controllers/editor/formula-editor.controller.ts +++ b/packages/sheets-ui/src/controllers/editor/formula-editor.controller.ts @@ -41,6 +41,7 @@ import { TextSelectionManagerService, VIEWPORT_KEY, } from '@univerjs/docs'; +import type { RenderComponentType } from '@univerjs/engine-render'; import { DeviceInputEventType, IRenderManagerService, ScrollBar } from '@univerjs/engine-render'; import type { IMoveRangeMutationParams, ISetRangeValuesMutationParams } from '@univerjs/sheets'; import { MoveRangeMutation, SetRangeValuesMutation } from '@univerjs/sheets'; @@ -55,7 +56,7 @@ import { IEditorBridgeService } from '../../services/editor-bridge.service'; @OnLifecycle(LifecycleStages.Steady, FormulaEditorController) export class FormulaEditorController extends RxDisposable { - private _loadedMap: Set = new Set(); + private _loadedMap = new WeakSet(); constructor( @IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService, @@ -105,9 +106,20 @@ export class FormulaEditorController extends RxDisposable { return; } - if (!this._loadedMap.has(unitId)) { + const formulaEditorDocObject = this._renderManagerService.getRenderById(unitId); + if (formulaEditorDocObject == null) { + return; + } + + const { mainComponent: documentComponent } = formulaEditorDocObject; + + if (documentComponent == null) { + return; + } + + if (!this._loadedMap.has(documentComponent)) { this._initialMain(unitId); - this._loadedMap.add(unitId); + this._loadedMap.add(documentComponent); } } diff --git a/packages/sheets-ui/src/controllers/editor/start-edit.controller.ts b/packages/sheets-ui/src/controllers/editor/start-edit.controller.ts index d744c42069a..fb51748dd32 100644 --- a/packages/sheets-ui/src/controllers/editor/start-edit.controller.ts +++ b/packages/sheets-ui/src/controllers/editor/start-edit.controller.ts @@ -99,7 +99,7 @@ export class StartEditController extends Disposable { } override dispose(): void { - + super.dispose(); } private _initialize() { @@ -509,7 +509,12 @@ export class StartEditController extends Disposable { (eventType === DeviceInputEventType.Dblclick && isInArrayFormulaRange) ) { const snapshot = Tools.deepClone(documentDataModel.snapshot) as IDocumentData; - const documentViewModel = this._docViewModelManagerService.getCurrent()?.docViewModel!; + const documentViewModel = this._docViewModelManagerService.getViewModel(editorUnitId); + + if (documentViewModel == null) { + return; + } + this._resetBodyStyle(snapshot.body!); documentDataModel.reset(snapshot); diff --git a/packages/sheets-ui/src/controllers/freeze.controller.ts b/packages/sheets-ui/src/controllers/freeze.controller.ts index df78af687c9..43490bb5635 100644 --- a/packages/sheets-ui/src/controllers/freeze.controller.ts +++ b/packages/sheets-ui/src/controllers/freeze.controller.ts @@ -153,6 +153,7 @@ export class FreezeController extends Disposable { override dispose(): void { super.dispose(); + this._clearFreeze(); } private _initialize() { diff --git a/packages/sheets-ui/src/services/selection/selection-render.service.ts b/packages/sheets-ui/src/services/selection/selection-render.service.ts index 505318afb4e..f68c907cb60 100644 --- a/packages/sheets-ui/src/services/selection/selection-render.service.ts +++ b/packages/sheets-ui/src/services/selection/selection-render.service.ts @@ -478,6 +478,11 @@ export class SelectionRenderService implements ISelectionRenderService { reset() { this._clearSelectionControls(); + + this._moveObserver?.dispose(); + this._upObserver?.dispose(); + this._downObserver?.dispose(); + this._moveObserver = null; this._upObserver = null; this._downObserver = null; diff --git a/packages/sheets-ui/src/services/selection/selection-shape.ts b/packages/sheets-ui/src/services/selection/selection-shape.ts index 16d58ceffbf..c5c3aa5945f 100644 --- a/packages/sheets-ui/src/services/selection/selection-shape.ts +++ b/packages/sheets-ui/src/services/selection/selection-shape.ts @@ -15,7 +15,7 @@ */ import type { IRangeWithCoord, ISelectionCellWithCoord, Nullable, ThemeService } from '@univerjs/core'; -import { ColorKit, RANGE_TYPE } from '@univerjs/core'; +import { ColorKit, Disposable, RANGE_TYPE, toDisposable } from '@univerjs/core'; import type { Scene } from '@univerjs/engine-render'; import { cancelRequestFrame, DEFAULT_SELECTION_LAYER_INDEX, FIX_ONE_PIXEL_BLUR_OFFSET, Group, Rect, requestNewFrame, TRANSFORM_CHANGE_OBSERVABLE_TYPE } from '@univerjs/engine-render'; import type { ISelectionStyle, ISelectionWidgetConfig, ISelectionWithCoordAndStyle } from '@univerjs/sheets'; @@ -68,7 +68,7 @@ const SELECTION_TITLE_HIGHLIGHT_ALPHA = 0.3; /** * The main selection canvas component */ -export class SelectionShape { +export class SelectionShape extends Disposable { private _leftControl!: Rect; private _rightControl!: Rect; private _topControl!: Rect; @@ -137,6 +137,7 @@ export class SelectionShape { private _isHeaderHighlight: boolean = true, private readonly _themeService: ThemeService ) { + super(); this._initialize(); } @@ -306,6 +307,9 @@ export class SelectionShape { this._bottomLeftWidget?.dispose(); this._bottomCenterWidget?.dispose(); this._bottomRightWidget?.dispose(); + + super.dispose(); + this._dispose$.next(this); this._dispose$.complete(); } @@ -610,15 +614,20 @@ export class SelectionShape { this._selectionShape.zIndex = zIndex; const scene = this.getScene(); + scene.addObject(this._selectionShape, SHEET_COMPONENT_SELECTION_LAYER_INDEX); - scene.onTransformChangeObservable.add((state) => { - if (state.type !== TRANSFORM_CHANGE_OBSERVABLE_TYPE.scale) { - return; - } + this.disposeWithMe( + toDisposable( + scene.onTransformChangeObservable.add((state) => { + if (state.type !== TRANSFORM_CHANGE_OBSERVABLE_TYPE.scale) { + return; + } - this._updateControl(this._currentStyle, this._rowHeaderWidth, this._columnHeaderHeight); - }); + this._updateControl(this._currentStyle, this._rowHeaderWidth, this._columnHeaderHeight); + }) + ) + ); this._initialTitle(); } diff --git a/packages/sheets-ui/src/services/sheet-skeleton-manager.service.ts b/packages/sheets-ui/src/services/sheet-skeleton-manager.service.ts index 23e852a4737..21f9f06f21c 100644 --- a/packages/sheets-ui/src/services/sheet-skeleton-manager.service.ts +++ b/packages/sheets-ui/src/services/sheet-skeleton-manager.service.ts @@ -76,6 +76,21 @@ export class SheetSkeletonManagerService implements IDisposable { } setCurrent(searchParam: ISheetSkeletonManagerSearch): Nullable { + this._setCurrent(searchParam); + } + + private _compareSearch(param1: Nullable, param2: Nullable) { + if (param1 == null || param2 == null) { + return false; + } + + if (param1.commandId === param2.commandId && param1.sheetId === param2.sheetId && param1.unitId === param2.unitId) { + return true; + } + return false; + } + + private _setCurrent(searchParam: ISheetSkeletonManagerSearch): Nullable { const param = this._getCurrentBySearch(searchParam); if (param != null) { this._reCalculate(param); @@ -107,8 +122,6 @@ export class SheetSkeletonManagerService implements IDisposable { this._currentSkeletonBefore$.next(nextParam); this._currentSkeleton$.next(nextParam); - - return this.getCurrent(); } reCalculate() { diff --git a/packages/sheets-ui/src/views/sheet-canvas-view.ts b/packages/sheets-ui/src/views/sheet-canvas-view.ts index 2821af37bb6..9f018da7c6a 100644 --- a/packages/sheets-ui/src/views/sheet-canvas-view.ts +++ b/packages/sheets-ui/src/views/sheet-canvas-view.ts @@ -15,7 +15,7 @@ */ import type { Nullable, Workbook, Worksheet } from '@univerjs/core'; -import { ICommandService, IUniverInstanceService, LifecycleStages, OnLifecycle, RxDisposable } from '@univerjs/core'; +import { ICommandService, IUniverInstanceService, LifecycleStages, OnLifecycle, RxDisposable, toDisposable } from '@univerjs/core'; import type { IRender, IWheelEvent, Scene } from '@univerjs/engine-render'; import { IRenderManagerService, @@ -48,10 +48,6 @@ export class SheetCanvasView extends RxDisposable { private _currentWorkbook!: Workbook; - private _loadedMap = new Set(); - - private _isLoadedEditor = false; - private readonly _fps$ = new BehaviorSubject(''); readonly fps$ = this._fps$.asObservable(); @@ -93,10 +89,9 @@ export class SheetCanvasView extends RxDisposable { } const unitId = workbook.getUnitId(); - if (!this._loadedMap.has(unitId)) { + if (!this._renderManagerService.has(unitId)) { this._currentWorkbook = workbook; this._addNewRender(); - this._loadedMap.add(unitId); } } @@ -261,88 +256,92 @@ export class SheetCanvasView extends RxDisposable { }); // mouse scroll - scene.onMouseWheelObserver.add((evt: IWheelEvent, state) => { - if (evt.ctrlKey) { - return; - } - - let offsetX = 0; - let offsetY = 0; - - const isLimitedStore = viewMain.limitedScroll(); - if (evt.inputIndex === PointerInput.MouseWheelX) { - const deltaFactor = Math.abs(evt.deltaX); - // let magicNumber = deltaFactor < 40 ? 2 : deltaFactor < 80 ? 3 : 4; - const scrollNum = deltaFactor; - - if (evt.deltaX > 0) { - offsetX = scrollNum; - } else { - offsetX = -scrollNum; - } - this._commandService.executeCommand(SetScrollRelativeCommand.id, { offsetX }); - - // 临界点时执行浏览器行为 - if (scene.getParent().classType === RENDER_CLASS_TYPE.SCENE_VIEWER) { - if (!isLimitedStore?.isLimitedX) { - state.stopPropagation(); - } - } else if (viewMain.isWheelPreventDefaultX) { - evt.preventDefault(); - } else if (!isLimitedStore?.isLimitedX) { - evt.preventDefault(); - } - } - if (evt.inputIndex === PointerInput.MouseWheelY) { - const deltaFactor = Math.abs(evt.deltaY); - // let magicNumber = deltaFactor < 40 ? 2 : deltaFactor < 80 ? 3 : 4; - let scrollNum = deltaFactor; - if (evt.shiftKey) { - scrollNum *= 3; - if (evt.deltaY > 0) { - offsetX = scrollNum; - } else { - offsetX = -scrollNum; + this.disposeWithMe( + toDisposable( + scene.onMouseWheelObserver.add((evt: IWheelEvent, state) => { + if (evt.ctrlKey) { + return; } - this._commandService.executeCommand(SetScrollRelativeCommand.id, { offsetX }); - // 临界点时执行浏览器行为 - if (scene.getParent().classType === RENDER_CLASS_TYPE.SCENE_VIEWER) { - if (!isLimitedStore?.isLimitedX) { - state.stopPropagation(); + let offsetX = 0; + let offsetY = 0; + + const isLimitedStore = viewMain.limitedScroll(); + if (evt.inputIndex === PointerInput.MouseWheelX) { + const deltaFactor = Math.abs(evt.deltaX); + // let magicNumber = deltaFactor < 40 ? 2 : deltaFactor < 80 ? 3 : 4; + const scrollNum = deltaFactor; + + if (evt.deltaX > 0) { + offsetX = scrollNum; + } else { + offsetX = -scrollNum; + } + this._commandService.executeCommand(SetScrollRelativeCommand.id, { offsetX }); + + // 临界点时执行浏览器行为 + if (scene.getParent().classType === RENDER_CLASS_TYPE.SCENE_VIEWER) { + if (!isLimitedStore?.isLimitedX) { + state.stopPropagation(); + } + } else if (viewMain.isWheelPreventDefaultX) { + evt.preventDefault(); + } else if (!isLimitedStore?.isLimitedX) { + evt.preventDefault(); } - } else if (viewMain.isWheelPreventDefaultX) { - evt.preventDefault(); - } else if (!isLimitedStore?.isLimitedX) { - evt.preventDefault(); - } - } else { - if (evt.deltaY > 0) { - offsetY = scrollNum; - } else { - offsetY = -scrollNum; } - this._commandService.executeCommand(SetScrollRelativeCommand.id, { offsetY }); - - // 临界点时执行浏览器行为 - if (scene.getParent().classType === RENDER_CLASS_TYPE.SCENE_VIEWER) { - if (!isLimitedStore?.isLimitedY) { - state.stopPropagation(); + if (evt.inputIndex === PointerInput.MouseWheelY) { + const deltaFactor = Math.abs(evt.deltaY); + // let magicNumber = deltaFactor < 40 ? 2 : deltaFactor < 80 ? 3 : 4; + let scrollNum = deltaFactor; + if (evt.shiftKey) { + scrollNum *= 3; + if (evt.deltaY > 0) { + offsetX = scrollNum; + } else { + offsetX = -scrollNum; + } + this._commandService.executeCommand(SetScrollRelativeCommand.id, { offsetX }); + + // 临界点时执行浏览器行为 + if (scene.getParent().classType === RENDER_CLASS_TYPE.SCENE_VIEWER) { + if (!isLimitedStore?.isLimitedX) { + state.stopPropagation(); + } + } else if (viewMain.isWheelPreventDefaultX) { + evt.preventDefault(); + } else if (!isLimitedStore?.isLimitedX) { + evt.preventDefault(); + } + } else { + if (evt.deltaY > 0) { + offsetY = scrollNum; + } else { + offsetY = -scrollNum; + } + this._commandService.executeCommand(SetScrollRelativeCommand.id, { offsetY }); + + // 临界点时执行浏览器行为 + if (scene.getParent().classType === RENDER_CLASS_TYPE.SCENE_VIEWER) { + if (!isLimitedStore?.isLimitedY) { + state.stopPropagation(); + } + } else if (viewMain.isWheelPreventDefaultY) { + evt.preventDefault(); + } else if (!isLimitedStore?.isLimitedY) { + evt.preventDefault(); + } } - } else if (viewMain.isWheelPreventDefaultY) { - evt.preventDefault(); - } else if (!isLimitedStore?.isLimitedY) { - evt.preventDefault(); } - } - } - if (evt.inputIndex === PointerInput.MouseWheelZ) { - // TODO - // ... - } - - this._scene.makeDirty(true); - }); + if (evt.inputIndex === PointerInput.MouseWheelZ) { + // TODO + // ... + } + + this._scene.makeDirty(true); + }) + ) + ); // create a scroll bar new ScrollBar(viewMain); diff --git a/packages/ui/src/components/editor/TextEditor.tsx b/packages/ui/src/components/editor/TextEditor.tsx index dfe07823aae..68bb5507d30 100644 --- a/packages/ui/src/components/editor/TextEditor.tsx +++ b/packages/ui/src/components/editor/TextEditor.tsx @@ -67,6 +67,8 @@ export function TextEditor(props: ITextEditorProps & MyComponentProps): JSX.Elem // Clean up on unmount return () => { resizeObserver.unobserve(editor); + + editorService.unRegister(id); }; }, []); diff --git a/packages/ui/src/services/editor/editor.service.ts b/packages/ui/src/services/editor/editor.service.ts index e7b0e7efc51..cfee41c332a 100644 --- a/packages/ui/src/services/editor/editor.service.ts +++ b/packages/ui/src/services/editor/editor.service.ts @@ -62,6 +62,10 @@ class Editor { } + get documentDataModel() { + return this._param.documentDataModel; + } + get editorUnitId() { return this._param.editorUnitId; } @@ -344,6 +348,7 @@ export class EditorService extends Disposable implements IEditorService, IDispos dispose(): void { this._state$.complete(); this._editors.clear(); + super.dispose(); } getEditor(id?: string): Readonly> { @@ -433,6 +438,16 @@ export class EditorService extends Disposable implements IEditorService, IDispos } unRegister(editorUnitId: string) { + const editor = this._editors.get(editorUnitId); + + if (editor == null) { + return; + } + + this._renderManagerService.removeItem(editorUnitId); + + editor.documentDataModel.dispose(); + this._editors.delete(editorUnitId); this._currentUniverService.disposeDocument(editorUnitId);