From 5465729c5c6351c6c3d798f9baf72eec60c3b18d Mon Sep 17 00:00:00 2001 From: Dushusir <1414556676@qq.com> Date: Tue, 14 May 2024 19:12:14 +0800 Subject: [PATCH 1/8] feat(render-engine): add drag event listener,Facade add onCellPointerOver,onCellDragOver,onCellDrop --- examples/public/sheets/index.html | 5 +- packages/engine-render/src/base-object.ts | 47 ++++++- packages/engine-render/src/basics/i-events.ts | 11 ++ packages/engine-render/src/engine.ts | 115 ++++++++++++++++ .../engine-render/src/scene.input-manager.ts | 86 +++++++++++- packages/engine-render/src/scene.ts | 46 ++++++- packages/engine-render/src/thin-scene.ts | 22 +++- .../sheets/__tests__/f-sheet-hooks.spec.ts | 2 +- .../facade/src/apis/sheets/f-sheet-hooks.ts | 41 +++++- packages/sheets-ui/src/common/utils.ts | 71 +++++++++- .../src/controllers/drag-render.controller.ts | 63 +++++++++ packages/sheets-ui/src/index.ts | 3 + .../src/services/drag-manager.service.ts | 124 ++++++++++++++++++ .../src/services/hover-manager.service.ts | 71 +++------- packages/sheets-ui/src/sheets-ui-plugin.ts | 4 + 15 files changed, 641 insertions(+), 70 deletions(-) create mode 100644 packages/sheets-ui/src/controllers/drag-render.controller.ts create mode 100644 packages/sheets-ui/src/services/drag-manager.service.ts diff --git a/examples/public/sheets/index.html b/examples/public/sheets/index.html index c228b9f521f..744b31d9b6b 100644 --- a/examples/public/sheets/index.html +++ b/examples/public/sheets/index.html @@ -24,7 +24,10 @@ -
+ +
diff --git a/packages/engine-render/src/base-object.ts b/packages/engine-render/src/base-object.ts index 017bbef7994..732df6cfc21 100644 --- a/packages/engine-render/src/base-object.ts +++ b/packages/engine-render/src/base-object.ts @@ -19,7 +19,7 @@ import { Disposable, Observable } from '@univerjs/core'; import type { EVENT_TYPE } from './basics/const'; import { CURSOR_TYPE, RENDER_CLASS_TYPE } from './basics/const'; -import type { IMouseEvent, IPointerEvent, IWheelEvent } from './basics/i-events'; +import type { IDragEvent, IMouseEvent, IPointerEvent, IWheelEvent } from './basics/i-events'; import type { IObjectFullState, ITransformChangeState } from './basics/interfaces'; import { TRANSFORM_CHANGE_OBSERVABLE_TYPE } from './basics/interfaces'; import { generateRandomKey, toPx } from './basics/tools'; @@ -72,6 +72,14 @@ export abstract class BaseObject extends Disposable { onPointerEnterObserver = new Observable(); + onDragLeaveObserver = new Observable(); + + onDragOverObserver = new Observable(); + + onDragEnterObserver = new Observable(); + + onDropObserver = new Observable(); + onIsAddedToParentObserver = new Observable(); onDisposeObserver = new Observable(); @@ -670,9 +678,40 @@ export abstract class BaseObject extends Disposable { return true; } + triggerDragLeave(evt: IDragEvent | IMouseEvent) { + if (!this.onDragLeaveObserver.notifyObservers(evt)?.stopPropagation) { + this._parent?.triggerDragLeave(evt); + return false; + } + return true; + } + + triggerDragOver(evt: IDragEvent | IMouseEvent) { + if (!this.onDragOverObserver.notifyObservers(evt)?.stopPropagation) { + this._parent?.triggerDragOver(evt); + return false; + } + return true; + } + + triggerDragEnter(evt: IDragEvent | IMouseEvent) { + if (!this.onDragEnterObserver.notifyObservers(evt)?.stopPropagation) { + this._parent?.triggerDragEnter(evt); + return false; + } + return true; + } + + triggerDrop(evt: IDragEvent | IMouseEvent) { + if (!this.onDropObserver.notifyObservers(evt)?.stopPropagation) { + this._parent?.triggerDrop(evt); + return false; + } + return true; + } + override dispose() { super.dispose(); - this.onTransformChangeObservable.clear(); this.onPointerDownObserver.clear(); this.onPointerMoveObserver.clear(); @@ -682,6 +721,10 @@ export abstract class BaseObject extends Disposable { this.onPointerLeaveObserver.clear(); this.onPointerOverObserver.clear(); this.onPointerEnterObserver.clear(); + this.onDragLeaveObserver.clear(); + this.onDragOverObserver.clear(); + this.onDragEnterObserver.clear(); + this.onDropObserver.clear(); this.onDblclickObserver.clear(); this.onTripleClickObserver.clear(); this.onIsAddedToParentObserver.clear(); diff --git a/packages/engine-render/src/basics/i-events.ts b/packages/engine-render/src/basics/i-events.ts index 713fddd635f..bdfce9345b2 100644 --- a/packages/engine-render/src/basics/i-events.ts +++ b/packages/engine-render/src/basics/i-events.ts @@ -300,6 +300,17 @@ export interface IPointerEvent extends IMouseEvent { pointerType: string; } +/** + * Native friendly interface for DragEvent Object + */ +export interface IDragEvent extends IMouseEvent { + // Properties + /** + * Holds the drag operation's data + */ + dataTransfer: DataTransfer; +} + /** * Native friendly interface for WheelEvent Object */ diff --git a/packages/engine-render/src/engine.ts b/packages/engine-render/src/engine.ts index b93b8a5ca50..bb692e95762 100644 --- a/packages/engine-render/src/engine.ts +++ b/packages/engine-render/src/engine.ts @@ -79,6 +79,14 @@ export class Engine extends ThinEngine { private _pointerLeaveEvent!: (evt: any) => void; + private _dragEnterEvent!: (evt: any) => void; + + private _dragLeaveEvent!: (evt: any) => void; + + private _dragOverEvent!: (evt: any) => void; + + private _dropEvent!: (evt: any) => void; + private _remainCapture: number = -1; /** previous pointer position */ @@ -103,6 +111,8 @@ export class Engine extends ThinEngine { this._handleKeyboardAction(); this._handlePointerAction(); + this._handleDragAction(); + if (mode !== CanvasRenderMode.Printing) { this._matchMediaHandler(); } @@ -233,8 +243,13 @@ export class Engine extends ThinEngine { canvasEle.removeEventListener(`${eventPrefix}down`, this._pointerDownEvent); canvasEle.removeEventListener(`${eventPrefix}up`, this._pointerUpEvent); canvasEle.removeEventListener('blur', this._pointerBlurEvent); + canvasEle.removeEventListener('dragenter', this._dragEnterEvent); + canvasEle.removeEventListener('dragleave', this._dragLeaveEvent); + canvasEle.removeEventListener('dragover', this._dragOverEvent); + canvasEle.removeEventListener('drop', this._dropEvent); canvasEle.removeEventListener(this._getWheelEventName(), this._pointerWheelEvent); + this._activeRenderLoops = []; this.getCanvas().dispose(); // this._canvas = null; // 不应该这么做, 上面已经调用了 _canvas 的 dispose 方法 @@ -664,6 +679,106 @@ export class Engine extends ThinEngine { ); } + private _handleDragAction() { + this._dragEnterEvent = (evt: any) => { + const deviceType = this._getPointerType(evt); + // Store previous values for event + const deviceEvent = evt as IPointerEvent; + deviceEvent.deviceType = deviceType; + + deviceEvent.currentState = 4; + + this.onInputChangedObservable.notifyObservers(deviceEvent); + }; + + this._dragLeaveEvent = (evt: any) => { + const deviceType = this._getPointerType(evt); + // Store previous values for event + const deviceEvent = evt as IPointerEvent; + deviceEvent.deviceType = deviceType; + + deviceEvent.currentState = 5; + + this.onInputChangedObservable.notifyObservers(deviceEvent); + }; + + this._dragOverEvent = (evt: any) => { + // prevent default to allow drop + evt.preventDefault(); + + const deviceType = this._getPointerType(evt); + // Store previous values for event + const previousHorizontal = this.pointer[PointerInput.Horizontal]; + const previousVertical = this.pointer[PointerInput.Vertical]; + const previousDeltaHorizontal = this.pointer[PointerInput.DeltaHorizontal]; + const previousDeltaVertical = this.pointer[PointerInput.DeltaVertical]; + + this.pointer[PointerInput.Horizontal] = evt.clientX; + this.pointer[PointerInput.Vertical] = evt.clientY; + this.pointer[PointerInput.DeltaHorizontal] = evt.movementX; + this.pointer[PointerInput.DeltaVertical] = evt.movementY; + // console.log('pointerMoveEvent_1', previousHorizontal, evt.clientX, previousVertical, evt.clientY, this._pointer); + const deviceEvent = evt as IPointerEvent; + deviceEvent.deviceType = deviceType; + + if (previousHorizontal !== evt.clientX) { + deviceEvent.inputIndex = PointerInput.Horizontal; + deviceEvent.previousState = previousHorizontal; + deviceEvent.currentState = this.pointer[PointerInput.Horizontal]; + + this.onInputChangedObservable.notifyObservers(deviceEvent); + } + if (previousVertical !== evt.clientY) { + deviceEvent.inputIndex = PointerInput.Vertical; + deviceEvent.previousState = previousVertical; + deviceEvent.currentState = this.pointer[PointerInput.Vertical]; + + this.onInputChangedObservable.notifyObservers(deviceEvent); + } + if (this.pointer[PointerInput.DeltaHorizontal] !== 0) { + deviceEvent.inputIndex = PointerInput.DeltaHorizontal; + deviceEvent.previousState = previousDeltaHorizontal; + deviceEvent.currentState = this.pointer[PointerInput.DeltaHorizontal]; + + this.onInputChangedObservable.notifyObservers(deviceEvent); + } + if (this.pointer[PointerInput.DeltaVertical] !== 0) { + deviceEvent.inputIndex = PointerInput.DeltaVertical; + deviceEvent.previousState = previousDeltaVertical; + deviceEvent.currentState = this.pointer[PointerInput.DeltaVertical]; + + this.onInputChangedObservable.notifyObservers(deviceEvent); + } + + // Lets Propagate the event for move with same position. + if (!this._usingSafari && evt.button !== -1) { + deviceEvent.inputIndex = evt.button + 2; + deviceEvent.previousState = this.pointer[evt.button + 2]; + this.pointer[evt.button + 2] = this.pointer[evt.button + 2] ? 0 : 1; // Reverse state of button if evt.button has value + deviceEvent.currentState = this.pointer[evt.button + 2]; + this.onInputChangedObservable.notifyObservers(deviceEvent); + } + }; + + this._dropEvent = (evt: any) => { + const deviceType = this._getPointerType(evt); + // Store previous values for event + const deviceEvent = evt as IPointerEvent; + deviceEvent.deviceType = deviceType; + + + deviceEvent.currentState = 6; + + this.onInputChangedObservable.notifyObservers(deviceEvent); + }; + + const canvasEle = this.getCanvasElement(); + canvasEle.addEventListener('dragenter', this._dragEnterEvent); + canvasEle.addEventListener('dragleave', this._dragLeaveEvent); + canvasEle.addEventListener('dragover', this._dragOverEvent); + canvasEle.addEventListener('drop', this._dropEvent); + } + private _getWheelEventName(): string { const wheelEventName = 'onwheel' in document.createElement('div') diff --git a/packages/engine-render/src/scene.input-manager.ts b/packages/engine-render/src/scene.input-manager.ts index e49c2c0a9dd..8c8034263c6 100644 --- a/packages/engine-render/src/scene.input-manager.ts +++ b/packages/engine-render/src/scene.input-manager.ts @@ -18,7 +18,7 @@ import { Disposable, type Nullable, type Observer, toDisposable } from '@univerj import type { BaseObject } from './base-object'; import { RENDER_CLASS_TYPE } from './basics/const'; -import type { IEvent, IKeyboardEvent, IMouseEvent, IPointerEvent, IWheelEvent } from './basics/i-events'; +import type { IDragEvent, IEvent, IKeyboardEvent, IMouseEvent, IPointerEvent, IWheelEvent } from './basics/i-events'; import { DeviceType, PointerInput } from './basics/i-events'; import { Vector2 } from './basics/vector2'; import type { ThinScene } from './thin-scene'; @@ -65,6 +65,15 @@ export class InputManager extends Disposable { private _onKeyUp!: (evt: IKeyboardEvent) => void; + // Drag + private _onDragEnter!: (evt: IDragEvent) => void; + + private _onDragLeave!: (evt: IDragEvent) => void; + + private _onDragOver!: (evt: IDragEvent) => void; + + private _onDrop!: (evt: IDragEvent) => void; + private _scene!: ThinScene; private _currentMouseEnterPicked: Nullable; @@ -108,6 +117,10 @@ export class InputManager extends Disposable { this._onMouseWheel = null as unknown as (evt: IWheelEvent) => void; this._onKeyDown = null as unknown as (evt: IKeyboardEvent) => void; this._onKeyUp = null as unknown as (evt: IKeyboardEvent) => void; + this._onDragEnter = null as unknown as (evt: IDragEvent) => void; + this._onDragLeave = null as unknown as (evt: IDragEvent) => void; + this._onDragOver = null as unknown as (evt: IDragEvent) => void; + this._onDrop = null as unknown as (evt: IDragEvent) => void; } // Handle events such as triggering mouseleave and mouseenter. @@ -124,6 +137,20 @@ export class InputManager extends Disposable { } } + // Handle events such as triggering dragleave and dragenter. + dragLeaveEnterHandler(evt: IMouseEvent) { + const o = this._currentObject; + if (o === null || o === undefined) { + this._currentMouseEnterPicked?.triggerDragLeave(evt); + this._currentMouseEnterPicked = null; + } else if (o !== this._currentMouseEnterPicked) { + const previousPicked = this._currentMouseEnterPicked; + this._currentMouseEnterPicked = o; + previousPicked?.triggerDragLeave(evt); + o?.triggerDragEnter(evt); + } + } + // eslint-disable-next-line max-lines-per-function attachControl( hasDown: boolean = true, @@ -264,6 +291,44 @@ export class InputManager extends Disposable { } }; + this._onDragEnter = (evt: IDragEvent) => { + this._currentObject = this._getCurrentObject(evt.offsetX, evt.offsetY); + this._currentObject?.triggerDragOver(evt); + + this.dragLeaveEnterHandler(evt); + }; + + this._onDragLeave = (evt: IDragEvent) => { + this._currentObject = null; + + this.dragLeaveEnterHandler(evt); + }; + + this._onDragOver = (evt: IDragEvent) => { + this._currentObject = this._getCurrentObject(evt.offsetX, evt.offsetY); + const isStop = this._currentObject?.triggerDragOver(evt); + + this.dragLeaveEnterHandler(evt); + + if (this._checkDirectSceneEventTrigger(!isStop, this._currentObject)) { + if (this._scene.onDragOverObserver.hasObservers()) { + this._scene.onDragOverObserver.notifyObservers(evt); + this._scene.getEngine()?.setRemainCapture(); + } + } + }; + + this._onDrop = (evt: IDragEvent) => { + const currentObject = this._getCurrentObject(evt.offsetX, evt.offsetY); + const isStop = currentObject?.triggerDrop(evt); + + if (this._checkDirectSceneEventTrigger(!isStop, currentObject)) { + if (this._scene.onDropObserver.hasObservers()) { + this._scene.onDropObserver.notifyObservers(evt); + } + } + }; + // eslint-disable-next-line complexity this._onInputObserver = engine.onInputChangedObservable.add((eventData: IEvent) => { const evt: IEvent = eventData; @@ -278,6 +343,25 @@ export class InputManager extends Disposable { } } + // Drag Events + if ((eventData as IDragEvent).dataTransfer) { + if (hasMove && + (eventData.inputIndex === PointerInput.Horizontal || + eventData.inputIndex === PointerInput.Vertical || + eventData.inputIndex === PointerInput.DeltaHorizontal || + eventData.inputIndex === PointerInput.DeltaVertical)) { + this._onDragOver(evt as IDragEvent); + } else if (hasEnter && eventData.currentState === 4) { + this._onDragEnter(evt as IDragEvent); + } else if (hasLeave && eventData.currentState === 5) { + this._onDragLeave(evt as IDragEvent); + } else if (hasUp && eventData.currentState === 6) { + this._onDrop(evt as IDragEvent); + } + + return; + } + // Pointer Events if (eventData.deviceType === DeviceType.Mouse || eventData.deviceType === DeviceType.Touch) { if ( diff --git a/packages/engine-render/src/scene.ts b/packages/engine-render/src/scene.ts index 5f4993d1908..7298a38fce4 100644 --- a/packages/engine-render/src/scene.ts +++ b/packages/engine-render/src/scene.ts @@ -20,7 +20,7 @@ import { BehaviorSubject } from 'rxjs'; import type { BaseObject } from './base-object'; import { CURSOR_TYPE, RENDER_CLASS_TYPE } from './basics/const'; -import type { IKeyboardEvent, IMouseEvent, IPointerEvent, IWheelEvent } from './basics/i-events'; +import type { IDragEvent, IKeyboardEvent, IMouseEvent, IPointerEvent, IWheelEvent } from './basics/i-events'; import type { IObjectFullState, ISceneTransformState, ITransformChangeState } from './basics/interfaces'; import { TRANSFORM_CHANGE_OBSERVABLE_TYPE } from './basics/interfaces'; import { precisionTo, requestNewFrame } from './basics/tools'; @@ -875,6 +875,50 @@ export class Scene extends ThinScene { return true; } + override triggerDragLeave(evt: IDragEvent) { + if ( + !this.onDragLeaveObserver.notifyObservers(evt)?.stopPropagation && + this._parent.classType === RENDER_CLASS_TYPE.SCENE_VIEWER + ) { + (this._parent as SceneViewer)?.triggerDragLeave(evt); + return false; + } + return true; + } + + override triggerDragOver(evt: IDragEvent) { + if ( + !this.onDragOverObserver.notifyObservers(evt)?.stopPropagation && + this._parent.classType === RENDER_CLASS_TYPE.SCENE_VIEWER + ) { + (this._parent as SceneViewer)?.triggerDragOver(evt); + return false; + } + return true; + } + + override triggerDragEnter(evt: IDragEvent) { + if ( + !this.onDragEnterObserver.notifyObservers(evt)?.stopPropagation && + this._parent.classType === RENDER_CLASS_TYPE.SCENE_VIEWER + ) { + (this._parent as SceneViewer)?.triggerDragEnter(evt); + return false; + } + return true; + } + + override triggerDrop(evt: IDragEvent) { + if ( + !this.onDropObserver.notifyObservers(evt)?.stopPropagation && + this._parent.classType === RENDER_CLASS_TYPE.SCENE_VIEWER + ) { + (this._parent as SceneViewer)?.triggerDrop(evt); + return false; + } + return true; + } + private _createDefaultLayer(zIndex: number = 1) { const defaultLayer = new Layer(this, [], zIndex); this.addLayer(defaultLayer); diff --git a/packages/engine-render/src/thin-scene.ts b/packages/engine-render/src/thin-scene.ts index d18fd2530dc..2fdcafbe0ae 100644 --- a/packages/engine-render/src/thin-scene.ts +++ b/packages/engine-render/src/thin-scene.ts @@ -20,7 +20,7 @@ import { Disposable, Observable } from '@univerjs/core'; import type { BaseObject } from './base-object'; import type { CURSOR_TYPE, EVENT_TYPE } from './basics/const'; import { RENDER_CLASS_TYPE } from './basics/const'; -import type { IKeyboardEvent, IMouseEvent, IPointerEvent, IWheelEvent } from './basics/i-events'; +import type { IDragEvent, IKeyboardEvent, IMouseEvent, IPointerEvent, IWheelEvent } from './basics/i-events'; import type { ITransformChangeState } from './basics/interfaces'; import { Transform } from './basics/transform'; import type { Vector2 } from './basics/vector2'; @@ -41,6 +41,14 @@ export abstract class ThinScene extends Disposable { onPointerLeaveObserver = new Observable(); + onDragEnterObserver = new Observable(); + + onDragOverObserver = new Observable(); + + onDragLeaveObserver = new Observable(); + + onDropObserver = new Observable(); + onDblclickObserver = new Observable(); onTripleClickObserver = new Observable(); @@ -182,6 +190,14 @@ export abstract class ThinScene extends Disposable { abstract triggerPointerEnter(evt: IPointerEvent | IMouseEvent): void; + abstract triggerDragEnter(evt: IDragEvent | IMouseEvent): void; + + abstract triggerDragOver(evt: IDragEvent | IMouseEvent): void; + + abstract triggerDragLeave(evt: IDragEvent | IMouseEvent): void; + + abstract triggerDrop(evt: IDragEvent | IMouseEvent): void; + abstract render(parentCtx?: UniverRenderingContext): void; abstract getParent(): any; @@ -194,6 +210,10 @@ export abstract class ThinScene extends Disposable { this.onPointerUpObserver.clear(); this.onPointerEnterObserver.clear(); this.onPointerLeaveObserver.clear(); + this.onDragEnterObserver.clear(); + this.onDragOverObserver.clear(); + this.onDragLeaveObserver.clear(); + this.onDropObserver.clear(); this.onDblclickObserver.clear(); this.onTripleClickObserver.clear(); this.onMouseWheelObserver.clear(); diff --git a/packages/facade/src/apis/sheets/__tests__/f-sheet-hooks.spec.ts b/packages/facade/src/apis/sheets/__tests__/f-sheet-hooks.spec.ts index fa0a5a92414..2213df15752 100644 --- a/packages/facade/src/apis/sheets/__tests__/f-sheet-hooks.spec.ts +++ b/packages/facade/src/apis/sheets/__tests__/f-sheet-hooks.spec.ts @@ -109,7 +109,7 @@ describe('Test FSheetHooks', () => { }); it('Test onCellPointerMove', () => { - sheetHooks.onCellPointerMove((cellPos) => { + sheetHooks.onCellPointerOver((cellPos) => { expect(cellPos).toEqual({ location: { workbook, worksheet, unitId: workbook.getUnitId(), subUnitId: worksheet.getSheetId(), row: 0, col: 0 }, position: { startX: 0, endX: 1, startY: 0, endY: 1 } }); }); diff --git a/packages/facade/src/apis/sheets/f-sheet-hooks.ts b/packages/facade/src/apis/sheets/f-sheet-hooks.ts index c2a3b80d26e..dcd00b7978e 100644 --- a/packages/facade/src/apis/sheets/f-sheet-hooks.ts +++ b/packages/facade/src/apis/sheets/f-sheet-hooks.ts @@ -18,21 +18,50 @@ import { type Nullable, toDisposable } from '@univerjs/core'; import type { IDisposable } from '@wendellhu/redi'; import { Inject } from '@wendellhu/redi'; -import type { IHoverCellPosition } from '@univerjs/sheets-ui'; -import { HoverManagerService } from '@univerjs/sheets-ui'; +import type { IDragCellPosition, IHoverCellPosition } from '@univerjs/sheets-ui'; +import { DragManagerService, HoverManagerService } from '@univerjs/sheets-ui'; export class FSheetHooks { constructor( - @Inject(HoverManagerService) private readonly _hoverManagerService: HoverManagerService - ) { + @Inject(HoverManagerService) private readonly _hoverManagerService: HoverManagerService, + @Inject(DragManagerService) private readonly _dragManagerService: DragManagerService + ) { // empty } /** - * Subscribe to the location information of the currently hovered cell - * @returns a disposable object that can be used to unsubscribe from the event + * The onCellPointerMove event is fired when a pointing device is moved into a cell's hit test boundaries. + * @param callback Callback function that will be called when the event is fired + * @returns A disposable object that can be used to unsubscribe from the event */ onCellPointerMove(callback: (cellPos: Nullable) => void): IDisposable { + return toDisposable(this._hoverManagerService.currentPosition$.subscribe(callback)); + } + + /** + * The onCellPointerOver event is fired when a pointing device is moved into a cell's hit test boundaries. + * @param callback Callback function that will be called when the event is fired + * @returns A disposable object that can be used to unsubscribe from the event + */ + onCellPointerOver(callback: (cellPos: Nullable) => void): IDisposable { return toDisposable(this._hoverManagerService.currentCell$.subscribe(callback)); } + + /** + * The onCellDragOver event is fired when an element or text selection is being dragged into a cell's hit test boundaries. + * @param callback Callback function that will be called when the event is fired + * @returns A disposable object that can be used to unsubscribe from the event + */ + onCellDragOver(callback: (cellPos: Nullable) => void): IDisposable { + return toDisposable(this._dragManagerService.currentCell$.subscribe(callback)); + } + + /** + * The onCellDrop event is fired when an element or text selection is being dropped on the cell + * @param callback Callback function that will be called when the event is fired + * @returns A disposable object that can be used to unsubscribe from the event + */ + onCellDrop(callback: (cellPos: Nullable) => void) { + return toDisposable(this._dragManagerService.endCell$.subscribe(callback)); + } } diff --git a/packages/sheets-ui/src/common/utils.ts b/packages/sheets-ui/src/common/utils.ts index 6dd934c7717..49696636822 100644 --- a/packages/sheets-ui/src/common/utils.ts +++ b/packages/sheets-ui/src/common/utils.ts @@ -14,13 +14,14 @@ * limitations under the License. */ -import type { ICellData, IMutationInfo, IRange, Nullable, Worksheet } from '@univerjs/core'; +import type { ICellData, IMutationInfo, IPosition, IRange, Nullable, Workbook, Worksheet } from '@univerjs/core'; import { ObjectMatrix } from '@univerjs/core'; import { Vector2 } from '@univerjs/engine-render'; -import type { ISetRangeValuesMutationParams } from '@univerjs/sheets'; +import type { ISetRangeValuesMutationParams, ISheetLocation } from '@univerjs/sheets'; import { SetRangeValuesMutation, SetRangeValuesUndoMutationFactory } from '@univerjs/sheets'; import type { IAccessor } from '@wendellhu/redi'; -import type { IBoundRectNoAngle, Scene, SpreadsheetSkeleton } from '@univerjs/engine-render'; +import type { IBoundRectNoAngle, IRender, Scene, SpreadsheetSkeleton } from '@univerjs/engine-render'; +import type { ISheetSkeletonManagerParam } from '../services/sheet-skeleton-manager.service'; import { VIEWPORT_KEY } from './keys'; export function checkCellContentInRanges(worksheet: Worksheet, ranges: IRange[]): boolean { @@ -202,3 +203,67 @@ export function transformPosition2Offset(x: number, y: number, scene: Scene, ske y: offsetY, }; } + + +export function getHoverCellPosition(currentRender: IRender, workbook: Workbook, worksheet: Worksheet, skeletonParam: ISheetSkeletonManagerParam, offsetX: number, offsetY: number) { + const { scene } = currentRender; + + const { skeleton, sheetId, unitId } = skeletonParam; + + const cellIndex = getCellIndexByOffsetWithMerge(offsetX, offsetY, scene, skeleton); + + if (!cellIndex) { + return null; + } + + const { row, col, mergeCell, actualCol, actualRow } = cellIndex; + + const location: ISheetLocation = { + unitId, + subUnitId: sheetId, + workbook, + worksheet, + row: actualRow, + col: actualCol, + }; + + let anchorCell: IRange; + + if (mergeCell) { + anchorCell = mergeCell; + } else { + anchorCell = { + startRow: row, + endRow: row, + startColumn: col, + endColumn: col, + }; + } + + const activeViewport = scene.getActiveViewportByCoord( + Vector2.FromArray([offsetX, offsetY]) + ); + + if (!activeViewport) { + return; + } + + const { scaleX, scaleY } = scene.getAncestorScale(); + + const scrollXY = { + x: activeViewport.actualScrollX, + y: activeViewport.actualScrollY, + }; + + const position: IPosition = { + startX: (skeleton.getOffsetByPositionX(anchorCell.startColumn - 1) - scrollXY.x) * scaleX, + endX: (skeleton.getOffsetByPositionX(anchorCell.endColumn) - scrollXY.x) * scaleX, + startY: (skeleton.getOffsetByPositionY(anchorCell.startRow - 1) - scrollXY.y) * scaleY, + endY: (skeleton.getOffsetByPositionY(anchorCell.endRow) - scrollXY.y) * scaleY, + }; + + return { + position, + location, + }; +} diff --git a/packages/sheets-ui/src/controllers/drag-render.controller.ts b/packages/sheets-ui/src/controllers/drag-render.controller.ts new file mode 100644 index 00000000000..6d8f72807b0 --- /dev/null +++ b/packages/sheets-ui/src/controllers/drag-render.controller.ts @@ -0,0 +1,63 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Workbook } from '@univerjs/core'; +import { Disposable, IUniverInstanceService, LifecycleStages, OnLifecycle, UniverInstanceType } from '@univerjs/core'; +import { IRenderController, IRenderManagerService } from '@univerjs/engine-render'; +import { Inject } from '@wendellhu/redi'; +import { DragManagerService } from '../services/drag-manager.service'; + +@OnLifecycle(LifecycleStages.Rendered, DragRenderController) +export class DragRenderController extends Disposable implements IRenderController { + constructor( + @IRenderManagerService private _renderManagerService: IRenderManagerService, + @Inject(DragManagerService) private _dragManagerService: DragManagerService, + @IUniverInstanceService private _univerInstanceService: IUniverInstanceService + ) { + super(); + + this._initDragEvent(); + } + + private _initDragEvent() { + const scene = this._getCurrentScene(); + + if (!scene) { + return; + } + + const dragOverObserver = scene.onDragOverObserver.add((evt) => { + this._dragManagerService.onDragOver(evt); + }); + + const dropObserver = scene.onDropObserver.add((evt) => { + this._dragManagerService.onDrop(evt); + }); + + this.disposeWithMe({ + dispose() { + scene.onDragOverObserver.remove(dragOverObserver); + scene.onDropObserver.remove(dropObserver); + }, + }); + } + + private _getCurrentScene() { + return this._renderManagerService.getRenderById( + this._univerInstanceService.getCurrentUnitForType(UniverInstanceType.UNIVER_SHEET)!.getUnitId() + )?.scene; + } +} diff --git a/packages/sheets-ui/src/index.ts b/packages/sheets-ui/src/index.ts index 06621005a07..3e83561ad96 100644 --- a/packages/sheets-ui/src/index.ts +++ b/packages/sheets-ui/src/index.ts @@ -81,8 +81,10 @@ export { SheetSkeletonManagerService } from './services/sheet-skeleton-manager.s export { UniverSheetsUIPlugin } from './sheets-ui-plugin'; export { SheetCanvasView } from './views/sheet-canvas-view'; export { HoverManagerService } from './services/hover-manager.service'; +export { DragManagerService } from './services/drag-manager.service'; export { CellAlertManagerService, CellAlertType, type ICellAlert } from './services/cell-alert-manager.service'; export { HoverRenderController } from './controllers/hover-render.controller'; +export { DragRenderController } from './controllers/drag-render.controller'; export { SHEET_VIEW_KEY } from './common/keys'; export { SheetCanvasPopManagerService, type ICanvasPopup } from './services/canvas-pop-manager.service'; export { mergeSetRangeValues } from './services/clipboard/utils'; @@ -91,3 +93,4 @@ export type { IDiscreteRange } from './controllers/utils/range-tools'; export { virtualizeDiscreteRanges, rangeToDiscreteRange } from './controllers/utils/range-tools'; export { type IHoverCellPosition } from './services/hover-manager.service'; export { AutoHeightController } from './controllers/auto-height.controller'; +export { type IDragCellPosition } from './services/drag-manager.service'; diff --git a/packages/sheets-ui/src/services/drag-manager.service.ts b/packages/sheets-ui/src/services/drag-manager.service.ts new file mode 100644 index 00000000000..14039e4e4a2 --- /dev/null +++ b/packages/sheets-ui/src/services/drag-manager.service.ts @@ -0,0 +1,124 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Nullable, Workbook } from '@univerjs/core'; +import { Disposable, IUniverInstanceService, UniverInstanceType } from '@univerjs/core'; +import { Inject } from '@wendellhu/redi'; +import { distinctUntilChanged, Subject } from 'rxjs'; +import type { IDragEvent } from '@univerjs/engine-render'; +import { IRenderManagerService } from '@univerjs/engine-render'; +import { getHoverCellPosition } from '../common/utils'; +import { ScrollManagerService } from './scroll-manager.service'; +import { SheetSkeletonManagerService } from './sheet-skeleton-manager.service'; +import type { IHoverCellPosition } from './hover-manager.service'; + +export interface IDragCellPosition extends IHoverCellPosition { + dataTransfer: DataTransfer; +} + +export class DragManagerService extends Disposable { + private _currentCell$ = new Subject>(); + currentCell$ = this._currentCell$.asObservable().pipe(distinctUntilChanged(( + (pre, aft) => ( + pre?.location?.unitId === aft?.location?.unitId + && pre?.location?.subUnitId === aft?.location?.subUnitId + && pre?.location?.row === aft?.location?.row + && pre?.location?.col === aft?.location?.col + ) + ))); + + private _endCell$ = new Subject>(); + endCell$ = this._endCell$.asObservable(); + + constructor( + @IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService, + @Inject(ScrollManagerService) private readonly _scrollManagerService: ScrollManagerService, + @Inject(SheetSkeletonManagerService) private readonly _sheetSkeletonManagerService: SheetSkeletonManagerService, + @IRenderManagerService private readonly _renderManagerService: IRenderManagerService + ) { + super(); + + this._initCellDisposableListener(); + } + + override dispose(): void { + super.dispose(); + this._currentCell$.complete(); + this._endCell$.complete(); + } + + private _initCellDisposableListener(): void { + this.disposeWithMe(this._univerInstanceService.getCurrentTypeOfUnit$(UniverInstanceType.UNIVER_SHEET).subscribe((workbook) => { + if (!workbook) { + this._currentCell$.next(null); + this._endCell$.next(null); + } + })); + } + + private _calcActiveCell(offsetX: number, offsetY: number): Nullable { + const workbook = this._univerInstanceService.getCurrentUnitForType(UniverInstanceType.UNIVER_SHEET); + if (!workbook) { + return null; + } + + const worksheet = workbook.getActiveSheet(); + const skeletonParam = this._sheetSkeletonManagerService.getCurrent(); + const currentRender = this._renderManagerService.getRenderById(workbook.getUnitId()); + + const scrollInfo = this._scrollManagerService.getCurrentScroll(); + + if (!skeletonParam || !scrollInfo || !currentRender) return; + + return getHoverCellPosition(currentRender, workbook, worksheet, skeletonParam, offsetX, offsetY); + } + + onDragOver(evt: IDragEvent) { + const { offsetX, offsetY, dataTransfer } = evt; + const activeCell = this._calcActiveCell(offsetX, offsetY); + + if (!activeCell) { + this._currentCell$.next(null); + return; + } + + const { location, position } = activeCell; + + this._currentCell$.next({ + location, + position, + dataTransfer, + }); + } + + onDrop(evt: IDragEvent) { + const { offsetX, offsetY, dataTransfer } = evt; + const activeCell = this._calcActiveCell(offsetX, offsetY); + + if (!activeCell) { + this._endCell$.next(null); + return; + } + + const { location, position } = activeCell; + + this._endCell$.next({ + location, + position, + dataTransfer, + }); + } +} diff --git a/packages/sheets-ui/src/services/hover-manager.service.ts b/packages/sheets-ui/src/services/hover-manager.service.ts index dad780d01f6..31f13e1242e 100644 --- a/packages/sheets-ui/src/services/hover-manager.service.ts +++ b/packages/sheets-ui/src/services/hover-manager.service.ts @@ -14,13 +14,13 @@ * limitations under the License. */ -import type { IPosition, IRange, Nullable, Workbook } from '@univerjs/core'; +import type { IPosition, Nullable, Workbook } from '@univerjs/core'; import { Disposable, IUniverInstanceService, UniverInstanceType } from '@univerjs/core'; import type { ISheetLocation } from '@univerjs/sheets'; import { Inject } from '@wendellhu/redi'; import { distinctUntilChanged, Subject } from 'rxjs'; -import { IRenderManagerService, Vector2 } from '@univerjs/engine-render'; -import { getCellIndexByOffsetWithMerge } from '../common/utils'; +import { IRenderManagerService } from '@univerjs/engine-render'; +import { getHoverCellPosition } from '../common/utils'; import { ScrollManagerService } from './scroll-manager.service'; import { SheetSkeletonManagerService } from './sheet-skeleton-manager.service'; @@ -31,6 +31,8 @@ export interface IHoverCellPosition { export class HoverManagerService extends Disposable { private _currentCell$ = new Subject>(); + + // Notify when hovering over different cells currentCell$ = this._currentCell$.asObservable().pipe(distinctUntilChanged(( (pre, aft) => ( pre?.location?.unitId === aft?.location?.unitId @@ -40,6 +42,9 @@ export class HoverManagerService extends Disposable { ) ))); + // Notify when mouse position changes + currentPosition$ = this._currentCell$.asObservable(); + private _lastPosition: Nullable<{ offsetX: number; offsetY: number }> = null; constructor( @@ -54,6 +59,11 @@ export class HoverManagerService extends Disposable { this._initCellDisposableListener(); } + override dispose(): void { + super.dispose(); + this._currentCell$.complete(); + } + private _initCellDisposableListener(): void { this.disposeWithMe(this._univerInstanceService.getCurrentTypeOfUnit$(UniverInstanceType.UNIVER_SHEET).subscribe((workbook) => { if (!workbook) this._currentCell$.next(null); @@ -78,64 +88,17 @@ export class HoverManagerService extends Disposable { if (!skeletonParam || !scrollInfo || !currentRender) return; - const { scene } = currentRender; - - const { skeleton, sheetId, unitId } = skeletonParam; + const hoverPosition = getHoverCellPosition(currentRender, workbook, worksheet, skeletonParam, offsetX, offsetY); - const cellIndex = getCellIndexByOffsetWithMerge(offsetX, offsetY, scene, skeleton); - - if (!cellIndex) { + if (!hoverPosition) { this._currentCell$.next(null); return; } - const { row, col, mergeCell, actualCol, actualRow } = cellIndex; - - const params: ISheetLocation = { - unitId, - subUnitId: sheetId, - workbook, - worksheet, - row: actualRow, - col: actualCol, - }; - - let anchorCell: IRange; - - if (mergeCell) { - anchorCell = mergeCell; - } else { - anchorCell = { - startRow: row, - endRow: row, - startColumn: col, - endColumn: col, - }; - } - const activeViewport = scene.getActiveViewportByCoord( - Vector2.FromArray([offsetX, offsetY]) - ); - - if (!activeViewport) { - return; - } - - const { scaleX, scaleY } = scene.getAncestorScale(); - - const scrollXY = { - x: activeViewport.actualScrollX, - y: activeViewport.actualScrollY, - }; - - const position: IPosition = { - startX: (skeleton.getOffsetByPositionX(anchorCell.startColumn - 1) - scrollXY.x) * scaleX, - endX: (skeleton.getOffsetByPositionX(anchorCell.endColumn) - scrollXY.x) * scaleX, - startY: (skeleton.getOffsetByPositionY(anchorCell.startRow - 1) - scrollXY.y) * scaleY, - endY: (skeleton.getOffsetByPositionY(anchorCell.endRow) - scrollXY.y) * scaleY, - }; + const { location, position } = hoverPosition; this._currentCell$.next({ - location: params, + location, position, }); } diff --git a/packages/sheets-ui/src/sheets-ui-plugin.ts b/packages/sheets-ui/src/sheets-ui-plugin.ts index f0de83474cf..55304bdc015 100644 --- a/packages/sheets-ui/src/sheets-ui-plugin.ts +++ b/packages/sheets-ui/src/sheets-ui-plugin.ts @@ -72,6 +72,8 @@ import { SheetContextMenuRenderController } from './controllers/render-controlle import { EditorBridgeRenderController } from './controllers/render-controllers/editor-bridge.render-controller'; import { AutoFillController } from './controllers/auto-fill.controller'; import { FormatPainterController } from './controllers/format-painter/format-painter.controller'; +import { DragRenderController } from './controllers/drag-render.controller'; +import { DragManagerService } from './services/drag-manager.service'; export class UniverSheetsUIPlugin extends Plugin { static override pluginName = 'SHEET_UI_PLUGIN'; @@ -109,6 +111,7 @@ export class UniverSheetsUIPlugin extends Plugin { [IStatusBarService, { useClass: StatusBarService }], [IMarkSelectionService, { useClass: MarkSelectionService }], [HoverManagerService], + [DragManagerService], [SheetCanvasPopManagerService], [CellAlertManagerService], @@ -160,6 +163,7 @@ export class UniverSheetsUIPlugin extends Plugin { ForceStringAlertRenderController, MarkSelectionRenderController, HoverRenderController, + DragRenderController, ForceStringRenderController, CellCustomRenderController, SheetContextMenuRenderController, From 00c8102e37a9ef2a2c7d9d03fb3c484ca8e85ddc Mon Sep 17 00:00:00 2001 From: Dushusir <1414556676@qq.com> Date: Tue, 14 May 2024 19:53:47 +0800 Subject: [PATCH 2/8] fix(sheet): drag render set context --- .../sheets-ui/src/controllers/drag-render.controller.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/sheets-ui/src/controllers/drag-render.controller.ts b/packages/sheets-ui/src/controllers/drag-render.controller.ts index 6d8f72807b0..bdd65fe710b 100644 --- a/packages/sheets-ui/src/controllers/drag-render.controller.ts +++ b/packages/sheets-ui/src/controllers/drag-render.controller.ts @@ -16,13 +16,15 @@ import type { Workbook } from '@univerjs/core'; import { Disposable, IUniverInstanceService, LifecycleStages, OnLifecycle, UniverInstanceType } from '@univerjs/core'; -import { IRenderController, IRenderManagerService } from '@univerjs/engine-render'; +import type { IRenderContext, IRenderController } from '@univerjs/engine-render'; +import { IRenderManagerService } from '@univerjs/engine-render'; import { Inject } from '@wendellhu/redi'; import { DragManagerService } from '../services/drag-manager.service'; @OnLifecycle(LifecycleStages.Rendered, DragRenderController) -export class DragRenderController extends Disposable implements IRenderController { +export class DragRenderController extends Disposable implements IRenderController { constructor( + private readonly _context: IRenderContext, @IRenderManagerService private _renderManagerService: IRenderManagerService, @Inject(DragManagerService) private _dragManagerService: DragManagerService, @IUniverInstanceService private _univerInstanceService: IUniverInstanceService From f891311d1ad27b20254a458a3160e10b1af4591c Mon Sep 17 00:00:00 2001 From: Dushusir <1414556676@qq.com> Date: Tue, 14 May 2024 20:18:58 +0800 Subject: [PATCH 3/8] test(facade): test cell hooks --- .../sheets/__tests__/f-sheet-hooks.spec.ts | 86 +++++++++++++++++-- .../facade/src/apis/sheets/f-sheet-hooks.ts | 2 +- 2 files changed, 81 insertions(+), 7 deletions(-) diff --git a/packages/facade/src/apis/sheets/__tests__/f-sheet-hooks.spec.ts b/packages/facade/src/apis/sheets/__tests__/f-sheet-hooks.spec.ts index 2213df15752..9729a0a01b4 100644 --- a/packages/facade/src/apis/sheets/__tests__/f-sheet-hooks.spec.ts +++ b/packages/facade/src/apis/sheets/__tests__/f-sheet-hooks.spec.ts @@ -21,12 +21,35 @@ import type { Injector } from '@wendellhu/redi'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { Subject } from 'rxjs'; -import type { IHoverCellPosition } from '@univerjs/sheets-ui'; -import { HoverManagerService } from '@univerjs/sheets-ui'; +import type { IDragCellPosition, IHoverCellPosition } from '@univerjs/sheets-ui'; +import { DragManagerService, HoverManagerService } from '@univerjs/sheets-ui'; import type { FUniver } from '../../facade'; import { createFacadeTestBed } from '../../__tests__/create-test-bed'; import { FSheetHooks } from '../f-sheet-hooks'; +class MockDataTransfer implements DataTransfer { + effectAllowed: 'none' | 'copy' | 'link' | 'move' | 'all' | 'copyLink' | 'copyMove' | 'linkMove' | 'uninitialized'; + files: FileList; + items: DataTransferItemList; + types: readonly string[]; + clearData(format?: string | undefined): void { + throw new Error('Method not implemented.'); + } + + getData(format: string): string { + throw new Error('Method not implemented.'); + } + + setData(format: string, data: string): void { + throw new Error('Method not implemented.'); + } + + dropEffect: 'none' | 'copy' | 'link' | 'move' = 'none'; + setDragImage(image: Element, x: number, y: number): void { + throw new Error('Method not implemented.'); + } +} + describe('Test FSheetHooks', () => { let get: Injector['get']; let injector: Injector; @@ -46,7 +69,11 @@ describe('Test FSheetHooks', () => { endColumn: number ) => Nullable; let mockHoverManagerService: Partial; - let currentCell$: Subject>; + let mockDragManagerService: Partial; + let hoverCurrentCell$: Subject>; + let hoverCurrentPosition$: Subject>; + let dragCurrentCell$: Subject>; + let dragEndCell$: Subject>; let sheetHooks: FSheetHooks; let workbook: Workbook; @@ -56,15 +83,26 @@ describe('Test FSheetHooks', () => { beforeEach(() => { // Initialize the subject - currentCell$ = new Subject>(); + hoverCurrentCell$ = new Subject>(); + hoverCurrentPosition$ = new Subject>(); + dragCurrentCell$ = new Subject>(); + dragEndCell$ = new Subject>(); // Create a mock HoverManagerService with currentCell$ mockHoverManagerService = { - currentCell$: currentCell$.asObservable(), + currentCell$: hoverCurrentCell$.asObservable(), + currentPosition$: hoverCurrentPosition$.asObservable(), + }; + + // Create a mock DragManagerService with currentCell$ + mockDragManagerService = { + currentCell$: dragCurrentCell$.asObservable(), + endCell$: dragEndCell$.asObservable(), }; const testBed = createFacadeTestBed(undefined, [ [HoverManagerService, { useValue: mockHoverManagerService }], + [DragManagerService, { useValue: mockDragManagerService }], ]); get = testBed.get; injector = testBed.injector; @@ -109,6 +147,18 @@ describe('Test FSheetHooks', () => { }); it('Test onCellPointerMove', () => { + sheetHooks.onCellPointerMove((cellPos) => { + expect(cellPos).toEqual({ location: { workbook, worksheet, unitId: workbook.getUnitId(), subUnitId: worksheet.getSheetId(), row: 0, col: 0 }, position: { startX: 0, endX: 1, startY: 0, endY: 1 } }); + }); + + // Trigger the Observable to emit a new value + const worksheet = workbook.getActiveSheet(); + const unitId = workbook.getUnitId(); + const subUnitId = worksheet.getSheetId(); + hoverCurrentPosition$.next({ location: { workbook, worksheet, unitId, subUnitId, row: 0, col: 0 }, position: { startX: 0, endX: 1, startY: 0, endY: 1 } }); + }); + + it('Test onCellPointerOver', () => { sheetHooks.onCellPointerOver((cellPos) => { expect(cellPos).toEqual({ location: { workbook, worksheet, unitId: workbook.getUnitId(), subUnitId: worksheet.getSheetId(), row: 0, col: 0 }, position: { startX: 0, endX: 1, startY: 0, endY: 1 } }); }); @@ -117,6 +167,30 @@ describe('Test FSheetHooks', () => { const worksheet = workbook.getActiveSheet(); const unitId = workbook.getUnitId(); const subUnitId = worksheet.getSheetId(); - currentCell$.next({ location: { workbook, worksheet, unitId, subUnitId, row: 0, col: 0 }, position: { startX: 0, endX: 1, startY: 0, endY: 1 } }); + hoverCurrentCell$.next({ location: { workbook, worksheet, unitId, subUnitId, row: 0, col: 0 }, position: { startX: 0, endX: 1, startY: 0, endY: 1 } }); + }); + + it('Test onCellDragOver', () => { + sheetHooks.onCellDragOver((cellPos) => { + expect(cellPos).toEqual({ location: { workbook, worksheet, unitId: workbook.getUnitId(), subUnitId: worksheet.getSheetId(), row: 0, col: 0 }, position: { startX: 0, endX: 1, startY: 0, endY: 1 }, dataTransfer: new MockDataTransfer() }); + }); + + // Trigger the Observable to emit a new value + const worksheet = workbook.getActiveSheet(); + const unitId = workbook.getUnitId(); + const subUnitId = worksheet.getSheetId(); + dragCurrentCell$.next({ location: { workbook, worksheet, unitId, subUnitId, row: 0, col: 0 }, position: { startX: 0, endX: 1, startY: 0, endY: 1 }, dataTransfer: new MockDataTransfer() }); + }); + + it('Test onCellDrop', () => { + sheetHooks.onCellDrop((cellPos) => { + expect(cellPos).toEqual({ location: { workbook, worksheet, unitId: workbook.getUnitId(), subUnitId: worksheet.getSheetId(), row: 0, col: 0 }, position: { startX: 0, endX: 1, startY: 0, endY: 1 }, dataTransfer: new MockDataTransfer() }); + }); + + // Trigger the Observable to emit a new value + const worksheet = workbook.getActiveSheet(); + const unitId = workbook.getUnitId(); + const subUnitId = worksheet.getSheetId(); + dragEndCell$.next({ location: { workbook, worksheet, unitId, subUnitId, row: 0, col: 0 }, position: { startX: 0, endX: 1, startY: 0, endY: 1 }, dataTransfer: new MockDataTransfer() }); }); }); diff --git a/packages/facade/src/apis/sheets/f-sheet-hooks.ts b/packages/facade/src/apis/sheets/f-sheet-hooks.ts index dcd00b7978e..ac982044261 100644 --- a/packages/facade/src/apis/sheets/f-sheet-hooks.ts +++ b/packages/facade/src/apis/sheets/f-sheet-hooks.ts @@ -25,7 +25,7 @@ export class FSheetHooks { constructor( @Inject(HoverManagerService) private readonly _hoverManagerService: HoverManagerService, @Inject(DragManagerService) private readonly _dragManagerService: DragManagerService - ) { + ) { // empty } From 9bffdac0b1675c64c978af26530c5bc2fe56ee13 Mon Sep 17 00:00:00 2001 From: Dushusir <1414556676@qq.com> Date: Mon, 20 May 2024 19:37:39 +0800 Subject: [PATCH 4/8] fix(sheets): subscribe current skeleton in drag render manager --- packages/engine-render/src/engine.ts | 2 - .../views/more-functions/MoreFunctions.tsx | 4 +- packages/sheets-ui/src/common/utils.ts | 1 - .../src/controllers/drag-render.controller.ts | 58 +++++++++++-------- 4 files changed, 36 insertions(+), 29 deletions(-) diff --git a/packages/engine-render/src/engine.ts b/packages/engine-render/src/engine.ts index bb692e95762..10e1c7cfda4 100644 --- a/packages/engine-render/src/engine.ts +++ b/packages/engine-render/src/engine.ts @@ -249,7 +249,6 @@ export class Engine extends ThinEngine { canvasEle.removeEventListener('drop', this._dropEvent); canvasEle.removeEventListener(this._getWheelEventName(), this._pointerWheelEvent); - this._activeRenderLoops = []; this.getCanvas().dispose(); // this._canvas = null; // 不应该这么做, 上面已经调用了 _canvas 的 dispose 方法 @@ -766,7 +765,6 @@ export class Engine extends ThinEngine { const deviceEvent = evt as IPointerEvent; deviceEvent.deviceType = deviceType; - deviceEvent.currentState = 6; this.onInputChangedObservable.notifyObservers(deviceEvent); diff --git a/packages/sheets-formula/src/views/more-functions/MoreFunctions.tsx b/packages/sheets-formula/src/views/more-functions/MoreFunctions.tsx index 6ea8175a2d5..75de5703aad 100644 --- a/packages/sheets-formula/src/views/more-functions/MoreFunctions.tsx +++ b/packages/sheets-formula/src/views/more-functions/MoreFunctions.tsx @@ -29,7 +29,7 @@ export function MoreFunctions() { const workbook = useActiveWorkbook(); const [selectFunction, setSelectFunction] = useState(true); const [inputParams, setInputParams] = useState(false); - const [params, setParams] = useState([]); + // const [params, setParams] = useState([]); // TODO@Dushusir: bind setParams to InputParams's onChange const [functionInfo, setFunctionInfo] = useState(null); const localeService = useDependency(LocaleService); @@ -52,7 +52,7 @@ export function MoreFunctions() { return (
{selectFunction && } - {inputParams && } + {inputParams && {}} />}
{/* TODO@Dushusir: open input params after range selector refactor */} {inputParams && ( diff --git a/packages/sheets-ui/src/common/utils.ts b/packages/sheets-ui/src/common/utils.ts index 49696636822..b61a8c095a3 100644 --- a/packages/sheets-ui/src/common/utils.ts +++ b/packages/sheets-ui/src/common/utils.ts @@ -204,7 +204,6 @@ export function transformPosition2Offset(x: number, y: number, scene: Scene, ske }; } - export function getHoverCellPosition(currentRender: IRender, workbook: Workbook, worksheet: Worksheet, skeletonParam: ISheetSkeletonManagerParam, offsetX: number, offsetY: number) { const { scene } = currentRender; diff --git a/packages/sheets-ui/src/controllers/drag-render.controller.ts b/packages/sheets-ui/src/controllers/drag-render.controller.ts index bdd65fe710b..234fba4d8c7 100644 --- a/packages/sheets-ui/src/controllers/drag-render.controller.ts +++ b/packages/sheets-ui/src/controllers/drag-render.controller.ts @@ -14,12 +14,14 @@ * limitations under the License. */ -import type { Workbook } from '@univerjs/core'; -import { Disposable, IUniverInstanceService, LifecycleStages, OnLifecycle, UniverInstanceType } from '@univerjs/core'; +import type { Nullable, Workbook } from '@univerjs/core'; +import { Disposable, DisposableCollection, LifecycleStages, OnLifecycle } from '@univerjs/core'; import type { IRenderContext, IRenderController } from '@univerjs/engine-render'; import { IRenderManagerService } from '@univerjs/engine-render'; import { Inject } from '@wendellhu/redi'; import { DragManagerService } from '../services/drag-manager.service'; +import type { ISheetSkeletonManagerParam } from '../services/sheet-skeleton-manager.service'; +import { SheetSkeletonManagerService } from '../services/sheet-skeleton-manager.service'; @OnLifecycle(LifecycleStages.Rendered, DragRenderController) export class DragRenderController extends Disposable implements IRenderController { @@ -27,7 +29,7 @@ export class DragRenderController extends Disposable implements IRenderControlle private readonly _context: IRenderContext, @IRenderManagerService private _renderManagerService: IRenderManagerService, @Inject(DragManagerService) private _dragManagerService: DragManagerService, - @IUniverInstanceService private _univerInstanceService: IUniverInstanceService + @Inject(SheetSkeletonManagerService) private _sheetSkeletonManagerService: SheetSkeletonManagerService ) { super(); @@ -35,31 +37,39 @@ export class DragRenderController extends Disposable implements IRenderControlle } private _initDragEvent() { - const scene = this._getCurrentScene(); + const disposeSet = new DisposableCollection(); + const handleSkeletonChange = (skeletonParam: Nullable) => { + disposeSet.dispose(); + if (!skeletonParam) { + return; + } - if (!scene) { - return; - } + const currentRender = this._renderManagerService.getRenderById(skeletonParam.unitId); - const dragOverObserver = scene.onDragOverObserver.add((evt) => { - this._dragManagerService.onDragOver(evt); - }); + if (!currentRender) { + return; + } - const dropObserver = scene.onDropObserver.add((evt) => { - this._dragManagerService.onDrop(evt); - }); + const { scene } = currentRender; + const dragOverObserver = scene.onDragOverObserver.add((evt) => { + this._dragManagerService.onDragOver(evt); + }); - this.disposeWithMe({ - dispose() { - scene.onDragOverObserver.remove(dragOverObserver); - scene.onDropObserver.remove(dropObserver); - }, - }); - } + const dropObserver = scene.onDropObserver.add((evt) => { + this._dragManagerService.onDrop(evt); + }); + + disposeSet.add({ + dispose() { + scene.onDragOverObserver.remove(dragOverObserver); + scene.onDropObserver.remove(dropObserver); + }, + }); + }; - private _getCurrentScene() { - return this._renderManagerService.getRenderById( - this._univerInstanceService.getCurrentUnitForType(UniverInstanceType.UNIVER_SHEET)!.getUnitId() - )?.scene; + handleSkeletonChange(this._sheetSkeletonManagerService.getCurrent()); + this.disposeWithMe(this._sheetSkeletonManagerService.currentSkeleton$.subscribe((skeletonParam) => { + handleSkeletonChange(skeletonParam); + })); } } From f89b2e0bef29ff0ac31184b643ef6af8bfc630cf Mon Sep 17 00:00:00 2001 From: Dushusir <1414556676@qq.com> Date: Mon, 20 May 2024 19:56:11 +0800 Subject: [PATCH 5/8] fix(sheet): remove unused button --- examples/public/sheets/index.html | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/examples/public/sheets/index.html b/examples/public/sheets/index.html index 744b31d9b6b..c228b9f521f 100644 --- a/examples/public/sheets/index.html +++ b/examples/public/sheets/index.html @@ -24,10 +24,7 @@ - -
+
From f50174bd7fa934415729a2c6e418708c210e9503 Mon Sep 17 00:00:00 2001 From: Dushusir <1414556676@qq.com> Date: Mon, 20 May 2024 19:57:49 +0800 Subject: [PATCH 6/8] fix(facade): add return interface --- packages/facade/src/apis/sheets/f-sheet-hooks.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/facade/src/apis/sheets/f-sheet-hooks.ts b/packages/facade/src/apis/sheets/f-sheet-hooks.ts index ac982044261..4cf26922bad 100644 --- a/packages/facade/src/apis/sheets/f-sheet-hooks.ts +++ b/packages/facade/src/apis/sheets/f-sheet-hooks.ts @@ -61,7 +61,7 @@ export class FSheetHooks { * @param callback Callback function that will be called when the event is fired * @returns A disposable object that can be used to unsubscribe from the event */ - onCellDrop(callback: (cellPos: Nullable) => void) { + onCellDrop(callback: (cellPos: Nullable) => void): IDisposable { return toDisposable(this._dragManagerService.endCell$.subscribe(callback)); } } From a0258cec9e1897ffbb87883d201e0386e5dcc146 Mon Sep 17 00:00:00 2001 From: Dushusir <1414556676@qq.com> Date: Mon, 20 May 2024 21:04:49 +0800 Subject: [PATCH 7/8] docs(facade): update description --- packages/facade/src/apis/sheets/f-sheet-hooks.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/facade/src/apis/sheets/f-sheet-hooks.ts b/packages/facade/src/apis/sheets/f-sheet-hooks.ts index 4cf26922bad..4fbff5ba08d 100644 --- a/packages/facade/src/apis/sheets/f-sheet-hooks.ts +++ b/packages/facade/src/apis/sheets/f-sheet-hooks.ts @@ -30,7 +30,7 @@ export class FSheetHooks { } /** - * The onCellPointerMove event is fired when a pointing device is moved into a cell's hit test boundaries. + * The onCellPointerMove event is fired when a pointer changes coordinates. * @param callback Callback function that will be called when the event is fired * @returns A disposable object that can be used to unsubscribe from the event */ @@ -39,7 +39,7 @@ export class FSheetHooks { } /** - * The onCellPointerOver event is fired when a pointing device is moved into a cell's hit test boundaries. + * The onCellPointerOver event is fired when a pointer is moved into a cell's hit test boundaries. * @param callback Callback function that will be called when the event is fired * @returns A disposable object that can be used to unsubscribe from the event */ @@ -57,7 +57,7 @@ export class FSheetHooks { } /** - * The onCellDrop event is fired when an element or text selection is being dropped on the cell + * The onCellDrop event is fired when an element or text selection is being dropped on the cell. * @param callback Callback function that will be called when the event is fired * @returns A disposable object that can be used to unsubscribe from the event */ From e64a14627ad95b586430c024be4d3f894256a231 Mon Sep 17 00:00:00 2001 From: Dushusir <1414556676@qq.com> Date: Tue, 21 May 2024 15:54:49 +0800 Subject: [PATCH 8/8] fix(sheet): export SheetMenuPosition --- packages/sheets-ui/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/sheets-ui/src/index.ts b/packages/sheets-ui/src/index.ts index 3e83561ad96..3f4f223f90e 100644 --- a/packages/sheets-ui/src/index.ts +++ b/packages/sheets-ui/src/index.ts @@ -94,3 +94,4 @@ export { virtualizeDiscreteRanges, rangeToDiscreteRange } from './controllers/ut export { type IHoverCellPosition } from './services/hover-manager.service'; export { AutoHeightController } from './controllers/auto-height.controller'; export { type IDragCellPosition } from './services/drag-manager.service'; +export { SheetMenuPosition } from './controllers/menu/menu';