Some checks failed
📦 Build / build (push) Has been cancelled
🧪 CI Test / test shard (1/4) (push) Has been cancelled
🧪 CI Test / test shard (2/4) (push) Has been cancelled
🧪 CI Test / test shard (3/4) (push) Has been cancelled
🧪 CI Test / test shard (4/4) (push) Has been cancelled
🧪 CI Test / test (push) Has been cancelled
🧪 CI Test / eslint (push) Has been cancelled
🧪 CI Test / typecheck (push) Has been cancelled
🔫 Sync to Pro / dispatch-sync (push) Has been cancelled
🔮 Sync Mirror / sync (push) Has been cancelled
701 lines
28 KiB
TypeScript
701 lines
28 KiB
TypeScript
/**
|
|
* Copyright 2023-present DreamNum Co., Ltd.
|
|
*
|
|
* 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 { DocumentDataModel, ICommandInfo, IDocDrawingPosition, IDrawingParam, IImageIoServiceParam, ITextRangeParam, Nullable } from '@univerjs/core';
|
|
import type { IRichTextEditingMutationParams } from '@univerjs/docs';
|
|
import type { IDocDrawing, IDrawingDocTransform, IInsertDocDrawingCommandParams, ISetDocDrawingArrangeCommandParams, IUpdateDrawingDocTransformCommandParams } from '@univerjs/docs-drawing';
|
|
import type { BaseObject, Documents, Image, IRenderContext, IRenderModule, ITransformerConfig } from '@univerjs/engine-render';
|
|
import type { LocaleKey } from '../../locale/types';
|
|
import {
|
|
BooleanNumber,
|
|
Disposable,
|
|
DrawingTypeEnum,
|
|
FOCUSING_COMMON_DRAWINGS,
|
|
ICommandService,
|
|
IContextService,
|
|
IImageIoService,
|
|
ImageUploadStatusType,
|
|
Inject,
|
|
IPermissionService,
|
|
LocaleService,
|
|
PositionedObjectLayoutType,
|
|
WrapTextType,
|
|
} from '@univerjs/core';
|
|
import { MessageType } from '@univerjs/design';
|
|
import {
|
|
buildDocTransform,
|
|
canEditDocumentTargets,
|
|
docDrawingPositionToTransform,
|
|
DocSelectionManagerService,
|
|
DocSkeletonManagerService,
|
|
getDocumentDrawingSegmentId,
|
|
getDocumentEditTargetObjectIds,
|
|
getDocumentEntityParentPermissionObjectIds,
|
|
getDocumentEntityPermissionObjectId,
|
|
RichTextEditingMutation,
|
|
} from '@univerjs/docs';
|
|
import { collectDocDrawings, findDocDrawing, IDocDrawingService, InsertDocDrawingCommand, SetDocDrawingArrangeCommand, UpdateDrawingDocTransformCommand } from '@univerjs/docs-drawing';
|
|
import { DocSelectionRenderService } from '@univerjs/docs-ui';
|
|
import {
|
|
DRAWING_IMAGE_ALLOW_IMAGE_LIST,
|
|
DRAWING_IMAGE_COUNT_LIMIT,
|
|
DRAWING_IMAGE_HEIGHT_LIMIT,
|
|
DRAWING_IMAGE_WIDTH_LIMIT,
|
|
getDrawingImageAllowSize,
|
|
getDrawingShapeKeyByDrawingSearch,
|
|
getImageSize,
|
|
IDrawingManagerService,
|
|
} from '@univerjs/drawing';
|
|
import { DocumentEditArea, IRenderManagerService } from '@univerjs/engine-render';
|
|
import { ILocalFileService, IMessageService } from '@univerjs/ui';
|
|
import { debounceTime } from 'rxjs';
|
|
import { GroupDocDrawingCommand } from '../../commands/commands/group-doc-drawing.command';
|
|
import { UngroupDocDrawingCommand } from '../../commands/commands/ungroup-doc-drawing.command';
|
|
import { DocRefreshDrawingsService } from '../../services/doc-refresh-drawings.service';
|
|
import { getDocImageCropUpdates } from './doc-drawing-crop';
|
|
import { getDocMutationAffectedDrawingIds } from './doc-drawing-mutation';
|
|
|
|
interface IImageInsertPosition {
|
|
left: number;
|
|
top: number;
|
|
}
|
|
|
|
export class DocDrawingUpdateRenderController extends Disposable implements IRenderModule {
|
|
private readonly _editableTransformerConfigs = new WeakMap<BaseObject, ITransformerConfig>();
|
|
private readonly _editableEventedStates = new WeakMap<BaseObject, boolean>();
|
|
private _editStatusUpdateScheduled = false;
|
|
private _pendingEditStatusDrawingIds: Set<string> | null | undefined;
|
|
private _lastEditStatusContextKey: string | null = null;
|
|
|
|
constructor(
|
|
private readonly _context: IRenderContext<DocumentDataModel>,
|
|
@ICommandService private readonly _commandService: ICommandService,
|
|
@Inject(DocSelectionManagerService) private readonly _docSelectionManagerService: DocSelectionManagerService,
|
|
@IRenderManagerService private readonly _renderManagerSrv: IRenderManagerService,
|
|
@IImageIoService private readonly _imageIoService: IImageIoService,
|
|
@IDocDrawingService private readonly _docDrawingService: IDocDrawingService,
|
|
@IDrawingManagerService private readonly _drawingManagerService: IDrawingManagerService,
|
|
@IPermissionService private readonly _permissionService: IPermissionService,
|
|
@IContextService private readonly _contextService: IContextService,
|
|
@IMessageService private readonly _messageService: IMessageService,
|
|
@Inject(LocaleService) private readonly _localeService: LocaleService,
|
|
@Inject(DocSelectionRenderService) private readonly _docSelectionRenderService: DocSelectionRenderService,
|
|
@Inject(DocRefreshDrawingsService) private readonly _docRefreshDrawingsService: DocRefreshDrawingsService,
|
|
@ILocalFileService private readonly _fileOpenerService: ILocalFileService
|
|
) {
|
|
super();
|
|
|
|
this._updateOrderListener();
|
|
this._updateImageCropListener();
|
|
this._groupDrawingListener();
|
|
this._focusDrawingListener();
|
|
this._transformDrawingListener();
|
|
this._editAreaChangeListener();
|
|
const scheduleEditStatusUpdate = (drawings: Array<{ unitId: string; drawingId: string }>) => {
|
|
const relevantDrawings = drawings.filter((drawing) => drawing.unitId === this._context?.unitId);
|
|
if (relevantDrawings.length > 0) {
|
|
const drawingIds = relevantDrawings
|
|
.map((drawing) => drawing.drawingId)
|
|
.filter((drawingId): drawingId is string => typeof drawingId === 'string' && drawingId.length > 0);
|
|
this._scheduleDrawingsEditStatusUpdate(
|
|
drawingIds.length === relevantDrawings.length ? new Set(drawingIds) : undefined
|
|
);
|
|
}
|
|
};
|
|
this.disposeWithMe(this._drawingManagerService.add$.subscribe(scheduleEditStatusUpdate));
|
|
this.disposeWithMe(this._drawingManagerService.update$.subscribe(scheduleEditStatusUpdate));
|
|
this.disposeWithMe(this._permissionService.permissionPointUpdate$.subscribe(() => {
|
|
this._updateDrawingsEditStatus(undefined, true);
|
|
}));
|
|
}
|
|
|
|
override dispose(): void {
|
|
super.dispose();
|
|
// @ts-ignore
|
|
delete this._context;
|
|
}
|
|
|
|
async insertDocImage(): Promise<boolean> {
|
|
const insertPosition = this._getCurrentImageInsertPosition();
|
|
const textRange = this._getCurrentImageInsertTextRange();
|
|
if (!this._canInsertDocImage(textRange)) {
|
|
return false;
|
|
}
|
|
const files = await this._fileOpenerService.openFile({
|
|
multiple: true,
|
|
accept: DRAWING_IMAGE_ALLOW_IMAGE_LIST.map((image) => `.${image.replace('image/', '')}`).join(','),
|
|
});
|
|
|
|
if (this._disposed || !this._canInsertDocImage(textRange)) {
|
|
return false;
|
|
}
|
|
|
|
const fileLength = files.length;
|
|
if (fileLength > DRAWING_IMAGE_COUNT_LIMIT) {
|
|
this._messageService.show({
|
|
type: MessageType.Error,
|
|
content: this._localeService.t<LocaleKey>('docs-drawing-ui.update-status.exceedMaxCount', String(DRAWING_IMAGE_COUNT_LIMIT)),
|
|
});
|
|
return false;
|
|
} else if (fileLength === 0) {
|
|
return false;
|
|
}
|
|
|
|
return await this._insertFloatImages(files, insertPosition, textRange);
|
|
}
|
|
|
|
// eslint-disable-next-line max-lines-per-function
|
|
private async _insertFloatImages(
|
|
files: File[],
|
|
insertPosition: Nullable<IImageInsertPosition>,
|
|
textRange: Nullable<ITextRangeParam>
|
|
) {
|
|
let imageParams: Nullable<IImageIoServiceParam>[] = [];
|
|
|
|
try {
|
|
imageParams = await Promise.all(files.map((file) => this._imageIoService.saveImage(file)));
|
|
} catch (error) {
|
|
const type = (error as Error).message;
|
|
let content = '';
|
|
|
|
switch (type) {
|
|
case ImageUploadStatusType.ERROR_EXCEED_SIZE:
|
|
content = this._localeService.t<LocaleKey>('docs-drawing-ui.update-status.exceedMaxSize', String(getDrawingImageAllowSize() / (1024 * 1024)));
|
|
break;
|
|
case ImageUploadStatusType.ERROR_IMAGE_TYPE:
|
|
content = this._localeService.t<LocaleKey>('docs-drawing-ui.update-status.invalidImageType');
|
|
break;
|
|
case ImageUploadStatusType.ERROR_IMAGE:
|
|
content = this._localeService.t<LocaleKey>('docs-drawing-ui.update-status.invalidImage');
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
this._messageService.show({
|
|
type: MessageType.Error,
|
|
content,
|
|
});
|
|
}
|
|
|
|
if (this._disposed || imageParams.length === 0 || !this._canInsertDocImage(textRange)) {
|
|
return false;
|
|
}
|
|
|
|
const { unitId } = this._context;
|
|
const docDrawingParams: IDocDrawing[] = [];
|
|
|
|
for (const imageParam of imageParams) {
|
|
if (imageParam == null) {
|
|
continue;
|
|
}
|
|
const { imageId, imageSourceType, source, base64Cache } = imageParam;
|
|
const { width, height, image } = await getImageSize(base64Cache || '');
|
|
|
|
if (this._disposed || !this._canInsertDocImage(textRange)) {
|
|
return false;
|
|
}
|
|
|
|
this._imageIoService.addImageSourceCache(imageId, imageSourceType, image);
|
|
|
|
let scale = 1;
|
|
if (width > DRAWING_IMAGE_WIDTH_LIMIT || height > DRAWING_IMAGE_HEIGHT_LIMIT) {
|
|
const scaleWidth = DRAWING_IMAGE_WIDTH_LIMIT / width;
|
|
const scaleHeight = DRAWING_IMAGE_HEIGHT_LIMIT / height;
|
|
scale = Math.min(scaleWidth, scaleHeight);
|
|
}
|
|
|
|
const imagePosition = insertPosition ?? this._getCurrentImageInsertPosition();
|
|
const docTransform = this._getImagePosition(width * scale, height * scale, imagePosition);
|
|
|
|
if (docTransform == null) {
|
|
return false;
|
|
}
|
|
|
|
const transform = docDrawingPositionToTransform(docTransform);
|
|
if (transform != null && imagePosition != null) {
|
|
transform.top = imagePosition.top;
|
|
}
|
|
|
|
const docDrawingParam: IDocDrawing = {
|
|
unitId,
|
|
subUnitId: unitId,
|
|
drawingId: imageId,
|
|
drawingType: DrawingTypeEnum.DRAWING_IMAGE,
|
|
imageSourceType,
|
|
source,
|
|
transform,
|
|
docTransform,
|
|
behindDoc: BooleanNumber.FALSE,
|
|
title: '',
|
|
description: '',
|
|
layoutType: PositionedObjectLayoutType.INLINE, // Insert inline drawing by default.
|
|
wrapText: WrapTextType.BOTH_SIDES,
|
|
distB: 0,
|
|
distL: 0,
|
|
distR: 0,
|
|
distT: 0,
|
|
};
|
|
|
|
const isInHeaderFooter = this._isInsertInHeaderFooter();
|
|
|
|
if (isInHeaderFooter) {
|
|
docDrawingParam.isMultiTransform = BooleanNumber.TRUE;
|
|
docDrawingParam.transforms = docDrawingParam.transform ? [docDrawingParam.transform] : null;
|
|
}
|
|
|
|
docDrawingParams.push(docDrawingParam);
|
|
}
|
|
|
|
return await this._commandService.executeCommand<IInsertDocDrawingCommandParams>(InsertDocDrawingCommand.id, {
|
|
unitId,
|
|
drawings: docDrawingParams,
|
|
textRange: textRange ?? undefined,
|
|
});
|
|
}
|
|
|
|
private _isInsertInHeaderFooter() {
|
|
const { unitId } = this._context;
|
|
const viewModel = this._renderManagerSrv.getRenderUnitById(unitId)
|
|
?.with(DocSkeletonManagerService)
|
|
.getViewModel();
|
|
|
|
const editArea = viewModel?.getEditArea();
|
|
|
|
return editArea === DocumentEditArea.HEADER || editArea === DocumentEditArea.FOOTER;
|
|
}
|
|
|
|
private _getImagePosition(
|
|
imageWidth: number,
|
|
imageHeight: number,
|
|
insertPosition?: Nullable<IImageInsertPosition>
|
|
): Nullable<IDocDrawingPosition> {
|
|
// TODO: NO need to get the cursor position, because the insert image is inline.
|
|
const position = insertPosition ?? this._getCurrentImageInsertPosition() ?? {
|
|
left: 0,
|
|
top: 0,
|
|
};
|
|
|
|
return buildDocTransform(imageWidth, imageHeight, {
|
|
left: position.left,
|
|
});
|
|
}
|
|
|
|
private _getCurrentImageInsertPosition(): Nullable<IImageInsertPosition> {
|
|
const position = this._docSelectionRenderService.getActiveTextRange()?.getAbsolutePosition();
|
|
|
|
if (position == null) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
left: position.left,
|
|
top: position.top,
|
|
};
|
|
}
|
|
|
|
private _getCurrentImageInsertTextRange(): Nullable<ITextRangeParam> {
|
|
return this._docSelectionRenderService.getAllTextRanges().find((range) => range.isActive)
|
|
?? this._docSelectionManagerService.getActiveTextRange();
|
|
}
|
|
|
|
private _updateOrderListener() {
|
|
this.disposeWithMe(
|
|
this._drawingManagerService.featurePluginOrderUpdate$.subscribe((params) => {
|
|
const { unitId, subUnitId, drawingIds, arrangeType } = params;
|
|
|
|
this._commandService.executeCommand<ISetDocDrawingArrangeCommandParams>(SetDocDrawingArrangeCommand.id, {
|
|
unitId,
|
|
subUnitId,
|
|
drawingIds,
|
|
arrangeType,
|
|
});
|
|
})
|
|
);
|
|
}
|
|
|
|
private _updateImageCropListener() {
|
|
this.disposeWithMe(
|
|
this._drawingManagerService.featurePluginUpdate$.subscribe((params) => {
|
|
const drawings = params.flatMap((param) => this._getImageCropUpdates(param));
|
|
|
|
if (drawings.length > 0) {
|
|
const { unitId } = this._context;
|
|
this._commandService.executeCommand<IUpdateDrawingDocTransformCommandParams>(UpdateDrawingDocTransformCommand.id, {
|
|
unitId,
|
|
subUnitId: unitId,
|
|
drawings,
|
|
});
|
|
}
|
|
})
|
|
);
|
|
}
|
|
|
|
private _getImageCropUpdates(param: IDrawingParam): IDrawingDocTransform[] {
|
|
const { unitId, subUnitId, drawingId } = param;
|
|
if ([unitId, subUnitId].some((id) => id !== this._context.unitId)) {
|
|
return [];
|
|
}
|
|
return getDocImageCropUpdates(
|
|
param,
|
|
this._getDocDrawing(drawingId),
|
|
this._drawingManagerService.getDrawingByParam({ unitId, subUnitId, drawingId }) ?? undefined
|
|
);
|
|
}
|
|
|
|
private _getDocDrawing(drawingId: string): IDocDrawing | undefined {
|
|
return findDocDrawing(this._context.unit.getSnapshot(), drawingId)?.drawing as IDocDrawing | undefined;
|
|
}
|
|
|
|
private _groupDrawingListener() {
|
|
this.disposeWithMe(
|
|
this._drawingManagerService.featurePluginGroupUpdate$.subscribe((params) => {
|
|
this._commandService.executeCommand(GroupDocDrawingCommand.id, params);
|
|
})
|
|
);
|
|
|
|
this.disposeWithMe(
|
|
this._drawingManagerService.featurePluginUngroupUpdate$.subscribe((params) => {
|
|
this._commandService.executeCommand(UngroupDocDrawingCommand.id, params);
|
|
})
|
|
);
|
|
}
|
|
|
|
private _getCurrentSceneAndTransformer() {
|
|
const { scene, mainComponent } = this._context;
|
|
|
|
if (scene == null || mainComponent == null) {
|
|
return;
|
|
}
|
|
|
|
const transformer = scene.getTransformerByCreate();
|
|
|
|
const { docsLeft, docsTop } = (mainComponent as Documents).getOffsetConfig();
|
|
|
|
return { scene, transformer, docsLeft, docsTop };
|
|
}
|
|
|
|
protected _getTransformerInteractionOptions(): ITransformerConfig {
|
|
return { moveOnlyWhenSelected: false };
|
|
}
|
|
|
|
protected _isDocumentInteractionFocusing(unitId: string): boolean {
|
|
return this._docSelectionRenderService.isFocusing
|
|
|| this._drawingManagerService.getFocusDrawings().some((drawing) => drawing.unitId === unitId);
|
|
}
|
|
|
|
private _transformDrawingListener() {
|
|
const res = this._getCurrentSceneAndTransformer();
|
|
if (res && res.transformer) {
|
|
res.transformer.resetProps(this._getTransformerInteractionOptions());
|
|
this.disposeWithMe(res.transformer.changeEnd$.pipe(debounceTime(30)).subscribe(() => {
|
|
this._docSelectionManagerService.refreshSelection();
|
|
}));
|
|
} else {
|
|
throw new Error('transformer is not init');
|
|
}
|
|
}
|
|
|
|
private _focusDrawingListener() {
|
|
this.disposeWithMe(
|
|
this._drawingManagerService.focus$.subscribe((params) => {
|
|
const { transformer, docsLeft, docsTop } = this._getCurrentSceneAndTransformer() ?? {};
|
|
if (params == null || params.length === 0) {
|
|
this._contextService.setContextValue(FOCUSING_COMMON_DRAWINGS, false);
|
|
this._docDrawingService.focusDrawing([]);
|
|
|
|
if (transformer) {
|
|
transformer.resetProps({
|
|
zeroTop: 0,
|
|
zeroLeft: 0,
|
|
});
|
|
}
|
|
} else {
|
|
this._contextService.setContextValue(FOCUSING_COMMON_DRAWINGS, true);
|
|
this._docDrawingService.focusDrawing(params);
|
|
this._setDrawingSelections(params);
|
|
const prevSegmentId = this._docSelectionRenderService.getSegment();
|
|
const segmentId = getDocumentDrawingSegmentId(this._context.unit, params[0].drawingId);
|
|
|
|
// Change segmentId when click drawing in different segment.
|
|
if (prevSegmentId !== segmentId) {
|
|
this._docSelectionRenderService.setSegment(segmentId);
|
|
}
|
|
|
|
if (transformer) {
|
|
transformer.resetProps({
|
|
zeroTop: docsTop,
|
|
zeroLeft: docsLeft,
|
|
});
|
|
}
|
|
}
|
|
this._updateDrawingsEditStatus();
|
|
})
|
|
);
|
|
}
|
|
|
|
// Update drawings edit status and opacity. You can not edit header footer images when you are editing body. and vice verse.
|
|
private _updateDrawingsEditStatus(drawingIds?: ReadonlySet<string>, force = false) {
|
|
if (!this._context) return;
|
|
|
|
const { unit: docDataModel, scene, unitId } = this._context;
|
|
const viewModel = this._renderManagerSrv
|
|
.getRenderUnitById(unitId)
|
|
?.with(DocSkeletonManagerService)
|
|
.getViewModel();
|
|
|
|
if (viewModel == null || docDataModel == null) {
|
|
return;
|
|
}
|
|
|
|
const snapshot = docDataModel.getSnapshot();
|
|
const { drawings } = collectDocDrawings(snapshot);
|
|
const isEditBody = viewModel.getEditArea() === DocumentEditArea.BODY;
|
|
const isDocInteractionFocusing = this._isDocumentInteractionFocusing(unitId);
|
|
const contextKey = `${viewModel.getEditArea()}:${isDocInteractionFocusing}`;
|
|
const effectiveDrawingIds = drawingIds != null && contextKey === this._lastEditStatusContextKey
|
|
? drawingIds
|
|
: undefined;
|
|
if (effectiveDrawingIds == null) {
|
|
if (!force && contextKey === this._lastEditStatusContextKey) {
|
|
return;
|
|
}
|
|
this._lastEditStatusContextKey = contextKey;
|
|
}
|
|
const readOnlyDrawingIds = new Set<string>();
|
|
const drawingShapesById = this._indexDrawingShapes(scene.getAllObjects(), unitId);
|
|
|
|
for (const key of Object.keys(drawings)) {
|
|
const drawing = drawings[key];
|
|
if (effectiveDrawingIds != null && !effectiveDrawingIds.has(drawing.drawingId)) {
|
|
continue;
|
|
}
|
|
const segmentId = getDocumentDrawingSegmentId(docDataModel, drawing.drawingId);
|
|
const editable = canEditDocumentTargets(this._permissionService, unitId, [
|
|
...getDocumentEntityParentPermissionObjectIds(docDataModel, segmentId, 'drawing', drawing.drawingId),
|
|
getDocumentEntityPermissionObjectId(segmentId, 'drawing', drawing.drawingId),
|
|
]);
|
|
this._recordReadOnlyDrawing(readOnlyDrawingIds, drawing.drawingId, editable);
|
|
const drawingShapes = drawingShapesById.get(drawing.drawingId) ?? [];
|
|
|
|
if (drawingShapes.length) {
|
|
for (const shape of drawingShapes) {
|
|
scene.detachTransformerFrom(shape);
|
|
this._setTransformerEditable(shape, editable);
|
|
try {
|
|
(shape as Image).setOpacity(isDocInteractionFocusing ? 0.5 : 1);
|
|
} catch {
|
|
}
|
|
if (!isDocInteractionFocusing) {
|
|
continue;
|
|
}
|
|
if (
|
|
(isEditBody && drawing.isMultiTransform !== BooleanNumber.TRUE)
|
|
|| (!isEditBody && drawing.isMultiTransform === BooleanNumber.TRUE)
|
|
) {
|
|
this._attachTransformerIfEditable(shape, editable, drawing.allowTransform);
|
|
|
|
try {
|
|
(shape as Image).setOpacity(1);
|
|
} catch {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
this._clearReadOnlyDrawingFocus(unitId, readOnlyDrawingIds);
|
|
}
|
|
|
|
private _indexDrawingShapes(shapes: BaseObject[], unitId: string): Map<string, BaseObject[]> {
|
|
const prefix = getDrawingShapeKeyByDrawingSearch({ unitId, subUnitId: unitId, drawingId: '' });
|
|
const result = new Map<string, BaseObject[]>();
|
|
for (const shape of shapes) {
|
|
if (!shape.oKey.startsWith(prefix)) {
|
|
continue;
|
|
}
|
|
const drawingId = shape.oKey.slice(prefix.length).split('#-#', 1)[0];
|
|
if (!drawingId) {
|
|
continue;
|
|
}
|
|
const drawingShapes = result.get(drawingId) ?? [];
|
|
drawingShapes.push(shape);
|
|
result.set(drawingId, drawingShapes);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private _recordReadOnlyDrawing(readOnlyDrawingIds: Set<string>, drawingId: string, editable: boolean): void {
|
|
if (!editable) {
|
|
readOnlyDrawingIds.add(drawingId);
|
|
}
|
|
}
|
|
|
|
private _attachTransformerIfEditable(shape: BaseObject, editable: boolean, allowTransform?: boolean): void {
|
|
if (editable && allowTransform !== false) {
|
|
this._context.scene.attachTransformerTo(shape);
|
|
}
|
|
}
|
|
|
|
private _clearReadOnlyDrawingFocus(unitId: string, readOnlyDrawingIds: Set<string>): void {
|
|
if (readOnlyDrawingIds.size === 0) {
|
|
return;
|
|
}
|
|
const focusedDrawings = this._drawingManagerService.getFocusDrawings();
|
|
const editableFocus = focusedDrawings.filter((drawing) =>
|
|
drawing.unitId !== unitId || !readOnlyDrawingIds.has(drawing.drawingId));
|
|
if (editableFocus.length !== focusedDrawings.length) {
|
|
this._drawingManagerService.focusDrawing(editableFocus);
|
|
}
|
|
}
|
|
|
|
private _setTransformerEditable(shape: BaseObject, editable: boolean): void {
|
|
if (editable) {
|
|
const config = this._editableTransformerConfigs.get(shape);
|
|
if (config) {
|
|
shape.transformerConfig = config;
|
|
this._editableTransformerConfigs.delete(shape);
|
|
}
|
|
const evented = this._editableEventedStates.get(shape);
|
|
if (evented !== undefined) {
|
|
shape.evented = evented;
|
|
this._editableEventedStates.delete(shape);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (!this._editableTransformerConfigs.has(shape)) {
|
|
this._editableTransformerConfigs.set(shape, shape.transformerConfig);
|
|
}
|
|
if (!this._editableEventedStates.has(shape)) {
|
|
this._editableEventedStates.set(shape, shape.evented);
|
|
}
|
|
shape.evented = false;
|
|
this._context.scene.getTransformer()?.clearControlByIds([shape.oKey]);
|
|
shape.transformerConfig = {
|
|
...shape.transformerConfig,
|
|
moveEnabled: false,
|
|
resizeEnabled: false,
|
|
rotateEnabled: false,
|
|
};
|
|
}
|
|
|
|
private _scheduleDrawingsEditStatusUpdate(drawingIds?: ReadonlySet<string>): void {
|
|
if (drawingIds == null) {
|
|
this._pendingEditStatusDrawingIds = null;
|
|
this._lastEditStatusContextKey = null;
|
|
} else if (this._pendingEditStatusDrawingIds !== null) {
|
|
this._pendingEditStatusDrawingIds ??= new Set<string>();
|
|
drawingIds.forEach((drawingId) => this._pendingEditStatusDrawingIds?.add(drawingId));
|
|
}
|
|
if (this._editStatusUpdateScheduled) {
|
|
return;
|
|
}
|
|
this._editStatusUpdateScheduled = true;
|
|
queueMicrotask(() => {
|
|
this._editStatusUpdateScheduled = false;
|
|
if (!this._disposed) {
|
|
const pendingDrawingIds = this._pendingEditStatusDrawingIds;
|
|
this._pendingEditStatusDrawingIds = undefined;
|
|
this._updateDrawingsEditStatus(pendingDrawingIds ?? undefined);
|
|
}
|
|
});
|
|
}
|
|
|
|
private _canInsertDocImage(textRange: Nullable<ITextRangeParam>): boolean {
|
|
const { unit, unitId } = this._context;
|
|
const objectIds = textRange?.startOffset == null || textRange.endOffset == null
|
|
? []
|
|
: getDocumentEditTargetObjectIds(unit, textRange.segmentId ?? '', textRange);
|
|
return canEditDocumentTargets(this._permissionService, unitId, objectIds);
|
|
}
|
|
|
|
private _editAreaChangeListener() {
|
|
const { unitId } = this._context;
|
|
const viewModel = this._renderManagerSrv
|
|
.getRenderUnitById(unitId)
|
|
?.with(DocSkeletonManagerService)
|
|
.getViewModel();
|
|
|
|
if (viewModel == null) {
|
|
return;
|
|
}
|
|
|
|
this._updateDrawingsEditStatus();
|
|
|
|
this.disposeWithMe(
|
|
viewModel.editAreaChange$.subscribe(() => {
|
|
this._updateDrawingsEditStatus();
|
|
})
|
|
);
|
|
|
|
this.disposeWithMe(
|
|
this._docSelectionRenderService.onFocus$.subscribe(() => {
|
|
this._updateDrawingsEditStatus();
|
|
})
|
|
);
|
|
|
|
this.disposeWithMe(
|
|
this._docSelectionRenderService.onBlur$.subscribe(() => {
|
|
this._updateDrawingsEditStatus();
|
|
})
|
|
);
|
|
|
|
this.disposeWithMe(
|
|
this._docRefreshDrawingsService.refreshDrawings$.subscribe((skeleton) => {
|
|
if (skeleton == null) {
|
|
return;
|
|
}
|
|
|
|
// To wait the image is rendered.
|
|
queueMicrotask(() => {
|
|
this._updateDrawingsEditStatus(undefined, true);
|
|
});
|
|
})
|
|
);
|
|
|
|
this.disposeWithMe(
|
|
this._commandService.onCommandExecuted(async (command: ICommandInfo) => {
|
|
if (command.id !== RichTextEditingMutation.id) {
|
|
return;
|
|
}
|
|
const params = command.params as Partial<IRichTextEditingMutationParams> | undefined;
|
|
if (params?.unitId && params.unitId !== this._context.unitId) {
|
|
return;
|
|
}
|
|
const drawingIds = getDocMutationAffectedDrawingIds(params?.actions ?? null);
|
|
if (drawingIds?.size === 0) {
|
|
return;
|
|
}
|
|
|
|
// To wait the image is rendered.
|
|
this._scheduleDrawingsEditStatusUpdate(drawingIds ?? undefined);
|
|
})
|
|
);
|
|
}
|
|
|
|
private _setDrawingSelections(params: IDrawingParam[]) {
|
|
const { unit } = this._context;
|
|
const ranges = params.flatMap((item) => {
|
|
const segmentId = getDocumentDrawingSegmentId(unit, item.drawingId);
|
|
const body = unit.getSelfOrHeaderFooterModel(segmentId)?.getBody();
|
|
const block = body?.customBlocks?.find((block) => block.blockId === item.drawingId);
|
|
return block ? [{ startOffset: block.startIndex, endOffset: block.startIndex + 1, segmentId }] : [];
|
|
});
|
|
this._docSelectionManagerService.replaceDocRanges(ranges);
|
|
}
|
|
}
|