Skip to content

Commit

Permalink
fix: add dom popup canvas but slow
Browse files Browse the repository at this point in the history
  • Loading branch information
lumixraku committed Jan 11, 2025
1 parent 719acd1 commit 5501058
Show file tree
Hide file tree
Showing 10 changed files with 504 additions and 93 deletions.
17 changes: 17 additions & 0 deletions packages/engine-render/src/floating/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ import { floor, max, min } from '@floating-ui/utils';
import { getDocumentElement } from '@floating-ui/utils/dom';
import { Observable } from 'rxjs';

/**
* Tracks position and size changes of an element by monitoring:
* - Ancestor scrolling and resizing
* - Layout shifts
* - Element's bounding rectangle changes
*
* @param containerElement
* @returns Observable<void>
*/
export function observeClientRect(containerElement: HTMLElement): Observable<void> {
return new Observable<void>((observer) => {
const disposable = autoClientRect(containerElement, () => observer.next());
Expand Down Expand Up @@ -116,6 +125,14 @@ function getBoundingClientRect(reference: Element) {
return reference.getBoundingClientRect();
}

/**
* Tracks position and size changes of an element by monitoring:
* - Ancestor scrolling and resizing
* - Layout shifts
* - Element's bounding rectangle changes
*
* Returns a cleanup function to remove all listeners.
*/
function autoClientRect(
reference: Element,
update: () => void
Expand Down
52 changes: 51 additions & 1 deletion packages/sheets-ui/src/facade/f-range.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ interface IFRangeSheetsUIMixin {
```
let sheet = univerAPI.getActiveWorkbook().getActiveSheet();
let range = sheet.getRange(2, 2, 3, 3);
univerAPI.getActiveWorkbook().setActiveRange(range);
let disposable = range.attachPopup({
componentKey: 'univer.sheet.cell-alert',
extraProps: { alert: { type: 0, title: 'This is an Info', message: 'This is an info message' } },
Expand All @@ -102,6 +103,22 @@ interface IFRangeSheetsUIMixin {
* ```
*/
attachAlertPopup(alert: Omit<ICellAlert, 'location'>): IDisposable;
/**
* Attach a DOM popup to the start cell of current range.
* @param alert The alert to attach
* @returns The disposable object to detach the alert.
* @example
* ```ts
let sheet = univerAPI.getActiveWorkbook().getActiveSheet();
let range = sheet.getRange(2, 2, 3, 3);
univerAPI.getActiveWorkbook().setActiveRange(range);
let disposable = range.attachDOMPopup({
componentKey: 'univer.sheet.single-dom-popup',
extraProps: { alert: { type: 0, title: 'This is an Info', message: 'This is an info message' } },
});
* ```
*/
attachDOMPopup(popup: IFCanvasPopup): Nullable<IDisposable>;

/**
* Highlight the range with the specified style and primary cell.
Expand Down Expand Up @@ -145,7 +162,6 @@ class FRangeSheetsUIMixin extends FRange implements IFRangeSheetsUIMixin {
}

override attachPopup(popup: IFCanvasPopup): Nullable<IDisposable> {
popup.onClick;
popup.direction = popup.direction ?? 'horizontal';
popup.extraProps = popup.extraProps ?? {};
popup.offset = popup.offset ?? [0, 0];
Expand Down Expand Up @@ -190,6 +206,40 @@ class FRangeSheetsUIMixin extends FRange implements IFRangeSheetsUIMixin {
};
}

/**
* attachDOMPopup
* @param popup
* @returns ddd
let sheet = univerAPI.getActiveWorkbook().getActiveSheet();
let range = sheet.getRange(2, 2, 3, 3);
univerAPI.getActiveWorkbook().setActiveRange(range);
let disposable = range.attachDOMPopup({
componentKey: 'univer.sheet.single-dom-popup',
extraProps: { alert: { type: 0, title: 'This is an Info', message: 'This is an info message' } },
});
*/
override attachDOMPopup(popup: IFCanvasPopup): Nullable<IDisposable> {
popup.direction = popup.direction ?? 'horizontal';
popup.extraProps = popup.extraProps ?? {};
popup.offset = popup.offset ?? [0, 0];

const { key, disposableCollection } = transformComponentKey(popup, this._injector.get(ComponentManager));
const sheetsPopupService = this._injector.get(SheetCanvasPopManagerService);
const disposePopup = sheetsPopupService.attachDOMToRange(
this._range,
{ ...popup, componentKey: key },
this.getUnitId(),
this._worksheet.getSheetId()
);
if (disposePopup) {
disposableCollection.add(disposePopup);
return disposableCollection;
}

disposableCollection.dispose();
return null;
}

override highlight(style?: Nullable<Partial<ISelectionStyle>>, primary?: Nullable<ISelectionCell>): IDisposable {
const markSelectionService = this._injector.get(IMarkSelectionService);
const id = markSelectionService.addShape({ range: this._range, style, primary });
Expand Down
178 changes: 175 additions & 3 deletions packages/sheets-ui/src/services/canvas-pop-manager.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { Disposable, DisposableCollection, ICommandService, Inject, IUniverInsta
import { IRenderManagerService } from '@univerjs/engine-render';
import { COMMAND_LISTENER_SKELETON_CHANGE, RefRangeService, SetFrozenMutation, SetWorksheetRowAutoHeightMutation } from '@univerjs/sheets';
import { ICanvasPopupService } from '@univerjs/ui';
import { BehaviorSubject } from 'rxjs';
import { BehaviorSubject, combineLatestWith, map } from 'rxjs';
import { SetScrollOperation } from '../commands/operations/scroll.operation';
import { SetZoomRatioOperation } from '../commands/operations/set-zoom-ratio.operation';
import { getViewportByCell, transformBound2OffsetBound } from '../common/utils';
Expand Down Expand Up @@ -162,6 +162,7 @@ export class SheetCanvasPopManagerService extends Disposable {
}

// #region attach to object
// Unlike _createCellPositionObserver, this bind to a certain position.
private _createPositionObserver(
bound: IBoundRectNoAngle,
currentRender: IRender,
Expand Down Expand Up @@ -368,7 +369,8 @@ export class SheetCanvasPopManagerService extends Disposable {
// #region attach to cell

/**
*
* Bind popup to the right part of cell at(row, col).
* This popup would move with the cell.
* @param row
* @param col
* @param popup
Expand Down Expand Up @@ -452,6 +454,95 @@ export class SheetCanvasPopManagerService extends Disposable {
};
}

attachDOMToRange(range: IRange, popup: ICanvasPopup, _unitId?: string, _subUnitId?: string, viewport?: Viewport, showOnSelectionMoving = false): Nullable<INeedCheckDisposable> {
const workbook = this._univerInstanceService.getCurrentUnitForType<Workbook>(UniverInstanceType.UNIVER_SHEET)!;
const worksheet = workbook.getActiveSheet();
if (!worksheet) {
return null;
}

const unitId = workbook.getUnitId();
const subUnitId = worksheet.getSheetId();
if ((_unitId && unitId !== _unitId) || (_subUnitId && subUnitId !== _subUnitId)) {
return null;
}
const currentRender = this._renderManagerService.getRenderById(unitId);
const skeleton = currentRender?.with(SheetSkeletonManagerService).getOrCreateSkeleton({
sheetId: subUnitId,
});
const sheetSelectionRenderService = currentRender?.with(ISheetSelectionRenderService);

if (!currentRender || !skeleton || !sheetSelectionRenderService) {
return null;
}

if (sheetSelectionRenderService.selectionMoving && !showOnSelectionMoving) {
return;
}

const activeViewport = viewport ?? getViewportByCell(range.startRow, range.startColumn, currentRender.scene, worksheet);
if (!activeViewport) {
return null;
}

const { position, position$, disposable: positionObserverDisposable, updateRowCol,
topLeftPos$,
rightBottomPos$,
} = this._createRangePositionObserver(range, currentRender, skeleton, activeViewport);

const { rects$, disposable: rectsObserverDisposable } = this._createHiddenRectObserver({
row: range.startRow,
column: range.startColumn,
worksheet,
skeleton,
currentRender,
});
const id = this._globalPopupManagerService.addPopup({
...popup,
unitId,
subUnitId,
anchorRect: position,
anchorRect$: position$,
canvasElement: currentRender.engine.getCanvasElement(),
hiddenRects$: rects$,
});
const disposableCollection = new DisposableCollection();
disposableCollection.add(positionObserverDisposable);
disposableCollection.add(toDisposable(() => {
this._globalPopupManagerService.removePopup(id);
// position$.complete();
topLeftPos$.complete();
rightBottomPos$.complete();
}));
disposableCollection.add(rectsObserverDisposable);

// If the range changes, the popup should change with it. And if the range vanished, the popup should be removed.
const watchedRange = { ...range };
disposableCollection.add(this._refRangeService.watchRange(unitId, subUnitId, watchedRange, (_, after) => {
if (!after) {
disposableCollection.dispose();
} else {
updateRowCol(after.startRow, after.startColumn);
}
}));

return {
dispose() {
disposableCollection.dispose();
},
canDispose: () => this._globalPopupManagerService.activePopupId !== id,
};
}

/**
*
* @param initialRow
* @param initialCol
* @param currentRender
* @param skeleton
* @param activeViewport
* @returns
*/
private _createCellPositionObserver(
initialRow: number,
initialCol: number,
Expand Down Expand Up @@ -534,8 +625,89 @@ export class SheetCanvasPopManagerService extends Disposable {
bottom: ((cellInfo.endY - scrollXY.y) * scaleAdjust * scaleY) + top,
};
}

// #endregion

/**
* Unlike _createCellPositionObserver, this accept a range not a single cell.
* @param initialRow
* @param initialCol
* @param currentRender
* @param skeleton
* @param activeViewport
* @returns
*/
private _createRangePositionObserver(
range: IRange,
currentRender: IRender,
skeleton: SpreadsheetSkeleton,
activeViewport: Viewport
) {
let { startRow, startColumn } = range;
const topLeftCoord = this._calcCellPositionByCell(startRow, startColumn, currentRender, skeleton, activeViewport);
const topLeftPos$ = new BehaviorSubject(topLeftCoord);
const rightBottomCoord = this._calcCellPositionByCell(range.endRow, range.endColumn, currentRender, skeleton, activeViewport);
const rightBottomPos$ = new BehaviorSubject(rightBottomCoord);

const updatePosition = () => {
const topLeftCoord = this._calcCellPositionByCell(startRow, startColumn, currentRender, skeleton, activeViewport);
const rightBottomCoord = this._calcCellPositionByCell(range.endRow, range.endColumn, currentRender, skeleton, activeViewport);

topLeftPos$.next(topLeftCoord);
rightBottomPos$.next(rightBottomCoord);
};

const disposable = new DisposableCollection();
disposable.add(currentRender.engine.clientRect$.subscribe(() => updatePosition()));

disposable.add(this._commandService.onCommandExecuted((commandInfo) => {
if (commandInfo.id === SetWorksheetRowAutoHeightMutation.id) {
const params = commandInfo.params as ISetWorksheetRowAutoHeightMutationParams;
if (params.rowsAutoHeightInfo.findIndex((item) => item.row === startRow) > -1) {
updatePosition();
return;
}
}

if (
COMMAND_LISTENER_SKELETON_CHANGE.indexOf(commandInfo.id) > -1 ||
commandInfo.id === SetScrollOperation.id ||
commandInfo.id === SetZoomRatioOperation.id
) {
updatePosition();
}
}));

const updateRowCol = (newRow: number, newCol: number) => {
startRow = newRow;
startColumn = newCol;

updatePosition();
};
// const position$ = combineLatest(topLeftPos$, rightBottomPos$);
const position$ = topLeftPos$.pipe(
combineLatestWith(rightBottomPos$),
map(([topLeft, rightBottom]) => ({
top: topLeft.top,
left: topLeft.left,
right: rightBottom.right,
bottom: rightBottom.bottom,
}))
);
const position: IBoundRectNoAngle = {
top: topLeftCoord.top,
left: topLeftCoord.left,
right: rightBottomCoord.right,
bottom: rightBottomCoord.bottom,
};
return {
position$,
position,
updateRowCol,
topLeftPos$,
rightBottomPos$,
disposable,
};
}
}

function pxToNum(width: string): number {
Expand Down
3 changes: 2 additions & 1 deletion packages/ui/src/controllers/ui/ui-desktop.controller.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { ILayoutService } from '../../services/layout/layout.service';
import { IMenuManagerService } from '../../services/menu/menu-manager.service';
import { BuiltInUIPart, IUIPartsService } from '../../services/parts/parts.service';
import { FloatDom } from '../../views/components/dom/FloatDom';
import { CanvasPopup } from '../../views/components/popup/CanvasPopup';
import { CanvasDOMPopup, CanvasPopup } from '../../views/components/popup/CanvasPopup';
import { DesktopWorkbench } from '../../views/workbench/Workbench';
import { menuSchema } from '../menus/menu.schema';

Expand Down Expand Up @@ -102,6 +102,7 @@ export class DesktopUIController extends Disposable {

private _initBuiltinComponents(): void {
this.disposeWithMe(this._uiPartsService.registerComponent(BuiltInUIPart.FLOATING, () => connectInjector(CanvasPopup, this._injector)));
this.disposeWithMe(this._uiPartsService.registerComponent(BuiltInUIPart.FLOATING, () => connectInjector(CanvasDOMPopup, this._injector)));
this.disposeWithMe(this._uiPartsService.registerComponent(BuiltInUIPart.CONTENT, () => connectInjector(FloatDom, this._injector)));
}
}
Expand Down
12 changes: 6 additions & 6 deletions packages/ui/src/controllers/ui/ui-mobile.controller.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,21 @@
* limitations under the License.
*/

import type { IDisposable } from '@univerjs/core';
import type { IUniverUIConfig } from '../config.schema';
import type { IUIController, IWorkbenchOptions } from './ui.controller';
import { connectInjector, Disposable, Inject, Injector, IUniverInstanceService, LifecycleService, LifecycleStages, Optional, toDisposable, UniverInstanceType } from '@univerjs/core';
import { IRenderManagerService } from '@univerjs/engine-render';
import type { IDisposable } from '@univerjs/core';

import { render as createRoot, unmount } from 'rc-util/lib/React/render';
import React from 'react';

import { ILayoutService } from '../../services/layout/layout.service';
import { IMenuManagerService } from '../../services/menu/menu-manager.service';
import { BuiltInUIPart, IUIPartsService } from '../../services/parts/parts.service';
import { CanvasPopup } from '../../views/components/popup/CanvasPopup';
import { FloatDom } from '../../views/components/dom/FloatDom';
import { CanvasPopup } from '../../views/components/popup/CanvasPopup';
import { MobileApp } from '../../views/MobileApp';
import type { IUniverUIConfig } from '../config.schema';
import { IMenuManagerService } from '../../services/menu/menu-manager.service';
import { menuSchema } from '../menus/menu.schema';
import type { IUIController, IWorkbenchOptions } from './ui.controller';

const STEADY_TIMEOUT = 3000;

Expand Down
Loading

0 comments on commit 5501058

Please sign in to comment.