From e853a22770f79138859329cee6d10e3eb097f2f8 Mon Sep 17 00:00:00 2001 From: wpxp123456 <2677556700@qq.com> Date: Thu, 16 Jul 2026 20:37:49 +0800 Subject: [PATCH] feat(docs): move drawing commands and add image facade (#7278) --- .../data-model/text-x/build-utils/drawings.ts | 6 +- .../data-model/text-x/build-utils/index.ts | 1 + .../__tests__/drawing-commands.spec.ts | 201 ++++++-- .../__tests__/insert-shape.command.spec.ts | 2 +- .../commands/delete-doc-drawing.command.ts | 8 +- .../commands/commands/insert-shape.command.ts | 8 +- .../src/commands/commands/interfaces.ts | 41 -- .../commands/move-drawings.command.ts | 16 +- .../commands/update-doc-drawing.command.ts | 338 +------------ ...oc-drawing-notification.controller.spec.ts | 128 ++++- ...wing-transformer-update.controller.spec.ts | 2 +- .../doc-float-dom.controller.spec.ts | 2 +- .../doc-drawing-notification.controller.ts | 178 ++++++- ...c-drawing-transformer-update.controller.ts | 9 +- .../controllers/doc-float-dom.controller.ts | 9 +- ...c-drawing-update.render-controller.spec.ts | 2 +- .../doc-drawing-update.render-controller.ts | 16 +- .../src/controllers/ui.controller.ts | 10 - packages/docs-drawing-ui/src/index.ts | 4 - .../src/menu/drawing-popup-menu.controller.ts | 3 +- .../doc-image-panel/DocDrawingPosition.tsx | 6 +- .../doc-image-panel/DocDrawingTextWrap.tsx | 3 +- .../__tests__/DocDrawingPosition.spec.tsx | 2 - .../__tests__/DocDrawingTextWrap.spec.tsx | 7 +- packages/docs-drawing/package.json | 15 +- ...doc-drawing-wrapping-style.command.spec.ts | 110 ++++ .../commands/insert-doc-drawing.command.ts | 100 ++-- .../commands/remove-doc-drawing.command.ts | 117 ++--- .../commands/set-drawing-arrange.command.ts | 6 +- .../update-doc-drawing-transform.command.ts | 76 +++ ...date-doc-drawing-wrapping-style.command.ts | 123 +++++ .../__tests__/doc-drawing.controller.spec.ts | 3 +- .../src/controllers/doc-drawing.controller.ts | 21 +- .../src/facade/__tests__/create-test-bed.ts | 61 +++ .../facade/__tests__/f-document-image.spec.ts | 277 +++++++++++ .../src/facade/f-document-image.ts | 468 ++++++++++++++++++ .../docs-drawing/src/facade/f-document.ts | 247 +++++++++ packages/docs-drawing/src/facade/f-enum.ts | 99 ++++ packages/docs-drawing/src/facade/index.ts | 21 + packages/docs-drawing/src/index.ts | 10 + .../commands/clipboard.inner.command.ts | 27 +- packages/docs-ui/src/index.ts | 2 +- packages/docs/src/index.ts | 2 +- packages/docs/src/utils/util.ts | 36 +- .../commands/delete-drawings.command.ts | 4 +- .../sheet-drawing-copy-paste.controller.ts | 6 +- .../sheet-drawing-permission.controller.ts | 6 +- .../sheet-drawing-update.controller.ts | 6 +- .../sheets-drawing-ui/src/facade/f-univer.ts | 10 +- .../canvas-float-dom-manager.service.ts | 6 +- .../commands/insert-sheet-drawing.command.ts | 4 +- .../commands/remove-sheet-drawing.command.ts | 8 +- .../sheets-drawing/src/facade/f-univer.ts | 10 +- packages/sheets-drawing/src/index.ts | 7 +- pnpm-lock.yaml | 6 + .../preset-docs-drawing/src/preset.ts | 4 + 56 files changed, 2209 insertions(+), 691 deletions(-) delete mode 100644 packages/docs-drawing-ui/src/commands/commands/interfaces.ts create mode 100644 packages/docs-drawing/src/commands/commands/__tests__/update-doc-drawing-wrapping-style.command.spec.ts rename packages/{docs-drawing-ui => docs-drawing}/src/commands/commands/insert-doc-drawing.command.ts (66%) rename packages/{docs-drawing-ui => docs-drawing}/src/commands/commands/remove-doc-drawing.command.ts (66%) rename packages/{docs-drawing-ui => docs-drawing}/src/commands/commands/set-drawing-arrange.command.ts (93%) create mode 100644 packages/docs-drawing/src/commands/commands/update-doc-drawing-transform.command.ts create mode 100644 packages/docs-drawing/src/commands/commands/update-doc-drawing-wrapping-style.command.ts create mode 100644 packages/docs-drawing/src/facade/__tests__/create-test-bed.ts create mode 100644 packages/docs-drawing/src/facade/__tests__/f-document-image.spec.ts create mode 100644 packages/docs-drawing/src/facade/f-document-image.ts create mode 100644 packages/docs-drawing/src/facade/f-document.ts create mode 100644 packages/docs-drawing/src/facade/f-enum.ts create mode 100644 packages/docs-drawing/src/facade/index.ts diff --git a/packages/core/src/docs/data-model/text-x/build-utils/drawings.ts b/packages/core/src/docs/data-model/text-x/build-utils/drawings.ts index 130ca46363..b02c2d7135 100644 --- a/packages/core/src/docs/data-model/text-x/build-utils/drawings.ts +++ b/packages/core/src/docs/data-model/text-x/build-utils/drawings.ts @@ -15,7 +15,7 @@ */ import type { ITextRange, ITextRangeParam } from '../../../../sheets/typedef'; -import type { IDocumentBody } from '../../../../types/interfaces'; +import type { IDocumentBody, IDrawingParam } from '../../../../types/interfaces'; import type { DocumentDataModel } from '../../document-data-model'; import type { JSONXActions } from '../../json-x/json-x'; import { createParagraphId } from '../../../paragraph-id'; @@ -29,7 +29,7 @@ import { deleteSelectionTextX } from './text-x-utils'; export interface IAddDrawingParam { selection: ITextRangeParam; documentDataModel: DocumentDataModel; - drawings: any[]; + drawings: IDrawingParam[]; } export function getCustomBlockIdsInSelections(body: IDocumentBody, selections: ITextRange[]): string[] { @@ -147,7 +147,7 @@ function normalizeDrawingInsertOffset(body: IDocumentBody, offset: number): numb return offset === 0 && body.dataStream[0] === DataStreamTreeTokenType.PARAGRAPH ? 1 : offset; } -function buildDrawingInsertBody(body: IDocumentBody, drawings: any[], insertOffset: number): IDocumentBody { +function buildDrawingInsertBody(body: IDocumentBody, drawings: IDrawingParam[], insertOffset: number): IDocumentBody { const placeholders = DataStreamTreeTokenType.CUSTOM_BLOCK.repeat(drawings.length); const needsTrailingParagraph = body.dataStream[insertOffset] === DataStreamTreeTokenType.SECTION_BREAK || body.dataStream[insertOffset] === undefined; const dataStream = needsTrailingParagraph ? `${placeholders}${DataStreamTreeTokenType.PARAGRAPH}` : placeholders; diff --git a/packages/core/src/docs/data-model/text-x/build-utils/index.ts b/packages/core/src/docs/data-model/text-x/build-utils/index.ts index c2cbad0661..5931be3b52 100644 --- a/packages/core/src/docs/data-model/text-x/build-utils/index.ts +++ b/packages/core/src/docs/data-model/text-x/build-utils/index.ts @@ -81,6 +81,7 @@ export class BuildTextUtils { export { getSingleDataStreamChange } from './data-stream-change'; export type { IDataStreamChange } from './data-stream-change'; +export { getCustomBlockIdsInSelections } from './drawings'; export { getParagraphContentStartOffset, getParagraphContentStartOffsets, getParagraphFollowingBlockOffset } from './paragraph'; export { containsInteriorInsertionOffset, diff --git a/packages/docs-drawing-ui/src/commands/commands/__tests__/drawing-commands.spec.ts b/packages/docs-drawing-ui/src/commands/commands/__tests__/drawing-commands.spec.ts index f214eb4e4d..0e3ff62270 100644 --- a/packages/docs-drawing-ui/src/commands/commands/__tests__/drawing-commands.spec.ts +++ b/packages/docs-drawing-ui/src/commands/commands/__tests__/drawing-commands.spec.ts @@ -15,6 +15,7 @@ */ import type { Dependency, DependencyIdentifier, DocumentDataModel, ICommand, IDocumentData } from '@univerjs/core'; +import type { IInsertDocDrawingCommandParams, ISetDocDrawingArrangeCommandParams, IUpdateDrawingDocTransformCommandParams } from '@univerjs/docs-drawing'; import { ArrangeTypeEnum, awaitTime, @@ -45,6 +46,12 @@ import { DocDrawingService, IDocDrawingAdapterService, IDocDrawingService, + InsertDocDrawingCommand, + RemoveDocDrawingCommand, + SetDocDrawingArrangeCommand, + TextWrappingStyle, + UpdateDocDrawingWrappingStyleCommand, + UpdateDrawingDocTransformCommand, } from '@univerjs/docs-drawing'; import { DocSelectionRenderService } from '@univerjs/docs-ui'; import { DrawingManagerService, IDrawingManagerService } from '@univerjs/drawing'; @@ -58,20 +65,14 @@ import { DocRefreshDrawingsService } from '../../../services/doc-refresh-drawing import { ClearDocDrawingTransformerOperation } from '../../operations/clear-drawing-transformer.operation'; import { DeleteDocDrawingsCommand } from '../delete-doc-drawing.command'; import { GroupDocDrawingCommand } from '../group-doc-drawing.command'; -import { InsertDocDrawingCommand } from '../insert-doc-drawing.command'; import { InsertDocImageCommand } from '../insert-image.command'; import { MoveDocDrawingsCommand } from '../move-drawings.command'; -import { RemoveDocDrawingCommand } from '../remove-doc-drawing.command'; -import { SetDocDrawingArrangeCommand } from '../set-drawing-arrange.command'; import { UngroupDocDrawingCommand } from '../ungroup-doc-drawing.command'; import { IMoveInlineDrawingCommand, ITransformNonInlineDrawingCommand, - TextWrappingStyle, UpdateDocDrawingDistanceCommand, - UpdateDocDrawingWrappingStyleCommand, UpdateDocDrawingWrapTextCommand, - UpdateDrawingDocTransformCommand, } from '../update-doc-drawing.command'; function createBaseDocData(): IDocumentData { @@ -133,6 +134,35 @@ function createDrawingDocData(): IDocumentData { }; } +function createHeaderDrawingDocData(): IDocumentData { + const docData = createBaseDocData(); + docData.headers = { + 'header-1': { + headerId: 'header-1', + body: { + dataStream: '\b\r\n', + customBlocks: [{ startIndex: 0, blockId: 'header-shape-1' }], + }, + }, + }; + docData.drawings = { + 'header-shape-1': { + drawingId: 'header-shape-1', + unitId: 'test-doc', + subUnitId: 'test-doc', + drawingType: DrawingTypeEnum.DRAWING_IMAGE, + layoutType: PositionedObjectLayoutType.WRAP_SQUARE, + docTransform: { + positionH: { posOffset: 1 }, + positionV: { posOffset: 2 }, + }, + } as never, + }; + docData.drawingsOrder = ['header-shape-1']; + + return docData; +} + function createChartDrawingDocData(): IDocumentData { return { ...createDrawingDocData(), @@ -336,21 +366,16 @@ function setupDrawingTestBed(docData: IDocumentData, dependencies: Dependency[] const commandService = get(ICommandService); [ - InsertDocDrawingCommand, - RemoveDocDrawingCommand, DeleteDocDrawingsCommand, MoveDocDrawingsCommand, GroupDocDrawingCommand, UngroupDocDrawingCommand, - SetDocDrawingArrangeCommand, ClearDocDrawingTransformerOperation, InsertDocImageCommand, IMoveInlineDrawingCommand, ITransformNonInlineDrawingCommand, UpdateDocDrawingDistanceCommand, - UpdateDocDrawingWrappingStyleCommand, UpdateDocDrawingWrapTextCommand, - UpdateDrawingDocTransformCommand, RichTextEditingMutation as unknown as ICommand, ].forEach((command) => commandService.registerCommand(command)); @@ -393,16 +418,19 @@ describe('docs drawing commands integration', () => { style: null as never, }]); - expect(await testBed.commandService.executeCommand(InsertDocDrawingCommand.id, { + expect(await testBed.commandService.executeCommand(InsertDocDrawingCommand.id, { + unitId: 'test-doc', drawings: [{ drawingId: 'shape-1', unitId: 'test-doc', subUnitId: 'test-doc', - drawingType: 'image', + drawingType: DrawingTypeEnum.DRAWING_IMAGE, layoutType: PositionedObjectLayoutType.WRAP_SQUARE, docTransform: { - positionH: { posOffset: 1 }, - positionV: { posOffset: 2 }, + size: { width: 1, height: 1 }, + positionH: { relativeFrom: ObjectRelativeFromH.PAGE, posOffset: 1 }, + positionV: { relativeFrom: ObjectRelativeFromV.PARAGRAPH, posOffset: 2 }, + angle: 0, }, }], })).toBe(true); @@ -441,16 +469,19 @@ describe('docs drawing commands integration', () => { endOffset: insertOffset, }); - expect(await testBed.commandService.executeCommand(InsertDocDrawingCommand.id, { + expect(await testBed.commandService.executeCommand(InsertDocDrawingCommand.id, { + unitId: 'test-doc', drawings: [{ drawingId: 'shape-1', unitId: 'test-doc', subUnitId: 'test-doc', - drawingType: 'image', + drawingType: DrawingTypeEnum.DRAWING_IMAGE, layoutType: PositionedObjectLayoutType.WRAP_SQUARE, docTransform: { - positionH: { posOffset: 1 }, - positionV: { posOffset: 2 }, + size: { width: 1, height: 1 }, + positionH: { relativeFrom: ObjectRelativeFromH.PAGE, posOffset: 1 }, + positionV: { relativeFrom: ObjectRelativeFromV.PARAGRAPH, posOffset: 2 }, + angle: 0, }, }], })).toBe(true); @@ -478,7 +509,8 @@ describe('docs drawing commands integration', () => { style: null as never, }]); - expect(await testBed.commandService.executeCommand(InsertDocDrawingCommand.id, { + expect(await testBed.commandService.executeCommand(InsertDocDrawingCommand.id, { + unitId: 'test-doc', textRange: { startOffset: insertOffset, endOffset: insertOffset, @@ -489,11 +521,13 @@ describe('docs drawing commands integration', () => { drawingId: 'shape-1', unitId: 'test-doc', subUnitId: 'test-doc', - drawingType: 'image', + drawingType: DrawingTypeEnum.DRAWING_IMAGE, layoutType: PositionedObjectLayoutType.WRAP_SQUARE, docTransform: { - positionH: { posOffset: 1 }, - positionV: { posOffset: 2 }, + size: { width: 1, height: 1 }, + positionH: { relativeFrom: ObjectRelativeFromH.PAGE, posOffset: 1 }, + positionV: { relativeFrom: ObjectRelativeFromV.PARAGRAPH, posOffset: 2 }, + angle: 0, }, }], })).toBe(true); @@ -508,6 +542,44 @@ describe('docs drawing commands integration', () => { testBed.univer.dispose(); }); + it('inserts into the document specified by the command params', async () => { + const testBed = setupDrawingTestBed(createBaseDocData()); + const targetData = createBaseDocData(); + targetData.id = 'target-doc'; + const targetDoc = testBed.univer.createUnit(UniverInstanceType.UNIVER_DOC, targetData); + + testBed.injector.get(CoreDocDrawingController).loadDrawingDataForUnit('target-doc'); + testBed.get(IUniverInstanceService).setCurrentUnitForType('test-doc'); + + expect(await testBed.commandService.executeCommand(InsertDocDrawingCommand.id, { + unitId: 'target-doc', + textRange: { + startOffset: 5, + endOffset: 5, + collapsed: true, + segmentId: '', + }, + drawings: [{ + drawingId: 'target-shape-1', + unitId: 'target-doc', + subUnitId: 'target-doc', + drawingType: DrawingTypeEnum.DRAWING_IMAGE, + layoutType: PositionedObjectLayoutType.WRAP_SQUARE, + docTransform: { + size: { width: 1, height: 1 }, + positionH: { relativeFrom: ObjectRelativeFromH.PAGE, posOffset: 1 }, + positionV: { relativeFrom: ObjectRelativeFromV.PARAGRAPH, posOffset: 2 }, + angle: 0, + }, + }], + })).toBe(true); + + expect(targetDoc.getBody()?.customBlocks).toEqual([{ startIndex: 5, blockId: 'target-shape-1' }]); + expect(testBed.get(IUniverInstanceService).getUnit('test-doc', UniverInstanceType.UNIVER_DOC)?.getBody()?.customBlocks).toEqual([]); + + testBed.univer.dispose(); + }); + it('replaces the selected drawing block when inserting a new drawing over a selection', async () => { const testBed = setupDrawingTestBed(createDrawingDocData()); @@ -520,16 +592,19 @@ describe('docs drawing commands integration', () => { style: null as never, }]); - expect(await testBed.commandService.executeCommand(InsertDocDrawingCommand.id, { + expect(await testBed.commandService.executeCommand(InsertDocDrawingCommand.id, { + unitId: 'test-doc', drawings: [{ drawingId: 'shape-2', unitId: 'test-doc', subUnitId: 'test-doc', - drawingType: 'image', + drawingType: DrawingTypeEnum.DRAWING_IMAGE, layoutType: PositionedObjectLayoutType.WRAP_SQUARE, docTransform: { - positionH: { posOffset: 10 }, - positionV: { posOffset: 20 }, + size: { width: 1, height: 1 }, + positionH: { relativeFrom: ObjectRelativeFromH.PAGE, posOffset: 10 }, + positionV: { relativeFrom: ObjectRelativeFromV.PARAGRAPH, posOffset: 20 }, + angle: 0, }, }], })).toBe(true); @@ -568,6 +643,62 @@ describe('docs drawing commands integration', () => { testBed.univer.dispose(); }); + it('deletes from the explicit text range segment instead of the active selection segment', async () => { + const testBed = setupDrawingTestBed(createHeaderDrawingDocData()); + + expect(await testBed.commandService.executeCommand(RemoveDocDrawingCommand.id, { + unitId: 'test-doc', + drawings: [{ + unitId: 'test-doc', + subUnitId: 'test-doc', + drawingId: 'header-shape-1', + drawingType: DrawingTypeEnum.DRAWING_IMAGE, + }], + textRange: { + startOffset: 0, + endOffset: 0, + collapsed: true, + segmentId: 'header-1', + }, + } as never)).toBe(true); + + const doc = testBed.get(IUniverInstanceService) + .getUnit('test-doc', UniverInstanceType.UNIVER_DOC)!; + + expect(doc.getSelfOrHeaderFooterModel('header-1')?.getBody()?.customBlocks).toEqual([]); + expect(doc.getSnapshot().drawings?.['header-shape-1']).toBeUndefined(); + + testBed.univer.dispose(); + }); + + it('deletes from the document specified by the command params', async () => { + const testBed = setupDrawingTestBed(createBaseDocData()); + const targetData = createDrawingDocData(); + targetData.id = 'target-doc'; + targetData.drawings!['shape-1'].unitId = 'target-doc'; + targetData.drawings!['shape-1'].subUnitId = 'target-doc'; + const targetDoc = testBed.univer.createUnit(UniverInstanceType.UNIVER_DOC, targetData); + + testBed.injector.get(CoreDocDrawingController).loadDrawingDataForUnit('target-doc'); + testBed.get(IUniverInstanceService).setCurrentUnitForType('test-doc'); + + expect(await testBed.commandService.executeCommand(RemoveDocDrawingCommand.id, { + unitId: 'target-doc', + drawings: [{ + unitId: 'target-doc', + subUnitId: 'target-doc', + drawingId: 'shape-1', + drawingType: DrawingTypeEnum.DRAWING_IMAGE, + }], + })).toBe(true); + + expect(targetDoc.getBody()?.customBlocks).toEqual([]); + expect(targetDoc.getSnapshot().drawings).toEqual({}); + expect(testBed.get(IUniverInstanceService).getUnit('test-doc', UniverInstanceType.UNIVER_DOC)?.getBody()?.customBlocks).toEqual([]); + + testBed.univer.dispose(); + }); + it('includes drawing adapter resource mutations in the same undo item when deleting a drawing', async () => { const testBed = setupDrawingTestBed(createChartDrawingDocData()); const removeResourceMutation = { @@ -877,13 +1008,14 @@ describe('docs drawing commands integration', () => { vi.spyOn(testBed.get(DocSkeletonManagerService), 'getSkeleton').mockReturnValue(skeleton); const refreshDrawings = vi.spyOn(testBed.get(DocRefreshDrawingsService), 'refreshDrawings'); - expect(await testBed.commandService.executeCommand(UpdateDrawingDocTransformCommand.id, { + expect(await testBed.commandService.executeCommand(UpdateDrawingDocTransformCommand.id, { unitId: 'test-doc', subUnitId: 'test-doc', drawings: [{ drawingId: 'shape-1', key: 'positionV', value: { + relativeFrom: ObjectRelativeFromV.PAGE, posOffset: 18, }, }], @@ -893,7 +1025,10 @@ describe('docs drawing commands integration', () => { const doc = testBed.get(IUniverInstanceService) .getUnit('test-doc', UniverInstanceType.UNIVER_DOC)!; - expect(doc.getSnapshot().drawings?.['shape-1'].docTransform.positionV).toEqual({ posOffset: 18 }); + expect(doc.getSnapshot().drawings?.['shape-1'].docTransform.positionV).toEqual({ + relativeFrom: ObjectRelativeFromV.PAGE, + posOffset: 18, + }); expect(testBed.refreshControls).toHaveBeenCalled(); expect(refreshDrawings).toHaveBeenCalledWith(skeleton); @@ -1039,7 +1174,7 @@ describe('docs drawing commands integration', () => { expect(doc.getSnapshot().drawingsOrder).toEqual(['drawing-a', 'drawing-b', 'drawing-c']); - expect(await testBed.commandService.executeCommand(SetDocDrawingArrangeCommand.id, { + expect(await testBed.commandService.executeCommand(SetDocDrawingArrangeCommand.id, { unitId: 'test-doc', subUnitId: 'test-doc', drawingIds: ['drawing-a'], @@ -1048,7 +1183,7 @@ describe('docs drawing commands integration', () => { await awaitTime(0); expect(doc.getSnapshot().drawingsOrder).toEqual(['drawing-b', 'drawing-c', 'drawing-a']); - expect(await testBed.commandService.executeCommand(SetDocDrawingArrangeCommand.id, { + expect(await testBed.commandService.executeCommand(SetDocDrawingArrangeCommand.id, { unitId: 'test-doc', subUnitId: 'test-doc', drawingIds: ['drawing-a'], @@ -1057,7 +1192,7 @@ describe('docs drawing commands integration', () => { await awaitTime(0); expect(doc.getSnapshot().drawingsOrder).toEqual(['drawing-b', 'drawing-a', 'drawing-c']); - expect(await testBed.commandService.executeCommand(SetDocDrawingArrangeCommand.id, { + expect(await testBed.commandService.executeCommand(SetDocDrawingArrangeCommand.id, { unitId: 'test-doc', subUnitId: 'test-doc', drawingIds: ['drawing-b'], @@ -1066,7 +1201,7 @@ describe('docs drawing commands integration', () => { await awaitTime(0); expect(doc.getSnapshot().drawingsOrder).toEqual(['drawing-a', 'drawing-b', 'drawing-c']); - expect(await testBed.commandService.executeCommand(SetDocDrawingArrangeCommand.id, { + expect(await testBed.commandService.executeCommand(SetDocDrawingArrangeCommand.id, { unitId: 'test-doc', subUnitId: 'test-doc', drawingIds: ['drawing-c'], diff --git a/packages/docs-drawing-ui/src/commands/commands/__tests__/insert-shape.command.spec.ts b/packages/docs-drawing-ui/src/commands/commands/__tests__/insert-shape.command.spec.ts index 9094de7449..de7a99cac2 100644 --- a/packages/docs-drawing-ui/src/commands/commands/__tests__/insert-shape.command.spec.ts +++ b/packages/docs-drawing-ui/src/commands/commands/__tests__/insert-shape.command.spec.ts @@ -15,8 +15,8 @@ */ import { ICommandService, IUniverInstanceService, UniverInstanceType } from '@univerjs/core'; +import { InsertDocDrawingCommand } from '@univerjs/docs-drawing'; import { describe, expect, it, vi } from 'vitest'; -import { InsertDocDrawingCommand } from '../insert-doc-drawing.command'; import { InsertDocEllipseShapeCommand, InsertDocRectangleShapeCommand } from '../insert-shape.command'; function createAccessor() { diff --git a/packages/docs-drawing-ui/src/commands/commands/delete-doc-drawing.command.ts b/packages/docs-drawing-ui/src/commands/commands/delete-doc-drawing.command.ts index db0ce4b38e..52ed64ac02 100644 --- a/packages/docs-drawing-ui/src/commands/commands/delete-doc-drawing.command.ts +++ b/packages/docs-drawing-ui/src/commands/commands/delete-doc-drawing.command.ts @@ -15,11 +15,9 @@ */ import type { IAccessor, ICommand } from '@univerjs/core'; -import type { IDocDrawing } from '@univerjs/docs-drawing'; -import type { IDeleteDrawingCommandParams } from './interfaces'; +import type { IDocDrawing, IRemoveDocDrawingCommandParams } from '@univerjs/docs-drawing'; import { CommandType, ICommandService } from '@univerjs/core'; -import { IDocDrawingService } from '@univerjs/docs-drawing'; -import { RemoveDocDrawingCommand } from './remove-doc-drawing.command'; +import { IDocDrawingService, RemoveDocDrawingCommand } from '@univerjs/docs-drawing'; export const DeleteDocDrawingsCommand: ICommand = { id: 'doc.command.delete-drawing', @@ -46,7 +44,7 @@ export const DeleteDocDrawingsCommand: ICommand = { drawingType, }; }); - return commandService.executeCommand(RemoveDocDrawingCommand.id, { + return commandService.executeCommand(RemoveDocDrawingCommand.id, { unitId, drawings: newDrawings, }); diff --git a/packages/docs-drawing-ui/src/commands/commands/insert-shape.command.ts b/packages/docs-drawing-ui/src/commands/commands/insert-shape.command.ts index cb573cce0c..41798343cd 100644 --- a/packages/docs-drawing-ui/src/commands/commands/insert-shape.command.ts +++ b/packages/docs-drawing-ui/src/commands/commands/insert-shape.command.ts @@ -15,7 +15,7 @@ */ import type { IAccessor, ICommand } from '@univerjs/core'; -import type { IInsertDrawingCommandParams } from './interfaces'; +import type { IInsertDocDrawingCommandParams } from '@univerjs/docs-drawing'; import { BooleanNumber, CommandType, @@ -29,7 +29,7 @@ import { WrapTextType, } from '@univerjs/core'; import { buildDocTransform, docDrawingPositionToTransform } from '@univerjs/docs'; -import { InsertDocDrawingCommand } from './insert-doc-drawing.command'; +import { InsertDocDrawingCommand } from '@univerjs/docs-drawing'; type DocShapeKind = 'rectangle' | 'ellipse'; @@ -60,7 +60,7 @@ function createShapeInsertCommand(shape: DocShapeKind, width: number, height: nu const docTransform = buildDocTransform(width, height); - return commandService.executeCommand(InsertDocDrawingCommand.id, { + return commandService.executeCommand(InsertDocDrawingCommand.id, { unitId, drawings: [{ unitId, @@ -81,7 +81,7 @@ function createShapeInsertCommand(shape: DocShapeKind, width: number, height: nu distR: 0, distT: 0, }], - } as IInsertDrawingCommandParams); + }); }, }; } diff --git a/packages/docs-drawing-ui/src/commands/commands/interfaces.ts b/packages/docs-drawing-ui/src/commands/commands/interfaces.ts deleted file mode 100644 index f5552c5579..0000000000 --- a/packages/docs-drawing-ui/src/commands/commands/interfaces.ts +++ /dev/null @@ -1,41 +0,0 @@ -/** - * 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 { DrawingTypeEnum, ITextRangeParam } from '@univerjs/core'; -import type { IDocDrawing } from '@univerjs/docs-drawing'; - -export interface IInsertDrawingCommandParams { - unitId: string; - drawings: IDocDrawing[]; - textRange?: ITextRangeParam; -} - -export interface IDeleteDrawingCommandParam { - unitId: string; - subUnitId: string; - drawingId: string; - drawingType: DrawingTypeEnum; -} - -export interface IDeleteDrawingCommandParams { - unitId: string; - drawings: IDeleteDrawingCommandParam[]; -} - -export interface ISetDrawingCommandParams { - unitId: string; - drawings: Partial[]; -} diff --git a/packages/docs-drawing-ui/src/commands/commands/move-drawings.command.ts b/packages/docs-drawing-ui/src/commands/commands/move-drawings.command.ts index 994d981b4b..fc09c86150 100644 --- a/packages/docs-drawing-ui/src/commands/commands/move-drawings.command.ts +++ b/packages/docs-drawing-ui/src/commands/commands/move-drawings.command.ts @@ -14,9 +14,8 @@ * limitations under the License. */ -import type { DocumentDataModel, IAccessor, ICommand } from '@univerjs/core'; -import type { IDocDrawing } from '@univerjs/docs-drawing'; -import type { IDrawingDocTransform, IUpdateDrawingDocTransformParams } from './update-doc-drawing.command'; +import type { DocumentDataModel, IAccessor, ICommand, IObjectPositionH, IObjectPositionV } from '@univerjs/core'; +import type { IDocDrawing, IDrawingDocTransform, IUpdateDrawingDocTransformCommandParams } from '@univerjs/docs-drawing'; import { CommandType, Direction, @@ -25,9 +24,8 @@ import { PositionedObjectLayoutType, UniverInstanceType, } from '@univerjs/core'; -import { IDocDrawingService } from '@univerjs/docs-drawing'; +import { IDocDrawingService, UpdateDrawingDocTransformCommand } from '@univerjs/docs-drawing'; import { IRenderManagerService } from '@univerjs/engine-render'; -import { UpdateDrawingDocTransformCommand } from './update-doc-drawing.command'; export interface IMoveDrawingsCommandParams { direction: Direction; @@ -74,8 +72,8 @@ export const MoveDocDrawingsCommand: ICommand = { const { positionH, positionV } = drawingData.docTransform; - const newPositionH = { ...positionH }; - const newPositionV = { ...positionV }; + const newPositionH: IObjectPositionH = { ...positionH }; + const newPositionV: IObjectPositionV = { ...positionV }; if (direction === Direction.UP) { newPositionV.posOffset = (newPositionV.posOffset ?? 0) - 2; @@ -91,14 +89,14 @@ export const MoveDocDrawingsCommand: ICommand = { drawingId, key: direction === Direction.UP || direction === Direction.DOWN ? 'positionV' : 'positionH', value: direction === Direction.UP || direction === Direction.DOWN ? newPositionV : newPositionH, - } as IDrawingDocTransform; + }; }).filter((drawing) => drawing != null) as IDrawingDocTransform[]; if (newDrawings.length === 0) { return false; } - const result = commandService.syncExecuteCommand(UpdateDrawingDocTransformCommand.id, { + const result = commandService.syncExecuteCommand(UpdateDrawingDocTransformCommand.id, { unitId, subUnitId: unitId, drawings: newDrawings, diff --git a/packages/docs-drawing-ui/src/commands/commands/update-doc-drawing.command.ts b/packages/docs-drawing-ui/src/commands/commands/update-doc-drawing.command.ts index a3c8426227..0aa3303b53 100644 --- a/packages/docs-drawing-ui/src/commands/commands/update-doc-drawing.command.ts +++ b/packages/docs-drawing-ui/src/commands/commands/update-doc-drawing.command.ts @@ -14,20 +14,15 @@ * limitations under the License. */ -import type { DocumentDataModel, IAccessor, ICommand, IDocDrawingBase, IDocDrawingPosition, IMutationInfo, IObjectPositionH, IObjectPositionV, ISize, JSONXActions, WrapTextType } from '@univerjs/core'; +import type { DocumentDataModel, IAccessor, ICommand, IDocDrawingBase, IDocDrawingPosition, IMutationInfo, JSONXActions, WrapTextType } from '@univerjs/core'; import type { IRichTextEditingMutationParams } from '@univerjs/docs'; import type { IDocDrawing } from '@univerjs/docs-drawing'; -import type { IDocumentSkeletonDrawing, IDocumentSkeletonHeaderFooter, IDocumentSkeletonPage } from '@univerjs/engine-render'; import { - BooleanNumber, CommandType, getRichTextEditPath, ICommandService, IUniverInstanceService, JSONX, - ObjectRelativeFromH, - ObjectRelativeFromV, - PositionedObjectLayoutType, TextX, TextXActionType, Tools, @@ -35,60 +30,9 @@ import { } from '@univerjs/core'; import { DocSkeletonManagerService, RichTextEditingMutation } from '@univerjs/docs'; import { DocSelectionRenderService } from '@univerjs/docs-ui'; -import { DocumentEditArea, IRenderManagerService } from '@univerjs/engine-render'; +import { IRenderManagerService } from '@univerjs/engine-render'; import { DocRefreshDrawingsService } from '../../services/doc-refresh-drawings.service'; -export enum TextWrappingStyle { - INLINE = 'inline', - BEHIND_TEXT = 'behindText', - IN_FRONT_OF_TEXT = 'inFrontOfText', - WRAP_SQUARE = 'wrapSquare', - WRAP_TOP_AND_BOTTOM = 'wrapTopAndBottom', -} - -const WRAPPING_STYLE_TO_LAYOUT_TYPE = { - [TextWrappingStyle.INLINE]: PositionedObjectLayoutType.INLINE, - [TextWrappingStyle.WRAP_SQUARE]: PositionedObjectLayoutType.WRAP_SQUARE, - [TextWrappingStyle.WRAP_TOP_AND_BOTTOM]: PositionedObjectLayoutType.WRAP_TOP_AND_BOTTOM, - [TextWrappingStyle.IN_FRONT_OF_TEXT]: PositionedObjectLayoutType.WRAP_NONE, - [TextWrappingStyle.BEHIND_TEXT]: PositionedObjectLayoutType.WRAP_NONE, -}; - -interface IDrawingAnchorInPage { - skeDrawing: IDocumentSkeletonDrawing; - pageMarginTop: number; - pageMarginLeft: number; -} - -export function findDrawingAnchorInPage( - page: IDocumentSkeletonPage | IDocumentSkeletonHeaderFooter, - drawingId: string, - pageMarginTop: number, - pageMarginLeft: number -): IDrawingAnchorInPage | null { - const skeDrawing = page.skeDrawings.get(drawingId); - if (skeDrawing) { - return { - skeDrawing, - pageMarginTop, - pageMarginLeft, - }; - } - - for (const table of page.skeTables.values()) { - for (const row of table.rows) { - for (const cell of row.cells) { - const cellAnchor = findDrawingAnchorInPage(cell, drawingId, cell.marginTop, cell.marginLeft); - if (cellAnchor) { - return cellAnchor; - } - } - } - } - - return null; -} - // eslint-disable-next-line max-lines-per-function function getDeleteAndInsertCustomBlockActions( segmentId: string, @@ -238,191 +182,6 @@ function getDeleteAndInsertCustomBlockActions( return rawActions; } -interface IUpdateDocDrawingWrappingStyleParams { - unitId: string; - subUnitId: string; - drawings: IDocDrawing[]; - wrappingStyle: TextWrappingStyle; -} - -/** - * The command to update drawing wrapping style. - */ -export const UpdateDocDrawingWrappingStyleCommand: ICommand = { - id: 'doc.command.update-doc-drawing-wrapping-style', - - type: CommandType.COMMAND, - - // eslint-disable-next-line max-lines-per-function, complexity - handler: (accessor: IAccessor, params?: IUpdateDocDrawingWrappingStyleParams) => { - if (params == null) { - return false; - } - - const { drawings, wrappingStyle, unitId } = params; - - const commandService = accessor.get(ICommandService); - const univerInstanceService = accessor.get(IUniverInstanceService); - const renderManagerService = accessor.get(IRenderManagerService); - - const renderObject = renderManagerService.getRenderById(unitId); - const skeletonData = renderObject?.with(DocSkeletonManagerService) - .getSkeleton() - .getSkeletonData(); - const viewModel = renderObject?.with(DocSkeletonManagerService).getViewModel(); - const scene = renderObject?.scene; - const documentDataModel = univerInstanceService.getCurrentUnitOfType(UniverInstanceType.UNIVER_DOC); - - if (documentDataModel == null || skeletonData == null || scene == null || viewModel == null) { - return false; - } - - const editArea = viewModel.getEditArea(); - const transformer = scene.getTransformerByCreate(); - - const { pages, skeHeaders, skeFooters } = skeletonData; - - const jsonX = JSONX.getInstance(); - const rawActions: JSONXActions = []; - - const { drawings: oldDrawings = {} } = documentDataModel.getSnapshot(); - - // Update drawing layoutType. - for (const drawing of drawings) { - const { drawingId } = drawing; - - // Update layoutType. - const oldLayoutType = oldDrawings[drawingId].layoutType; - const newLayoutType = WRAPPING_STYLE_TO_LAYOUT_TYPE[wrappingStyle]; - - if (oldLayoutType !== newLayoutType) { - const updateLayoutTypeAction = jsonX.replaceOp(['drawings', drawingId, 'layoutType'], oldLayoutType, newLayoutType); - - rawActions.push(updateLayoutTypeAction!); - } - - // Update behindDoc if layoutType is WRAP_NONE. - if (wrappingStyle === TextWrappingStyle.BEHIND_TEXT || wrappingStyle === TextWrappingStyle.IN_FRONT_OF_TEXT) { - const oldBehindDoc = oldDrawings[drawingId].behindDoc; - const newBehindDoc = wrappingStyle === TextWrappingStyle.BEHIND_TEXT ? BooleanNumber.TRUE : BooleanNumber.FALSE; - - if (oldBehindDoc !== newBehindDoc) { - const updateBehindDocAction = jsonX.replaceOp(['drawings', drawingId, 'behindDoc'], oldBehindDoc, newBehindDoc); - - rawActions.push(updateBehindDocAction!); - } - } - - if (wrappingStyle === TextWrappingStyle.INLINE) { - continue; - } - - // Update positionH and positionV if layoutType is not inline. - let drawingAnchor: IDrawingAnchorInPage | null = null; - for (const page of pages) { - const { headerId, footerId, marginTop, marginLeft, marginBottom, pageWidth, pageHeight } = page; - - switch (editArea) { - case DocumentEditArea.HEADER: { - const headerSke = skeHeaders.get(headerId)?.get(pageWidth); - - if (headerSke != null) { - drawingAnchor = findDrawingAnchorInPage(headerSke, drawingId, headerSke.marginTop, marginLeft); - } - - break; - } - - case DocumentEditArea.FOOTER: { - const footerSke = skeFooters.get(footerId)?.get(pageWidth); - if (footerSke != null) { - drawingAnchor = findDrawingAnchorInPage(footerSke, drawingId, pageHeight - marginBottom + footerSke.marginTop, marginLeft); - } - break; - } - - case DocumentEditArea.BODY: { - drawingAnchor = findDrawingAnchorInPage(page, drawingId, marginTop, marginLeft); - break; - } - } - - if (drawingAnchor != null) { - break; - } - } - - if (drawingAnchor != null) { - const { skeDrawing, pageMarginTop, pageMarginLeft } = drawingAnchor; - const { aTop, aLeft } = skeDrawing; - const oldPositionH = oldDrawings[drawingId].docTransform.positionH; - let posOffsetH = aLeft; - - if (oldPositionH.relativeFrom === ObjectRelativeFromH.MARGIN) { - posOffsetH -= pageMarginLeft; - } else if (oldPositionH.relativeFrom === ObjectRelativeFromH.COLUMN) { - posOffsetH -= skeDrawing.columnLeft; - } - - const newPositionH = { - relativeFrom: oldPositionH.relativeFrom, - posOffset: posOffsetH, - }; - - if (oldPositionH.posOffset !== newPositionH.posOffset) { - const action = jsonX.replaceOp(['drawings', drawingId, 'docTransform', 'positionH'], oldPositionH, newPositionH); - - rawActions.push(action!); - } - - const oldPositionV = oldDrawings[drawingId].docTransform.positionV; - let posOffsetV = aTop; - - if (oldPositionV.relativeFrom === ObjectRelativeFromV.PAGE) { - posOffsetV += pageMarginTop; - } else if (oldPositionV.relativeFrom === ObjectRelativeFromV.LINE) { - posOffsetV -= skeDrawing.lineTop; - } else if (oldPositionV.relativeFrom === ObjectRelativeFromV.PARAGRAPH) { - posOffsetV -= skeDrawing.blockAnchorTop; - } - - const newPositionV = { - relativeFrom: oldPositionV.relativeFrom, - posOffset: posOffsetV, - }; - - if (oldPositionV.posOffset !== newPositionV.posOffset) { - const action = jsonX.replaceOp(['drawings', drawingId, 'docTransform', 'positionV'], oldPositionV, newPositionV); - - rawActions.push(action!); - } - } - } - - const doMutation: IMutationInfo = { - id: RichTextEditingMutation.id, - params: { - unitId, - actions: [], - textRanges: null, - }, - }; - - doMutation.params.actions = rawActions.reduce((acc, cur) => { - return JSONX.compose(acc, cur as JSONXActions); - }, null as JSONXActions); - - const result = commandService.syncExecuteCommand< - IRichTextEditingMutationParams, - IRichTextEditingMutationParams - >(doMutation.id, doMutation.params); - - transformer.refreshControls(); - - return Boolean(result); - }, -}; - interface IDist { distT: number; distB: number; @@ -569,99 +328,6 @@ export const UpdateDocDrawingWrapTextCommand: ICommand = { }, }; -export interface IDrawingDocTransform { - drawingId: string; - key: 'size' | 'angle' | 'positionH' | 'positionV'; - value: ISize | number | IObjectPositionH | IObjectPositionV; -} - -export interface IUpdateDrawingDocTransformParams { - unitId: string; - subUnitId: string; - drawings: IDrawingDocTransform[]; -} - -/** - * The command to update drawing position. - */ -export const UpdateDrawingDocTransformCommand: ICommand = { - id: 'doc.command.update-drawing-doc-transform', - - type: CommandType.COMMAND, - - handler: (accessor: IAccessor, params?: IUpdateDrawingDocTransformParams) => { - if (params == null) { - return false; - } - - const commandService = accessor.get(ICommandService); - const univerInstanceService = accessor.get(IUniverInstanceService); - const renderManagerService = accessor.get(IRenderManagerService); - - const { drawings, unitId } = params; - const renderObject = renderManagerService.getRenderUnitById(unitId); - const scene = renderObject?.scene; - if (scene == null) { - return false; - } - - const transformer = scene.getTransformerByCreate(); - - const documentDataModel = univerInstanceService.getUnit(unitId, UniverInstanceType.UNIVER_DOC); - if (documentDataModel == null) { - return false; - } - - const jsonX = JSONX.getInstance(); - const rawActions: JSONXActions = []; - - const { drawings: oldDrawings = {} } = documentDataModel.getSnapshot(); - - // Update drawing layoutType. - for (const drawing of drawings) { - const { drawingId, key, value } = drawing; - - const oldValue = oldDrawings[drawingId].docTransform[key]; - - if (!Tools.diffValue(oldValue, value)) { - const action = jsonX.replaceOp(['drawings', drawingId, 'docTransform', key], oldValue, value); - - rawActions.push(action!); - } - } - - const doMutation: IMutationInfo = { - id: RichTextEditingMutation.id, - params: { - unitId, - actions: [], - textRanges: null, - debounce: true, - }, - }; - - doMutation.params.actions = rawActions.reduce((acc, cur) => { - return JSONX.compose(acc, cur as JSONXActions); - }, null as JSONXActions); - - const result = commandService.syncExecuteCommand< - IRichTextEditingMutationParams, - IRichTextEditingMutationParams - >(doMutation.id, doMutation.params); - - // RichTextEditingMutation recalculates the document skeleton before the - // synchronous command returns. Publish that fresh geometry so the drawing - // manager, renderer, and transformer do not keep the pre-mutation size. - if (accessor.has(DocRefreshDrawingsService)) { - const skeleton = renderObject?.with(DocSkeletonManagerService).getSkeleton() ?? null; - accessor.get(DocRefreshDrawingsService).refreshDrawings(skeleton); - } - transformer.refreshControls(); - - return Boolean(result); - }, -}; - export interface IMoveInlineDrawingParams { unitId: string; subUnitId: string; diff --git a/packages/docs-drawing-ui/src/controllers/__tests__/doc-drawing-notification.controller.spec.ts b/packages/docs-drawing-ui/src/controllers/__tests__/doc-drawing-notification.controller.spec.ts index cf2b5ea7c6..ee7c74a615 100644 --- a/packages/docs-drawing-ui/src/controllers/__tests__/doc-drawing-notification.controller.spec.ts +++ b/packages/docs-drawing-ui/src/controllers/__tests__/doc-drawing-notification.controller.spec.ts @@ -14,8 +14,16 @@ * limitations under the License. */ -import { BooleanNumber, PositionedObjectLayoutType, UndoCommand } from '@univerjs/core'; +import { + BooleanNumber, + ObjectRelativeFromH, + ObjectRelativeFromV, + PositionedObjectLayoutType, + UndoCommand, +} from '@univerjs/core'; import { RichTextEditingMutation } from '@univerjs/docs'; +import { TextWrappingStyle, UpdateDocDrawingWrappingStyleCommand } from '@univerjs/docs-drawing'; +import { DocumentEditArea } from '@univerjs/engine-render'; import { describe, expect, it, vi } from 'vitest'; import { DocDrawingAddRemoveController } from '../doc-drawing-notification.controller'; @@ -23,6 +31,52 @@ function createController() { const beforeHandlers: Array<(command: { id: string; params?: unknown }) => void> = []; const executedHandlers: Array<(command: { id: string; params?: unknown }) => void> = []; const refreshControls = vi.fn(); + const refreshDrawings = vi.fn(); + const drawing = { + unitId: 'doc-1', + subUnitId: 'doc-1', + drawingId: 'drawing-1', + layoutType: PositionedObjectLayoutType.WRAP_NONE, + behindDoc: BooleanNumber.TRUE, + docTransform: { + size: { width: 100, height: 60 }, + angle: 0, + positionH: { relativeFrom: ObjectRelativeFromH.MARGIN, posOffset: 1 }, + positionV: { relativeFrom: ObjectRelativeFromV.PARAGRAPH, posOffset: 2 }, + }, + }; + const skeleton = { + getSkeletonData: () => ({ + pages: [{ + marginTop: 20, + marginLeft: 30, + marginBottom: 20, + pageWidth: 600, + pageHeight: 800, + headerId: '', + footerId: '', + skeDrawings: new Map([['drawing-1', { + drawingId: 'drawing-1', + aLeft: 130, + aTop: 180, + columnLeft: 30, + lineTop: 150, + blockAnchorTop: 140, + drawingOrigin: drawing, + }]]), + skeTables: new Map(), + }], + skeHeaders: new Map(), + skeFooters: new Map(), + }), + }; + const renderObject = { + scene: { getTransformerByCreate: () => ({ refreshControls }) }, + with: () => ({ + getSkeleton: () => skeleton, + getViewModel: () => ({ getEditArea: () => DocumentEditArea.BODY }), + }), + }; const drawingManagerService = { applyJson1: vi.fn(), addNotification: vi.fn(), @@ -44,12 +98,16 @@ function createController() { { getCurrentUnitOfType: vi.fn(() => ({ getUnitId: () => 'doc-1' })), getUnit: vi.fn(() => ({ + getDrawings: () => ({ + 'drawing-1': drawing, + 'drawing-2': { + layoutType: PositionedObjectLayoutType.WRAP_NONE, + behindDoc: BooleanNumber.FALSE, + }, + }), getSnapshot: () => ({ drawings: { - 'drawing-1': { - layoutType: PositionedObjectLayoutType.WRAP_NONE, - behindDoc: BooleanNumber.TRUE, - }, + 'drawing-1': drawing, 'drawing-2': { layoutType: PositionedObjectLayoutType.WRAP_NONE, behindDoc: BooleanNumber.FALSE, @@ -72,12 +130,23 @@ function createController() { drawingManagerService as never, docDrawingService as never, { - getRenderById: vi.fn(() => ({ scene: { getTransformerByCreate: () => ({ refreshControls }) } })), - getRenderUnitById: vi.fn(() => ({ scene: { getTransformerByCreate: () => ({ refreshControls }) } })), - } as never + getRenderById: vi.fn(() => renderObject), + getRenderUnitById: vi.fn(() => renderObject), + } as never, + { refreshDrawings } as never ); - return { controller, beforeHandlers, executedHandlers, drawingManagerService, docDrawingService, refreshControls }; + return { + controller, + beforeHandlers, + executedHandlers, + drawingManagerService, + docDrawingService, + drawing, + refreshControls, + refreshDrawings, + skeleton, + }; } describe('DocDrawingAddRemoveController', () => { @@ -123,7 +192,7 @@ describe('DocDrawingAddRemoveController', () => { actions: ['drawingsOrder', [0, { d: 0 }], [1, { p: 0 }]], }, }); - executedHandlers[1]({ id: UndoCommand.id }); + executedHandlers.forEach((handler) => handler({ id: UndoCommand.id })); expect(drawingManagerService.setDrawingOrder).toHaveBeenCalledWith('doc-1', 'doc-1', ['drawing-1', 'drawing-2']); expect(docDrawingService.setDrawingOrder).toHaveBeenCalledWith('doc-1', 'doc-1', ['drawing-2', 'drawing-1']); @@ -141,4 +210,43 @@ describe('DocDrawingAddRemoveController', () => { controller.dispose(); }); + + it('preserves skeleton position before wrapping changes and refreshes drawings afterward', () => { + const { + controller, + beforeHandlers, + executedHandlers, + drawing, + refreshControls, + refreshDrawings, + skeleton, + } = createController(); + const params = { + unitId: 'doc-1', + subUnitId: 'doc-1', + drawings: [{ drawingId: 'drawing-1' }], + wrappingStyle: TextWrappingStyle.WRAP_SQUARE, + }; + const command = { id: UpdateDocDrawingWrappingStyleCommand.id, params }; + + beforeHandlers.forEach((handler) => handler(command)); + + expect(params.drawings[0]).toMatchObject({ + drawingId: 'drawing-1', + docTransform: { + positionH: { relativeFrom: ObjectRelativeFromH.MARGIN, posOffset: 100 }, + positionV: { relativeFrom: ObjectRelativeFromV.PARAGRAPH, posOffset: 40 }, + }, + }); + expect(drawing.docTransform).toMatchObject({ + positionH: { posOffset: 1 }, + positionV: { posOffset: 2 }, + }); + + executedHandlers.forEach((handler) => handler(command)); + + expect(refreshDrawings).toHaveBeenCalledWith(skeleton); + expect(refreshControls).toHaveBeenCalled(); + controller.dispose(); + }); }); diff --git a/packages/docs-drawing-ui/src/controllers/__tests__/doc-drawing-transformer-update.controller.spec.ts b/packages/docs-drawing-ui/src/controllers/__tests__/doc-drawing-transformer-update.controller.spec.ts index 35629a73df..fa6bb927c9 100644 --- a/packages/docs-drawing-ui/src/controllers/__tests__/doc-drawing-transformer-update.controller.spec.ts +++ b/packages/docs-drawing-ui/src/controllers/__tests__/doc-drawing-transformer-update.controller.spec.ts @@ -15,12 +15,12 @@ */ import { ObjectRelativeFromH, ObjectRelativeFromV, PositionedObjectLayoutType } from '@univerjs/core'; +import { UpdateDrawingDocTransformCommand } from '@univerjs/docs-drawing'; import { Subject } from 'rxjs'; import { describe, expect, it, vi } from 'vitest'; import { IMoveInlineDrawingCommand, ITransformNonInlineDrawingCommand, - UpdateDrawingDocTransformCommand, } from '../../commands/commands/update-doc-drawing.command'; import { DocDrawingTransformerController, getDocsTableCellAnchorContext } from '../doc-drawing-transformer-update.controller'; diff --git a/packages/docs-drawing-ui/src/controllers/__tests__/doc-float-dom.controller.spec.ts b/packages/docs-drawing-ui/src/controllers/__tests__/doc-float-dom.controller.spec.ts index a228bec2e7..b726a71c77 100644 --- a/packages/docs-drawing-ui/src/controllers/__tests__/doc-float-dom.controller.spec.ts +++ b/packages/docs-drawing-ui/src/controllers/__tests__/doc-float-dom.controller.spec.ts @@ -15,10 +15,10 @@ */ import { DrawingTypeEnum } from '@univerjs/core'; +import { InsertDocDrawingCommand } from '@univerjs/docs-drawing'; import { Rect } from '@univerjs/engine-render'; import { BehaviorSubject, Subject } from 'rxjs'; import { describe, expect, it, vi } from 'vitest'; -import { InsertDocDrawingCommand } from '../../commands/commands/insert-doc-drawing.command'; import { calcDocFloatDomPositionByRect, DocFloatDomController } from '../doc-float-dom.controller'; function createScene() { diff --git a/packages/docs-drawing-ui/src/controllers/doc-drawing-notification.controller.ts b/packages/docs-drawing-ui/src/controllers/doc-drawing-notification.controller.ts index a6257b508e..777af52ed0 100644 --- a/packages/docs-drawing-ui/src/controllers/doc-drawing-notification.controller.ts +++ b/packages/docs-drawing-ui/src/controllers/doc-drawing-notification.controller.ts @@ -18,21 +18,32 @@ import type { DocumentDataModel, ICommandInfo, IDrawingSearch, JSONXActions, Nullable } from '@univerjs/core'; import type { IRichTextEditingMutationParams } from '@univerjs/docs'; -import type { IDocDrawing } from '@univerjs/docs-drawing'; +import type { IDocDrawing, IUpdateDocDrawingWrappingStyleParams, IUpdateDrawingDocTransformCommandParams } from '@univerjs/docs-drawing'; import type { IDrawingJsonUndo1, IDrawingMapItemData, IDrawingOrderMapParam } from '@univerjs/drawing'; +import type { IDocumentSkeletonDrawing, IDocumentSkeletonHeaderFooter, IDocumentSkeletonPage } from '@univerjs/engine-render'; import { Disposable, ICommandService, + Inject, IUniverInstanceService, JSONX, + ObjectRelativeFromH, + ObjectRelativeFromV, RedoCommand, UndoCommand, UniverInstanceType, } from '@univerjs/core'; -import { RichTextEditingMutation } from '@univerjs/docs'; -import { getDocDrawingRenderOrder, IDocDrawingService } from '@univerjs/docs-drawing'; +import { DocSkeletonManagerService, RichTextEditingMutation } from '@univerjs/docs'; +import { + getDocDrawingRenderOrder, + IDocDrawingService, + TextWrappingStyle, + UpdateDocDrawingWrappingStyleCommand, + UpdateDrawingDocTransformCommand, +} from '@univerjs/docs-drawing'; import { IDrawingManagerService } from '@univerjs/drawing'; -import { IRenderManagerService } from '@univerjs/engine-render'; +import { DocumentEditArea, IRenderManagerService } from '@univerjs/engine-render'; +import { DocRefreshDrawingsService } from '../services/doc-refresh-drawings.service'; interface IAddOrRemoveDrawing { type: 'add' | 'remove'; @@ -40,6 +51,37 @@ interface IAddOrRemoveDrawing { drawing?: IDocDrawing; } +interface IDrawingAnchorInPage { + skeDrawing: IDocumentSkeletonDrawing; + pageMarginTop: number; + pageMarginLeft: number; +} + +function findDrawingAnchorInPage( + page: IDocumentSkeletonPage | IDocumentSkeletonHeaderFooter, + drawingId: string, + pageMarginTop: number, + pageMarginLeft: number +): IDrawingAnchorInPage | null { + const skeDrawing = page.skeDrawings.get(drawingId); + if (skeDrawing) { + return { skeDrawing, pageMarginTop, pageMarginLeft }; + } + + for (const table of page.skeTables.values()) { + for (const row of table.rows) { + for (const cell of row.cells) { + const cellAnchor = findDrawingAnchorInPage(cell, drawingId, cell.marginTop, cell.marginLeft); + if (cellAnchor) { + return cellAnchor; + } + } + } + } + + return null; +} + // Check whether drawings are added or deleted from the mutation and obtain the drawing ID. // eslint-disable-next-line complexity function getAddOrRemoveDrawings(actions: JSONXActions): Nullable { @@ -152,7 +194,8 @@ export class DocDrawingAddRemoveController extends Disposable { @ICommandService private readonly _commandService: ICommandService, @IDrawingManagerService private readonly _drawingManagerService: IDrawingManagerService, @IDocDrawingService private readonly _docDrawingService: IDocDrawingService, - @IRenderManagerService private readonly _renderManagerService: IRenderManagerService + @IRenderManagerService private readonly _renderManagerService: IRenderManagerService, + @Inject(DocRefreshDrawingsService) private readonly _docRefreshDrawingsService: DocRefreshDrawingsService ) { super(); @@ -163,6 +206,7 @@ export class DocDrawingAddRemoveController extends Disposable { this._commandExecutedListener(); } + // eslint-disable-next-line max-lines-per-function private _commandExecutedListener() { this.disposeWithMe( this._commandService.beforeCommandExecuted((command: ICommandInfo) => { @@ -190,6 +234,16 @@ export class DocDrawingAddRemoveController extends Disposable { }) ); + this.disposeWithMe( + this._commandService.beforeCommandExecuted((command: ICommandInfo) => { + if (command.id !== UpdateDocDrawingWrappingStyleCommand.id) { + return; + } + + this._preserveWrappingStylePosition(command.params as IUpdateDocDrawingWrappingStyleParams); + }) + ); + this.disposeWithMe( this._commandService.onCommandExecuted((command: ICommandInfo) => { if (command.id !== RichTextEditingMutation.id) { @@ -211,6 +265,27 @@ export class DocDrawingAddRemoveController extends Disposable { }) ); + this.disposeWithMe( + this._commandService.onCommandExecuted((command: ICommandInfo) => { + if ( + command.id !== UpdateDrawingDocTransformCommand.id && + command.id !== UpdateDocDrawingWrappingStyleCommand.id + ) { + return; + } + + const { unitId } = command.params as IUpdateDrawingDocTransformCommandParams | IUpdateDocDrawingWrappingStyleParams; + const renderObject = this._renderManagerService.getRenderById(unitId); + const scene = renderObject?.scene; + if (renderObject == null || scene == null) { + return; + } + + this._docRefreshDrawingsService.refreshDrawings(renderObject.with(DocSkeletonManagerService).getSkeleton()); + scene.getTransformerByCreate().refreshControls(); + }) + ); + this.disposeWithMe( this._commandService.onCommandExecuted((command: ICommandInfo) => { if (command.id !== UndoCommand.id && command.id !== RedoCommand.id) { @@ -236,6 +311,99 @@ export class DocDrawingAddRemoveController extends Disposable { ); } + // eslint-disable-next-line max-lines-per-function + private _preserveWrappingStylePosition(params: IUpdateDocDrawingWrappingStyleParams): void { + if (params.wrappingStyle === TextWrappingStyle.INLINE) { + return; + } + + const { unitId } = params; + const documentDataModel = this._univerInstanceService.getUnit( + unitId, + UniverInstanceType.UNIVER_DOC + ); + const renderObject = this._renderManagerService.getRenderById(unitId); + const skeletonManager = renderObject?.with(DocSkeletonManagerService); + const skeletonData = skeletonManager?.getSkeleton().getSkeletonData(); + const viewModel = skeletonManager?.getViewModel(); + if (!documentDataModel || !skeletonData || !viewModel) { + return; + } + + const editArea = viewModel.getEditArea(); + const { pages, skeHeaders, skeFooters } = skeletonData; + const oldDrawings = documentDataModel.getDrawings() ?? {}; + + params.drawings = params.drawings.map((drawing) => { + const oldDrawing = oldDrawings[drawing.drawingId] as IDocDrawing | undefined; + if (!oldDrawing) { + return drawing; + } + + let drawingAnchor: IDrawingAnchorInPage | null = null; + for (const page of pages) { + const { headerId, footerId, marginTop, marginLeft, marginBottom, pageWidth, pageHeight } = page; + if (editArea === DocumentEditArea.HEADER) { + const header = skeHeaders.get(headerId)?.get(pageWidth); + if (header) { + drawingAnchor = findDrawingAnchorInPage(header, drawing.drawingId, header.marginTop, marginLeft); + } + } else if (editArea === DocumentEditArea.FOOTER) { + const footer = skeFooters.get(footerId)?.get(pageWidth); + if (footer) { + drawingAnchor = findDrawingAnchorInPage( + footer, + drawing.drawingId, + pageHeight - marginBottom + footer.marginTop, + marginLeft + ); + } + } else { + drawingAnchor = findDrawingAnchorInPage(page, drawing.drawingId, marginTop, marginLeft); + } + + if (drawingAnchor) { + break; + } + } + + if (!drawingAnchor) { + return drawing; + } + + const { skeDrawing, pageMarginTop, pageMarginLeft } = drawingAnchor; + const oldPositionH = oldDrawing.docTransform.positionH; + const oldPositionV = oldDrawing.docTransform.positionV; + let posOffsetH = skeDrawing.aLeft; + let posOffsetV = skeDrawing.aTop; + + if (oldPositionH.relativeFrom === ObjectRelativeFromH.MARGIN) { + posOffsetH -= pageMarginLeft; + } else if (oldPositionH.relativeFrom === ObjectRelativeFromH.COLUMN) { + posOffsetH -= skeDrawing.columnLeft; + } + + if (oldPositionV.relativeFrom === ObjectRelativeFromV.PAGE) { + posOffsetV += pageMarginTop; + } else if (oldPositionV.relativeFrom === ObjectRelativeFromV.LINE) { + posOffsetV -= skeDrawing.lineTop; + } else if (oldPositionV.relativeFrom === ObjectRelativeFromV.PARAGRAPH) { + posOffsetV -= skeDrawing.blockAnchorTop; + } + + return { + ...oldDrawing, + ...drawing, + docTransform: { + ...oldDrawing.docTransform, + ...drawing.docTransform, + positionH: { relativeFrom: oldPositionH.relativeFrom, posOffset: posOffsetH }, + positionV: { relativeFrom: oldPositionV.relativeFrom, posOffset: posOffsetV }, + }, + }; + }); + } + private _addDrawings(unitId: string, drawings: IDocDrawing[]) { const drawingManagerService = this._drawingManagerService; const docDrawingService = this._docDrawingService; diff --git a/packages/docs-drawing-ui/src/controllers/doc-drawing-transformer-update.controller.ts b/packages/docs-drawing-ui/src/controllers/doc-drawing-transformer-update.controller.ts index dd0f375daa..8ae49210c0 100644 --- a/packages/docs-drawing-ui/src/controllers/doc-drawing-transformer-update.controller.ts +++ b/packages/docs-drawing-ui/src/controllers/doc-drawing-transformer-update.controller.ts @@ -15,8 +15,8 @@ */ import type { DocumentDataModel, IDocDrawingBase, IDocDrawingPosition, Nullable } from '@univerjs/core'; +import type { IDrawingDocTransform, IUpdateDrawingDocTransformCommandParams } from '@univerjs/docs-drawing'; import type { BaseObject, Documents, IDocumentSkeletonGlyph, IDocumentSkeletonPage, IDocumentSkeletonRow, IDocumentSkeletonTable, Image, IPoint, Viewport } from '@univerjs/engine-render'; -import type { IDrawingDocTransform } from '../commands/commands/update-doc-drawing.command'; import { BooleanNumber, COLORS, @@ -33,10 +33,11 @@ import { UniverInstanceType, } from '@univerjs/core'; import { DocSkeletonManagerService } from '@univerjs/docs'; +import { UpdateDrawingDocTransformCommand } from '@univerjs/docs-drawing'; import { DocSelectionRenderService, getAnchorBounding, getDocObject, getOneTextSelectionRange, NodePositionConvertToCursor, TEXT_RANGE_LAYER_INDEX } from '@univerjs/docs-ui'; import { IDrawingManagerService } from '@univerjs/drawing'; import { DocumentSkeletonPageType, getColor, IRenderManagerService, Liquid, PageLayoutType, Rect, Vector2 } from '@univerjs/engine-render'; -import { IMoveInlineDrawingCommand, ITransformNonInlineDrawingCommand, UpdateDrawingDocTransformCommand } from '../commands/commands/update-doc-drawing.command'; +import { IMoveInlineDrawingCommand, ITransformNonInlineDrawingCommand } from '../commands/commands/update-doc-drawing.command'; import { getDocsTableCellDrawingOffset } from './render-controllers/doc-drawing-transform-update.controller'; const INLINE_DRAWING_ANCHOR_KEY_PREFIX = '__InlineDrawingAnchor__'; @@ -368,7 +369,7 @@ export class DocDrawingTransformerController extends Disposable { } if (drawings.length > 0 && unitId && subUnitId) { - this._commandService.executeCommand(UpdateDrawingDocTransformCommand.id, { + this._commandService.executeCommand(UpdateDrawingDocTransformCommand.id, { unitId, subUnitId, drawings, @@ -718,7 +719,7 @@ export class DocDrawingTransformerController extends Disposable { } if (drawings.length > 0 && unitId && subUnitId) { - this._commandService.executeCommand(UpdateDrawingDocTransformCommand.id, { + this._commandService.executeCommand(UpdateDrawingDocTransformCommand.id, { unitId, subUnitId, drawings, diff --git a/packages/docs-drawing-ui/src/controllers/doc-float-dom.controller.ts b/packages/docs-drawing-ui/src/controllers/doc-float-dom.controller.ts index d82dc2db7d..f328544660 100644 --- a/packages/docs-drawing-ui/src/controllers/doc-float-dom.controller.ts +++ b/packages/docs-drawing-ui/src/controllers/doc-float-dom.controller.ts @@ -15,12 +15,11 @@ */ import type { DocumentDataModel, IDisposable, IDrawingSearch, ITransformState, Nullable } from '@univerjs/core'; -import type { IDocFloatDom } from '@univerjs/docs-drawing'; +import type { IDocFloatDom, IInsertDocDrawingCommandParams } from '@univerjs/docs-drawing'; import type { ISetDocZoomRatioOperationParams } from '@univerjs/docs-ui'; import type { IDocFloatDomDataBase } from '@univerjs/drawing'; import type { IBoundRectNoAngle, IDocsCustomBlockRenderViewport, IRender, Rect, Scene } from '@univerjs/engine-render'; import type { IFloatDomLayout } from '@univerjs/ui'; -import type { IInsertDrawingCommandParams } from '../commands/commands/interfaces'; import { Disposable, DisposableCollection, @@ -37,13 +36,13 @@ import { UniverInstanceType, } from '@univerjs/core'; import { docDrawingPositionToTransform, DocSkeletonManagerService } from '@univerjs/docs'; +import { InsertDocDrawingCommand } from '@univerjs/docs-drawing'; import { SetDocZoomRatioOperation, VIEWPORT_KEY } from '@univerjs/docs-ui'; import { IDrawingManagerService } from '@univerjs/drawing'; import { DrawingRenderService } from '@univerjs/drawing-ui'; import { CURSOR_TYPE, IRenderManagerService } from '@univerjs/engine-render'; import { CanvasFloatDomService } from '@univerjs/ui'; import { BehaviorSubject, map, of, switchMap } from 'rxjs'; -import { InsertDocDrawingCommand } from '../commands/commands/insert-doc-drawing.command'; export function calcDocFloatDomPositionByRect( rect: IBoundRectNoAngle, @@ -444,7 +443,7 @@ export class DocFloatDomController extends Disposable { angle: 0, }; const drawingId = opts.drawingId ?? generateRandomId(); - const params: IInsertDrawingCommandParams = { + const params: IInsertDocDrawingCommandParams = { unitId: currentDoc.getUnitId(), drawings: [ { @@ -461,7 +460,7 @@ export class DocFloatDomController extends Disposable { }, ], }; - this._commandService.syncExecuteCommand(InsertDocDrawingCommand.id, params); + this._commandService.syncExecuteCommand(InsertDocDrawingCommand.id, params); return drawingId; } diff --git a/packages/docs-drawing-ui/src/controllers/render-controllers/__tests__/doc-drawing-update.render-controller.spec.ts b/packages/docs-drawing-ui/src/controllers/render-controllers/__tests__/doc-drawing-update.render-controller.spec.ts index 3a94e6559e..2bf4010def 100644 --- a/packages/docs-drawing-ui/src/controllers/render-controllers/__tests__/doc-drawing-update.render-controller.spec.ts +++ b/packages/docs-drawing-ui/src/controllers/render-controllers/__tests__/doc-drawing-update.render-controller.spec.ts @@ -16,11 +16,11 @@ import { BooleanNumber, FOCUSING_COMMON_DRAWINGS } from '@univerjs/core'; import { RichTextEditingMutation } from '@univerjs/docs'; +import { SetDocDrawingArrangeCommand } from '@univerjs/docs-drawing'; import { DocumentEditArea } from '@univerjs/engine-render'; import { Subject } from 'rxjs'; import { describe, expect, it, vi } from 'vitest'; import { GroupDocDrawingCommand } from '../../../commands/commands/group-doc-drawing.command'; -import { SetDocDrawingArrangeCommand } from '../../../commands/commands/set-drawing-arrange.command'; import { UngroupDocDrawingCommand } from '../../../commands/commands/ungroup-doc-drawing.command'; import { DocDrawingUpdateRenderController } from '../doc-drawing-update.render-controller'; diff --git a/packages/docs-drawing-ui/src/controllers/render-controllers/doc-drawing-update.render-controller.ts b/packages/docs-drawing-ui/src/controllers/render-controllers/doc-drawing-update.render-controller.ts index 036801d229..c8e0a9c583 100644 --- a/packages/docs-drawing-ui/src/controllers/render-controllers/doc-drawing-update.render-controller.ts +++ b/packages/docs-drawing-ui/src/controllers/render-controllers/doc-drawing-update.render-controller.ts @@ -16,10 +16,8 @@ import type { DocumentDataModel, ICommandInfo, IDocDrawingPosition, IDrawingParam, IImageIoServiceParam, Nullable } from '@univerjs/core'; import type { IRichTextEditingMutationParams } from '@univerjs/docs'; -import type { IDocDrawing } from '@univerjs/docs-drawing'; +import type { IDocDrawing, IInsertDocDrawingCommandParams, ISetDocDrawingArrangeCommandParams } from '@univerjs/docs-drawing'; import type { Documents, Image, IRenderContext, IRenderModule } from '@univerjs/engine-render'; -import type { IInsertDrawingCommandParams } from '../../commands/commands/interfaces'; -import type { ISetDrawingArrangeCommandParams } from '../../commands/commands/set-drawing-arrange.command'; import type { LocaleKey } from '../../locale/types'; import { BooleanNumber, @@ -37,7 +35,7 @@ import { } from '@univerjs/core'; import { MessageType } from '@univerjs/design'; import { buildDocTransform, docDrawingPositionToTransform, DocSelectionManagerService, DocSkeletonManagerService, RichTextEditingMutation } from '@univerjs/docs'; -import { IDocDrawingService } from '@univerjs/docs-drawing'; +import { IDocDrawingService, InsertDocDrawingCommand, SetDocDrawingArrangeCommand } from '@univerjs/docs-drawing'; import { DocSelectionRenderService } from '@univerjs/docs-ui'; import { DRAWING_IMAGE_ALLOW_IMAGE_LIST, @@ -53,8 +51,6 @@ 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 { InsertDocDrawingCommand } from '../../commands/commands/insert-doc-drawing.command'; -import { SetDocDrawingArrangeCommand } from '../../commands/commands/set-drawing-arrange.command'; import { UngroupDocDrawingCommand } from '../../commands/commands/ungroup-doc-drawing.command'; import { DocRefreshDrawingsService } from '../../services/doc-refresh-drawings.service'; @@ -211,10 +207,10 @@ export class DocDrawingUpdateRenderController extends Disposable implements IRen docDrawingParams.push(docDrawingParam); } - this._commandService.executeCommand(InsertDocDrawingCommand.id, { + this._commandService.executeCommand(InsertDocDrawingCommand.id, { unitId, drawings: docDrawingParams, - } as IInsertDrawingCommandParams); + }); } private _isInsertInHeaderFooter() { @@ -262,12 +258,12 @@ export class DocDrawingUpdateRenderController extends Disposable implements IRen this._drawingManagerService.featurePluginOrderUpdate$.subscribe((params) => { const { unitId, subUnitId, drawingIds, arrangeType } = params; - this._commandService.executeCommand(SetDocDrawingArrangeCommand.id, { + this._commandService.executeCommand(SetDocDrawingArrangeCommand.id, { unitId, subUnitId, drawingIds, arrangeType, - } as ISetDrawingArrangeCommandParams); + }); }) ); } diff --git a/packages/docs-drawing-ui/src/controllers/ui.controller.ts b/packages/docs-drawing-ui/src/controllers/ui.controller.ts index 7f45ebca43..ca50e45fcb 100644 --- a/packages/docs-drawing-ui/src/controllers/ui.controller.ts +++ b/packages/docs-drawing-ui/src/controllers/ui.controller.ts @@ -18,20 +18,15 @@ import { Disposable, ICommandService } from '@univerjs/core'; import { IMenuManagerService, IShortcutService } from '@univerjs/ui'; import { DeleteDocDrawingsCommand } from '../commands/commands/delete-doc-drawing.command'; import { GroupDocDrawingCommand } from '../commands/commands/group-doc-drawing.command'; -import { InsertDocDrawingCommand } from '../commands/commands/insert-doc-drawing.command'; import { InsertDocImageCommand } from '../commands/commands/insert-image.command'; import { InsertDocEllipseShapeCommand, InsertDocRectangleShapeCommand } from '../commands/commands/insert-shape.command'; import { MoveDocDrawingsCommand } from '../commands/commands/move-drawings.command'; -import { RemoveDocDrawingCommand } from '../commands/commands/remove-doc-drawing.command'; -import { SetDocDrawingArrangeCommand } from '../commands/commands/set-drawing-arrange.command'; import { UngroupDocDrawingCommand } from '../commands/commands/ungroup-doc-drawing.command'; import { IMoveInlineDrawingCommand, ITransformNonInlineDrawingCommand, UpdateDocDrawingDistanceCommand, - UpdateDocDrawingWrappingStyleCommand, UpdateDocDrawingWrapTextCommand, - UpdateDrawingDocTransformCommand, } from '../commands/commands/update-doc-drawing.command'; import { ClearDocDrawingTransformerOperation } from '../commands/operations/clear-drawing-transformer.operation'; import { EditDocDrawingOperation } from '../commands/operations/edit-doc-drawing.operation'; @@ -68,14 +63,10 @@ export class DocDrawingUIController extends Disposable { InsertDocImageCommand, InsertDocRectangleShapeCommand, InsertDocEllipseShapeCommand, - InsertDocDrawingCommand, - UpdateDocDrawingWrappingStyleCommand, UpdateDocDrawingDistanceCommand, UpdateDocDrawingWrapTextCommand, - UpdateDrawingDocTransformCommand, IMoveInlineDrawingCommand, ITransformNonInlineDrawingCommand, - RemoveDocDrawingCommand, SidebarDocDrawingOperation, ClearDocDrawingTransformerOperation, EditDocDrawingOperation, @@ -83,7 +74,6 @@ export class DocDrawingUIController extends Disposable { UngroupDocDrawingCommand, MoveDocDrawingsCommand, DeleteDocDrawingsCommand, - SetDocDrawingArrangeCommand, ].forEach((command) => this.disposeWithMe(this._commandService.registerCommand(command))); } diff --git a/packages/docs-drawing-ui/src/index.ts b/packages/docs-drawing-ui/src/index.ts index 7a58540e74..2359fcb308 100644 --- a/packages/docs-drawing-ui/src/index.ts +++ b/packages/docs-drawing-ui/src/index.ts @@ -18,14 +18,10 @@ import './global.css'; export { DeleteDocDrawingsCommand } from './commands/commands/delete-doc-drawing.command'; export { GroupDocDrawingCommand } from './commands/commands/group-doc-drawing.command'; -export { InsertDocDrawingCommand } from './commands/commands/insert-doc-drawing.command'; export { InsertDocImageCommand } from './commands/commands/insert-image.command'; export { InsertDocEllipseShapeCommand, InsertDocRectangleShapeCommand } from './commands/commands/insert-shape.command'; export { MoveDocDrawingsCommand } from './commands/commands/move-drawings.command'; -export { RemoveDocDrawingCommand } from './commands/commands/remove-doc-drawing.command'; -export { SetDocDrawingArrangeCommand } from './commands/commands/set-drawing-arrange.command'; export { UngroupDocDrawingCommand } from './commands/commands/ungroup-doc-drawing.command'; -export { UpdateDrawingDocTransformCommand } from './commands/commands/update-doc-drawing.command'; export { ClearDocDrawingTransformerOperation } from './commands/operations/clear-drawing-transformer.operation'; export { EditDocDrawingOperation } from './commands/operations/edit-doc-drawing.operation'; export { SidebarDocDrawingOperation } from './commands/operations/open-drawing-panel.operation'; diff --git a/packages/docs-drawing-ui/src/menu/drawing-popup-menu.controller.ts b/packages/docs-drawing-ui/src/menu/drawing-popup-menu.controller.ts index 62e2559859..f2fe5fad49 100644 --- a/packages/docs-drawing-ui/src/menu/drawing-popup-menu.controller.ts +++ b/packages/docs-drawing-ui/src/menu/drawing-popup-menu.controller.ts @@ -28,7 +28,7 @@ import { RxDisposable, UniverInstanceType, } from '@univerjs/core'; -import { IDocDrawingAdapterService } from '@univerjs/docs-drawing'; +import { IDocDrawingAdapterService, RemoveDocDrawingCommand } from '@univerjs/docs-drawing'; import { DocCanvasPopManagerService } from '@univerjs/docs-ui'; import { IDrawingManagerService } from '@univerjs/drawing'; import { @@ -39,7 +39,6 @@ import { } from '@univerjs/drawing-ui'; import { IRenderManagerService } from '@univerjs/engine-render'; import { takeUntil } from 'rxjs'; -import { RemoveDocDrawingCommand } from '../commands/commands/remove-doc-drawing.command'; import { EditDocDrawingOperation } from '../commands/operations/edit-doc-drawing.operation'; import { SidebarDocDrawingOperation } from '../commands/operations/open-drawing-panel.operation'; import { DocDrawingFloatingToolbarAdapterService } from '../services/doc-drawing-floating-toolbar-adapter.service'; diff --git a/packages/docs-drawing-ui/src/views/doc-image-panel/DocDrawingPosition.tsx b/packages/docs-drawing-ui/src/views/doc-image-panel/DocDrawingPosition.tsx index d62776f163..e542a43089 100644 --- a/packages/docs-drawing-ui/src/views/doc-image-panel/DocDrawingPosition.tsx +++ b/packages/docs-drawing-ui/src/views/doc-image-panel/DocDrawingPosition.tsx @@ -15,7 +15,7 @@ */ import type { DocumentDataModel, ICommandInfo, IDrawingParam, IObjectPositionH, IObjectPositionV, Nullable } from '@univerjs/core'; -import type { IDocDrawing } from '@univerjs/docs-drawing'; +import type { IDocDrawing, IUpdateDrawingDocTransformCommandParams } from '@univerjs/docs-drawing'; import type { IDocumentSkeletonDrawing } from '@univerjs/engine-render'; import type { LocaleKey } from '../../locale/types'; import { @@ -30,12 +30,12 @@ import { } from '@univerjs/core'; import { Checkbox, clsx, InputNumber, Select } from '@univerjs/design'; import { DocSkeletonManagerService, RichTextEditingMutation } from '@univerjs/docs'; +import { UpdateDrawingDocTransformCommand } from '@univerjs/docs-drawing'; import { DocSelectionRenderService } from '@univerjs/docs-ui'; import { IDrawingManagerService } from '@univerjs/drawing'; import { IRenderManagerService } from '@univerjs/engine-render'; import { useDependency } from '@univerjs/ui'; import { useEffect, useState } from 'react'; -import { UpdateDrawingDocTransformCommand } from '../../commands/commands/update-doc-drawing.command'; const MIN_OFFSET = -1000; const MAX_OFFSET = 1000; @@ -135,7 +135,7 @@ export const DocDrawingPosition = (props: IDocDrawingPositionProps) => { }; }); - commandService.executeCommand(UpdateDrawingDocTransformCommand.id, { + commandService.executeCommand(UpdateDrawingDocTransformCommand.id, { unitId: focusDrawings[0].unitId, subUnitId: focusDrawings[0].unitId, drawings: drawings.map((drawing) => ({ diff --git a/packages/docs-drawing-ui/src/views/doc-image-panel/DocDrawingTextWrap.tsx b/packages/docs-drawing-ui/src/views/doc-image-panel/DocDrawingTextWrap.tsx index 6968b19fde..206854aeb4 100644 --- a/packages/docs-drawing-ui/src/views/doc-image-panel/DocDrawingTextWrap.tsx +++ b/packages/docs-drawing-ui/src/views/doc-image-panel/DocDrawingTextWrap.tsx @@ -28,14 +28,13 @@ import { } from '@univerjs/core'; import { clsx, InputNumber, Radio, RadioGroup } from '@univerjs/design'; import { RichTextEditingMutation } from '@univerjs/docs'; +import { TextWrappingStyle, UpdateDocDrawingWrappingStyleCommand } from '@univerjs/docs-drawing'; import { IDrawingManagerService } from '@univerjs/drawing'; import { IRenderManagerService } from '@univerjs/engine-render'; import { useDependency } from '@univerjs/ui'; import { useEffect, useState } from 'react'; import { - TextWrappingStyle, UpdateDocDrawingDistanceCommand, - UpdateDocDrawingWrappingStyleCommand, UpdateDocDrawingWrapTextCommand, } from '../../commands/commands/update-doc-drawing.command'; diff --git a/packages/docs-drawing-ui/src/views/doc-image-panel/__tests__/DocDrawingPosition.spec.tsx b/packages/docs-drawing-ui/src/views/doc-image-panel/__tests__/DocDrawingPosition.spec.tsx index b4d20b53c7..2963d2bbfa 100644 --- a/packages/docs-drawing-ui/src/views/doc-image-panel/__tests__/DocDrawingPosition.spec.tsx +++ b/packages/docs-drawing-ui/src/views/doc-image-panel/__tests__/DocDrawingPosition.spec.tsx @@ -37,7 +37,6 @@ import { act } from 'react'; import { createRoot } from 'react-dom/client'; import { afterEach, describe, expect, it } from 'vitest'; import { createDocUiTestBed } from '../../../__tests__/create-doc-ui-test-bed'; -import { UpdateDrawingDocTransformCommand } from '../../../commands/commands/update-doc-drawing.command'; import locale from '../../../locale/en-US'; import { DocDrawingPosition } from '../DocDrawingPosition'; @@ -239,7 +238,6 @@ function createPositionTestBed() { const commandService = injector.get(ICommandService); [ - UpdateDrawingDocTransformCommand, RichTextEditingMutation as unknown as ICommand, ].forEach((command) => commandService.registerCommand(command)); diff --git a/packages/docs-drawing-ui/src/views/doc-image-panel/__tests__/DocDrawingTextWrap.spec.tsx b/packages/docs-drawing-ui/src/views/doc-image-panel/__tests__/DocDrawingTextWrap.spec.tsx index 9269ae64e3..1109887e62 100644 --- a/packages/docs-drawing-ui/src/views/doc-image-panel/__tests__/DocDrawingTextWrap.spec.tsx +++ b/packages/docs-drawing-ui/src/views/doc-image-panel/__tests__/DocDrawingTextWrap.spec.tsx @@ -40,10 +40,11 @@ import { afterEach, describe, expect, it } from 'vitest'; import { createDocUiTestBed } from '../../../__tests__/create-doc-ui-test-bed'; import { UpdateDocDrawingDistanceCommand, - UpdateDocDrawingWrappingStyleCommand, UpdateDocDrawingWrapTextCommand, } from '../../../commands/commands/update-doc-drawing.command'; +import { DocDrawingAddRemoveController } from '../../../controllers/doc-drawing-notification.controller'; import locale from '../../../locale/en-US'; +import { DocRefreshDrawingsService } from '../../../services/doc-refresh-drawings.service'; import { DocDrawingTextWrap } from '../DocDrawingTextWrap'; (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true; @@ -215,12 +216,13 @@ function createPanelTestBed() { injector.add([DocDrawingService]); injector.add([IDocDrawingService, { useClass: DocDrawingService }]); injector.add([IDrawingManagerService, { useClass: DrawingManagerService }]); + injector.add([DocRefreshDrawingsService]); injector.add([DocDrawingController]); + injector.add([DocDrawingAddRemoveController]); const commandService = injector.get(ICommandService); [ UpdateDocDrawingDistanceCommand, - UpdateDocDrawingWrappingStyleCommand, UpdateDocDrawingWrapTextCommand, RichTextEditingMutation as unknown as ICommand, ].forEach((command) => commandService.registerCommand(command)); @@ -231,6 +233,7 @@ function createPanelTestBed() { injector.get(LocaleService).setLocale(LocaleType.EN_US); injector.get(DocDrawingController).loadDrawingDataForUnit(UNIT_ID); + injector.get(DocDrawingAddRemoveController); injector.get(IDrawingManagerService).focusDrawing([{ unitId: UNIT_ID, subUnitId: UNIT_ID, diff --git a/packages/docs-drawing/package.json b/packages/docs-drawing/package.json index 3461102853..09523a3477 100644 --- a/packages/docs-drawing/package.json +++ b/packages/docs-drawing/package.json @@ -26,7 +26,8 @@ ], "exports": { ".": "./src/index.ts", - "./*": "./src/*" + "./*": "./src/*", + "./facade": "./src/facade/index.ts" }, "main": "./src/index.ts", "types": "./lib/types/index.d.ts", @@ -45,6 +46,16 @@ "require": "./lib/cjs/*", "types": "./lib/types/index.d.ts" }, + "./facade": { + "import": "./lib/es/facade.js", + "require": "./lib/cjs/facade.js", + "types": "./lib/types/facade/index.d.ts" + }, + "./lib/facade": { + "import": "./lib/es/facade.js", + "require": "./lib/cjs/facade.js", + "types": "./lib/types/facade/index.d.ts" + }, "./lib/*": "./lib/*" } }, @@ -65,10 +76,12 @@ }, "dependencies": { "@univerjs/core": "workspace:*", + "@univerjs/docs": "workspace:*", "@univerjs/drawing": "workspace:*" }, "devDependencies": { "@univerjs-infra/shared": "workspace:*", + "@univerjs/engine-render": "workspace:*", "typescript": "^6.0.3", "vitest": "^4.1.10" } diff --git a/packages/docs-drawing/src/commands/commands/__tests__/update-doc-drawing-wrapping-style.command.spec.ts b/packages/docs-drawing/src/commands/commands/__tests__/update-doc-drawing-wrapping-style.command.spec.ts new file mode 100644 index 0000000000..a5b008699b --- /dev/null +++ b/packages/docs-drawing/src/commands/commands/__tests__/update-doc-drawing-wrapping-style.command.spec.ts @@ -0,0 +1,110 @@ +/** + * 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, IDocumentData } from '@univerjs/core'; +import type { IUpdateDocDrawingWrappingStyleParams } from '@univerjs/docs-drawing'; +import { + BooleanNumber, + ICommandService, + ImageSourceType, + IUniverInstanceService, + ObjectRelativeFromH, + ObjectRelativeFromV, + PositionedObjectLayoutType, + UniverInstanceType, +} from '@univerjs/core'; +import { TextWrappingStyle, UpdateDocDrawingWrappingStyleCommand } from '@univerjs/docs-drawing'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { createFacadeTestBed } from '../../../facade/__tests__/create-test-bed'; + +class MockImage { + width = 800; + height = 400; + onload: (() => void) | null = null; + onerror: (() => void) | null = null; + + get src(): string { + return ''; + } + + set src(_value: string) { + queueMicrotask(() => this.onload?.()); + } +} + +describe('UpdateDocDrawingWrappingStyleCommand', () => { + let testBed: ReturnType; + + beforeEach(() => { + vi.stubGlobal('Image', MockImage); + testBed = createFacadeTestBed(); + }); + + afterEach(() => { + testBed.univer.dispose(); + vi.unstubAllGlobals(); + }); + + it('updates the requested document without render services or current-unit dependence', async () => { + const image = await testBed.document.insertImage({ + source: 'data:image/png;base64,image', + imageSourceType: ImageSourceType.BASE64, + width: 160, + height: 90, + textRange: { + startOffset: 3, + endOffset: 3, + collapsed: true, + segmentId: '', + }, + }); + const positionH = { relativeFrom: ObjectRelativeFromH.MARGIN, posOffset: 80 }; + const positionV = { relativeFrom: ObjectRelativeFromV.PAGE, posOffset: 120 }; + const drawing = image!.getImageData()!; + const otherDocument = testBed.univer.createUnit( + UniverInstanceType.UNIVER_DOC, + { + id: 'other-doc', + documentStyle: {}, + body: { dataStream: '\r\n', paragraphs: [{ startIndex: 0, paragraphId: 'other-paragraph' }], customBlocks: [] }, + drawings: {}, + drawingsOrder: [], + } + ); + testBed.injector.get(IUniverInstanceService).focusUnit(otherDocument.getUnitId()); + + const result = testBed.injector.get(ICommandService).syncExecuteCommand( + UpdateDocDrawingWrappingStyleCommand.id, + { + unitId: 'test-doc', + subUnitId: 'test-doc', + drawings: [{ + ...drawing, + docTransform: { ...drawing.docTransform, positionH, positionV }, + }], + wrappingStyle: TextWrappingStyle.BEHIND_TEXT, + } + ); + + expect(result).toBe(true); + expect(testBed.document.getImage(image!.getId())?.getImageData()).toMatchObject({ + layoutType: PositionedObjectLayoutType.WRAP_NONE, + behindDoc: BooleanNumber.TRUE, + docTransform: { positionH, positionV }, + }); + expect(otherDocument.getSnapshot().drawings).toEqual({}); + }); +}); diff --git a/packages/docs-drawing-ui/src/commands/commands/insert-doc-drawing.command.ts b/packages/docs-drawing/src/commands/commands/insert-doc-drawing.command.ts similarity index 66% rename from packages/docs-drawing-ui/src/commands/commands/insert-doc-drawing.command.ts rename to packages/docs-drawing/src/commands/commands/insert-doc-drawing.command.ts index 2528ed469e..903a05e987 100644 --- a/packages/docs-drawing-ui/src/commands/commands/insert-doc-drawing.command.ts +++ b/packages/docs-drawing/src/commands/commands/insert-doc-drawing.command.ts @@ -16,10 +16,12 @@ import type { DocumentDataModel, IAccessor, ICommand, IMutationInfo, ITextRangeParam, JSONXActions } from '@univerjs/core'; import type { IRichTextEditingMutationParams } from '@univerjs/docs'; -import type { IInsertDrawingCommandParams } from './interfaces'; +import type { IDocDrawing } from '../../services/doc-drawing.service'; import { + BooleanNumber, BuildTextUtils, CommandType, + getCustomBlockIdsInSelections, getRichTextEditPath, ICommandService, IUniverInstanceService, @@ -28,8 +30,13 @@ import { TextXActionType, UniverInstanceType, } from '@univerjs/core'; -import { DocContentInsertService, DocSelectionManagerService, RichTextEditingMutation } from '@univerjs/docs'; -import { getCustomBlockIdsInSelections } from '@univerjs/docs-ui'; +import { DocSelectionManagerService, getContentInsertRange, normalizeTextRange, RichTextEditingMutation } from '@univerjs/docs'; + +export interface IInsertDocDrawingCommandParams { + unitId: string; + drawings: IDocDrawing[]; + textRange?: ITextRangeParam; +} /** * The command to insert new drawings @@ -40,49 +47,46 @@ export const InsertDocDrawingCommand: ICommand = { type: CommandType.COMMAND, // eslint-disable-next-line max-lines-per-function - handler: (accessor: IAccessor, params?: IInsertDrawingCommandParams) => { - if (params == null) { + handler: (accessor: IAccessor, params?: IInsertDocDrawingCommandParams) => { + if (!params) { return false; } + const commandService = accessor.get(ICommandService); - const docSelectionManagerService = accessor.get(DocSelectionManagerService); const univerInstanceService = accessor.get(IUniverInstanceService); - const activeTextRange = docSelectionManagerService.getActiveTextRange(); - const documentDataModel = univerInstanceService.getCurrentUnitOfType(UniverInstanceType.UNIVER_DOC); - if (documentDataModel == null) { + const { unitId, drawings, textRange } = params; + const documentDataModel = univerInstanceService.getUnit(unitId, UniverInstanceType.UNIVER_DOC); + if (!documentDataModel) { return false; } - const unitId = documentDataModel.getUnitId(); - const explicitTextRange = params.textRange == null ? null : normalizeTextRange(params.textRange); - const contentInsertRange = explicitTextRange ?? getContentInsertRange(accessor, unitId); - const targetTextRange = contentInsertRange - ? { - ...activeTextRange, - startOffset: contentInsertRange.startOffset, - endOffset: contentInsertRange.endOffset, - collapsed: contentInsertRange.startOffset === contentInsertRange.endOffset, - segmentId: contentInsertRange.segmentId ?? activeTextRange?.segmentId ?? '', - } - : activeTextRange; + const targetTextRange = resolveDocDrawingInsertTextRange(accessor, unitId, textRange); - if (targetTextRange == null) { + if (!targetTextRange) { return false; } - const { drawings } = params; const { collapsed, startOffset, segmentId = '' } = targetTextRange; const body = documentDataModel.getSelfOrHeaderFooterModel(segmentId)?.getBody(); - if (body == null) { + if (!body) { return false; } const textX = new TextX(); const jsonX = JSONX.getInstance(); const rawActions: JSONXActions = []; - const drawingOrderLength = documentDataModel.getSnapshot().drawingsOrder?.length ?? 0; + const snapshot = documentDataModel.getSnapshot(); + const isHeaderFooter = !!snapshot.headers?.[segmentId] || !!snapshot.footers?.[segmentId]; + const targetDrawings = isHeaderFooter + ? drawings.map((drawing) => ({ + ...drawing, + isMultiTransform: BooleanNumber.TRUE, + transforms: drawing.transforms ?? (drawing.transform ? [drawing.transform] : null), + })) + : drawings; + const drawingOrderLength = snapshot.drawingsOrder?.length ?? 0; let removeDrawingLen = 0; // Step 1: Insert placeholder `\b` in dataStream and add drawing to customBlocks. @@ -132,13 +136,13 @@ export const InsertDocDrawingCommand: ICommand = { textX.push({ t: TextXActionType.INSERT, body: { - dataStream: '\b'.repeat(drawings.length), - customBlocks: drawings.map((drawing, i) => ({ + dataStream: '\b'.repeat(targetDrawings.length), + customBlocks: targetDrawings.map((drawing, i) => ({ startIndex: i, blockId: drawing.drawingId, })), }, - len: drawings.length, + len: targetDrawings.length, }); const path = getRichTextEditPath(documentDataModel, segmentId); @@ -147,7 +151,7 @@ export const InsertDocDrawingCommand: ICommand = { rawActions.push(placeHolderAction!); // Step 2: add drawing to drawings and drawingsOrder fields. - for (const drawing of drawings) { + for (const drawing of targetDrawings) { const { drawingId } = drawing; const addDrawingAction = jsonX.insertOp(['drawings', drawingId], drawing); const addDrawingOrderAction = jsonX.insertOp(['drawingsOrder', drawingOrderLength - removeDrawingLen], drawingId); @@ -178,31 +182,23 @@ export const InsertDocDrawingCommand: ICommand = { }, }; -function getContentInsertRange(accessor: IAccessor, unitId: string): ITextRangeParam | null { - try { - const range = accessor.get(DocContentInsertService).consumeInsertRange(unitId); - if (range == null) { - return null; - } - - return { - startOffset: range.startOffset, - endOffset: range.endOffset, - collapsed: range.startOffset === range.endOffset, - segmentId: range.segmentId, - }; - } catch { - return null; +function resolveDocDrawingInsertTextRange( + accessor: IAccessor, + unitId: string, + textRange?: ITextRangeParam +): ITextRangeParam | null { + const activeTextRange = accessor.get(DocSelectionManagerService).getActiveTextRange(); + const explicitTextRange = textRange ? normalizeTextRange(textRange) : null; + const contentInsertRange = explicitTextRange ?? getContentInsertRange(accessor, unitId); + if (!contentInsertRange) { + return activeTextRange ?? null; } -} - -function normalizeTextRange(textRange: ITextRangeParam): ITextRangeParam { - const endOffset = textRange.endOffset ?? textRange.startOffset; return { - ...textRange, - endOffset, - collapsed: textRange.collapsed ?? textRange.startOffset === endOffset, - segmentId: textRange.segmentId ?? '', + ...activeTextRange, + startOffset: contentInsertRange.startOffset, + endOffset: contentInsertRange.endOffset, + collapsed: contentInsertRange.startOffset === contentInsertRange.endOffset, + segmentId: contentInsertRange.segmentId ?? activeTextRange?.segmentId ?? '', }; } diff --git a/packages/docs-drawing-ui/src/commands/commands/remove-doc-drawing.command.ts b/packages/docs-drawing/src/commands/commands/remove-doc-drawing.command.ts similarity index 66% rename from packages/docs-drawing-ui/src/commands/commands/remove-doc-drawing.command.ts rename to packages/docs-drawing/src/commands/commands/remove-doc-drawing.command.ts index a7b17ba9df..bc4f55c353 100644 --- a/packages/docs-drawing-ui/src/commands/commands/remove-doc-drawing.command.ts +++ b/packages/docs-drawing/src/commands/commands/remove-doc-drawing.command.ts @@ -14,11 +14,9 @@ * limitations under the License. */ -import type { DocumentDataModel, IAccessor, ICommand, IDisposable, IMutationInfo, JSONXActions } from '@univerjs/core'; +import type { DocumentDataModel, DrawingTypeEnum, IAccessor, ICommand, IDisposable, IMutationInfo, ITextRangeParam, JSONXActions } from '@univerjs/core'; import type { IRichTextEditingMutationParams } from '@univerjs/docs'; -import type { IDocDrawing } from '@univerjs/docs-drawing'; -import type { ITextRangeWithStyle } from '@univerjs/engine-render'; -import type { IDeleteDrawingCommandParams } from './interfaces'; +import type { IDocDrawing } from '../../services/doc-drawing.service'; import { CommandType, getRichTextEditPath, @@ -31,35 +29,47 @@ import { TextXActionType, UniverInstanceType, } from '@univerjs/core'; -import { RichTextEditingMutation } from '@univerjs/docs'; -import { IDocDrawingAdapterService } from '@univerjs/docs-drawing'; -import { DocSelectionRenderService } from '@univerjs/docs-ui'; -import { IRenderManagerService } from '@univerjs/engine-render'; +import { DocSelectionManagerService, getContentInsertRange, normalizeTextRange, RichTextEditingMutation } from '@univerjs/docs'; +import { IDocDrawingAdapterService } from '../../services/doc-drawing-adapter.service'; + +export interface IRemoveDocDrawingCommandParam { + unitId: string; + subUnitId: string; + drawingId: string; + drawingType: DrawingTypeEnum; +} + +export interface IRemoveDocDrawingCommandParams { + unitId: string; + drawings: IRemoveDocDrawingCommandParam[]; + textRange?: ITextRangeParam; +} -/** - * The command to remove new sheet image - */ export const RemoveDocDrawingCommand: ICommand = { id: 'doc.command.remove-doc-image', type: CommandType.COMMAND, // eslint-disable-next-line max-lines-per-function - handler: (accessor: IAccessor, params?: IDeleteDrawingCommandParams) => { + handler: (accessor: IAccessor, params?: IRemoveDocDrawingCommandParams) => { + if (!params) { + return false; + } + const commandService = accessor.get(ICommandService); const undoRedoService = accessor.get(IUndoRedoService); const drawingAdapterService = accessor.get(IDocDrawingAdapterService); const univerInstanceService = accessor.get(IUniverInstanceService); - const renderManagerService = accessor.get(IRenderManagerService); - const documentDataModel = univerInstanceService.getCurrentUnitOfType(UniverInstanceType.UNIVER_DOC); + const docSelectionManagerService = accessor.get(DocSelectionManagerService); - if (params == null || documentDataModel == null) { + const { unitId, drawings: removeDrawings, textRange } = params; + const documentDataModel = univerInstanceService.getUnit(unitId, UniverInstanceType.UNIVER_DOC); + if (!documentDataModel || removeDrawings.length === 0) { return false; } - const docSelectionRenderService = renderManagerService.getRenderById(params.unitId)!.with(DocSelectionRenderService)!; - - const { drawings: removeDrawings } = params; - - const segmentId = docSelectionRenderService.getSegment() ?? ''; + const activeTextRange = docSelectionManagerService.getActiveTextRange(); + const explicitTextRange = !textRange ? null : normalizeTextRange(textRange); + const contentInsertRange = explicitTextRange ?? getContentInsertRange(accessor, unitId); + const segmentId = contentInsertRange?.segmentId ?? activeTextRange?.segmentId ?? ''; const textX = new TextX(); const jsonX = JSONX.getInstance(); @@ -69,8 +79,7 @@ export const RemoveDocDrawingCommand: ICommand = { .filter((block) => !!block) .sort((a, b) => a!.startIndex > b!.startIndex ? 1 : -1); - const unitId = removeDrawings[0]?.unitId; - if (unitId == null || removeCustomBlocks.length === 0) { + if (removeCustomBlocks.length === 0) { return false; } @@ -125,70 +134,34 @@ export const RemoveDocDrawingCommand: ICommand = { } const memoryCursor = new MemoryCursor(); - - memoryCursor.reset(); - const cursorIndex = removeCustomBlocks[0]!.startIndex; - const textRanges = [ - { - startOffset: cursorIndex, - endOffset: cursorIndex, - }, - ] as ITextRangeWithStyle[]; - + const textRanges = [{ startOffset: cursorIndex, endOffset: cursorIndex }] as IRichTextEditingMutationParams['textRanges']; const doMutation: IMutationInfo = { id: RichTextEditingMutation.id, - params: { - unitId, - actions: [], - textRanges, - }, + params: { unitId, actions: [], textRanges }, }; - const rawActions: JSONXActions = []; for (const block of removeCustomBlocks) { const { startIndex } = block!; - if (startIndex > memoryCursor.cursor) { - textX.push({ - t: TextXActionType.RETAIN, - len: startIndex - memoryCursor.cursor, - }); + textX.push({ t: TextXActionType.RETAIN, len: startIndex - memoryCursor.cursor }); } - - textX.push({ - t: TextXActionType.DELETE, - len: 1, - }); - + textX.push({ t: TextXActionType.DELETE, len: 1 }); memoryCursor.moveCursorTo(startIndex + 1); } - const path = getRichTextEditPath(documentDataModel, segmentId); - rawActions.push(jsonX.editOp(textX.serialize(), path)!); + rawActions.push(jsonX.editOp(textX.serialize(), getRichTextEditPath(documentDataModel, segmentId))!); for (const block of removeCustomBlocks) { const { blockId } = block!; - - const drawingOrder = documentDataModel.getDrawingsOrder(); - const drawingIndex = drawingOrder!.indexOf(blockId); - - const removeDrawingAction = jsonX.removeOp(['drawings', blockId], drawings[blockId]); - const removeDrawingOrderAction = jsonX.removeOp(['drawingsOrder', drawingIndex], blockId); - - rawActions.push(removeDrawingAction!); - rawActions.push(removeDrawingOrderAction!); + const drawingIndex = documentDataModel.getDrawingsOrder()!.indexOf(blockId); + rawActions.push(jsonX.removeOp(['drawings', blockId], drawings[blockId])!); + rawActions.push(jsonX.removeOp(['drawingsOrder', drawingIndex], blockId)!); } - doMutation.params.actions = rawActions.reduce((acc, cur) => { - return JSONX.compose(acc, cur as JSONXActions); - }, null as JSONXActions); - - const result = commandService.syncExecuteCommand< - IRichTextEditingMutationParams, - IRichTextEditingMutationParams - >(doMutation.id, doMutation.params); + doMutation.params.actions = rawActions.reduce((acc, cur) => JSONX.compose(acc, cur as JSONXActions), null as JSONXActions); + const result = commandService.syncExecuteCommand(doMutation.id, doMutation.params); if (!result && batchingDisposable != null) { batchingDisposable.dispose(); @@ -208,12 +181,10 @@ function executeResourceMutationGroups( const executedUndoGroups: IMutationInfo[][] = []; for (const mutationGroup of mutationGroups) { - const result = executeMutations(mutationGroup.redoMutations, commandService); - if (!result) { + if (!executeMutations(mutationGroup.redoMutations, commandService)) { executeMutationGroups([...executedUndoGroups].reverse(), commandService); return false; } - executedUndoGroups.push(mutationGroup.undoMutations); } @@ -221,9 +192,7 @@ function executeResourceMutationGroups( } function executeMutationGroups(mutationGroups: IMutationInfo[][], commandService: ICommandService): void { - mutationGroups.forEach((mutations) => { - executeMutations(mutations, commandService); - }); + mutationGroups.forEach((mutations) => executeMutations(mutations, commandService)); } function executeMutations(mutations: IMutationInfo[], commandService: ICommandService): boolean { diff --git a/packages/docs-drawing-ui/src/commands/commands/set-drawing-arrange.command.ts b/packages/docs-drawing/src/commands/commands/set-drawing-arrange.command.ts similarity index 93% rename from packages/docs-drawing-ui/src/commands/commands/set-drawing-arrange.command.ts rename to packages/docs-drawing/src/commands/commands/set-drawing-arrange.command.ts index 198c2660d7..9ee9a595b1 100644 --- a/packages/docs-drawing-ui/src/commands/commands/set-drawing-arrange.command.ts +++ b/packages/docs-drawing/src/commands/commands/set-drawing-arrange.command.ts @@ -25,9 +25,9 @@ import { Tools, } from '@univerjs/core'; import { RichTextEditingMutation } from '@univerjs/docs'; -import { IDocDrawingService } from '@univerjs/docs-drawing'; +import { IDocDrawingService } from '../../services/doc-drawing.service'; -export interface ISetDrawingArrangeCommandParams extends IDrawingOrderMapParam { +export interface ISetDocDrawingArrangeCommandParams extends IDrawingOrderMapParam { arrangeType: ArrangeTypeEnum; } @@ -39,7 +39,7 @@ export const SetDocDrawingArrangeCommand: ICommand = { type: CommandType.COMMAND, - handler: (accessor: IAccessor, params?: ISetDrawingArrangeCommandParams) => { + handler: (accessor: IAccessor, params?: ISetDocDrawingArrangeCommandParams) => { const commandService = accessor.get(ICommandService); const docDrawingService = accessor.get(IDocDrawingService); diff --git a/packages/docs-drawing/src/commands/commands/update-doc-drawing-transform.command.ts b/packages/docs-drawing/src/commands/commands/update-doc-drawing-transform.command.ts new file mode 100644 index 0000000000..638ded032d --- /dev/null +++ b/packages/docs-drawing/src/commands/commands/update-doc-drawing-transform.command.ts @@ -0,0 +1,76 @@ +/** + * 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, IAccessor, ICommand, IObjectPositionH, IObjectPositionV, ISize, JSONXActions } from '@univerjs/core'; +import type { IRichTextEditingMutationParams } from '@univerjs/docs'; +import { + CommandType, + ICommandService, + IUniverInstanceService, + JSONX, + Tools, + UniverInstanceType, +} from '@univerjs/core'; +import { RichTextEditingMutation } from '@univerjs/docs'; + +export interface IDrawingDocTransform { + drawingId: string; + key: 'size' | 'angle' | 'positionH' | 'positionV'; + value: ISize | number | IObjectPositionH | IObjectPositionV; +} + +export interface IUpdateDrawingDocTransformCommandParams { + unitId: string; + subUnitId: string; + drawings: IDrawingDocTransform[]; +} + +export const UpdateDrawingDocTransformCommand: ICommand = { + id: 'doc.command.update-drawing-doc-transform', + type: CommandType.COMMAND, + handler: (accessor: IAccessor, params?: IUpdateDrawingDocTransformCommandParams) => { + if (!params) { + return false; + } + + const commandService = accessor.get(ICommandService); + const univerInstanceService = accessor.get(IUniverInstanceService); + + const { unitId, drawings } = params; + const documentDataModel = univerInstanceService.getUnit(unitId, UniverInstanceType.UNIVER_DOC); + if (!documentDataModel) { + return false; + } + + const oldDrawings = documentDataModel.getSnapshot().drawings ?? {}; + const jsonX = JSONX.getInstance(); + const actions: JSONXActions = []; + + for (const { drawingId, key, value } of drawings) { + const oldValue = oldDrawings[drawingId]?.docTransform?.[key]; + if (oldValue == null || !Tools.diffValue(oldValue, value)) { + actions.push(jsonX.replaceOp(['drawings', drawingId, 'docTransform', key], oldValue, value)!); + } + } + + return Boolean(commandService.syncExecuteCommand(RichTextEditingMutation.id, { + unitId, + actions: actions.reduce((acc, action) => JSONX.compose(acc, action as JSONXActions), null as JSONXActions), + textRanges: null, + debounce: true, + })); + }, +}; diff --git a/packages/docs-drawing/src/commands/commands/update-doc-drawing-wrapping-style.command.ts b/packages/docs-drawing/src/commands/commands/update-doc-drawing-wrapping-style.command.ts new file mode 100644 index 0000000000..a4eb204fcd --- /dev/null +++ b/packages/docs-drawing/src/commands/commands/update-doc-drawing-wrapping-style.command.ts @@ -0,0 +1,123 @@ +/** + * 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, IAccessor, ICommand, IMutationInfo, JSONXActions } from '@univerjs/core'; +import type { IRichTextEditingMutationParams } from '@univerjs/docs'; +import type { IDocDrawing } from '../../services/doc-drawing.service'; +import { + BooleanNumber, + CommandType, + ICommandService, + IUniverInstanceService, + JSONX, + PositionedObjectLayoutType, + Tools, + UniverInstanceType, +} from '@univerjs/core'; +import { RichTextEditingMutation } from '@univerjs/docs'; + +export enum TextWrappingStyle { + INLINE = 'inline', + BEHIND_TEXT = 'behindText', + IN_FRONT_OF_TEXT = 'inFrontOfText', + WRAP_SQUARE = 'wrapSquare', + WRAP_TOP_AND_BOTTOM = 'wrapTopAndBottom', +} + +export const WRAPPING_STYLE_TO_LAYOUT_TYPE: Record = { + [TextWrappingStyle.INLINE]: PositionedObjectLayoutType.INLINE, + [TextWrappingStyle.WRAP_SQUARE]: PositionedObjectLayoutType.WRAP_SQUARE, + [TextWrappingStyle.WRAP_TOP_AND_BOTTOM]: PositionedObjectLayoutType.WRAP_TOP_AND_BOTTOM, + [TextWrappingStyle.IN_FRONT_OF_TEXT]: PositionedObjectLayoutType.WRAP_NONE, + [TextWrappingStyle.BEHIND_TEXT]: PositionedObjectLayoutType.WRAP_NONE, +}; + +export interface IUpdateDocDrawingWrappingStyleParams { + unitId: string; + subUnitId: string; + drawings: IDocDrawing[]; + wrappingStyle: TextWrappingStyle; +} + +/** + * Updates document drawing wrapping styles and optional persisted positions. + */ +export const UpdateDocDrawingWrappingStyleCommand: ICommand = { + id: 'doc.command.update-doc-drawing-wrapping-style', + type: CommandType.COMMAND, + handler: (accessor: IAccessor, params?: IUpdateDocDrawingWrappingStyleParams) => { + if (!params) { + return false; + } + + const { drawings, wrappingStyle, unitId } = params; + const commandService = accessor.get(ICommandService); + const documentDataModel = accessor.get(IUniverInstanceService).getUnit( + unitId, + UniverInstanceType.UNIVER_DOC + ); + if (!documentDataModel) { + return false; + } + + const oldDrawings = documentDataModel.getDrawings() ?? {}; + const jsonX = JSONX.getInstance(); + const rawActions: JSONXActions = []; + + for (const drawing of drawings) { + const oldDrawing = oldDrawings[drawing.drawingId] as IDocDrawing | undefined; + if (!oldDrawing) { + continue; + } + + const layoutType = WRAPPING_STYLE_TO_LAYOUT_TYPE[wrappingStyle]; + if (oldDrawing.layoutType !== layoutType) { + rawActions.push(jsonX.replaceOp(['drawings', drawing.drawingId, 'layoutType'], oldDrawing.layoutType, layoutType)!); + } + + if (wrappingStyle === TextWrappingStyle.BEHIND_TEXT || wrappingStyle === TextWrappingStyle.IN_FRONT_OF_TEXT) { + const behindDoc = wrappingStyle === TextWrappingStyle.BEHIND_TEXT ? BooleanNumber.TRUE : BooleanNumber.FALSE; + if (oldDrawing.behindDoc !== behindDoc) { + rawActions.push(jsonX.replaceOp(['drawings', drawing.drawingId, 'behindDoc'], oldDrawing.behindDoc, behindDoc)!); + } + } + + if (wrappingStyle !== TextWrappingStyle.INLINE) { + for (const key of ['positionH', 'positionV'] as const) { + const value = drawing.docTransform?.[key]; + const oldValue = oldDrawing.docTransform[key]; + if (value && !Tools.diffValue(oldValue, value)) { + rawActions.push(jsonX.replaceOp(['drawings', drawing.drawingId, 'docTransform', key], oldValue, value)!); + } + } + } + } + + const mutation: IMutationInfo = { + id: RichTextEditingMutation.id, + params: { + unitId, + actions: rawActions.reduce( + (actions, action) => JSONX.compose(actions, action as JSONXActions), + null as JSONXActions + ), + textRanges: null, + }, + }; + + return Boolean(commandService.syncExecuteCommand(mutation.id, mutation.params)); + }, +}; diff --git a/packages/docs-drawing/src/controllers/__tests__/doc-drawing.controller.spec.ts b/packages/docs-drawing/src/controllers/__tests__/doc-drawing.controller.spec.ts index f78ca012fe..0c08ee1824 100644 --- a/packages/docs-drawing/src/controllers/__tests__/doc-drawing.controller.spec.ts +++ b/packages/docs-drawing/src/controllers/__tests__/doc-drawing.controller.spec.ts @@ -53,7 +53,8 @@ describe('DocDrawingController', () => { { registerDrawingData } as any, { registerDrawingData: registerDrawingDataForManager } as any, resourceManagerService as any, - univerInstanceService as any + univerInstanceService as any, + { registerCommand: vi.fn(() => ({ dispose: vi.fn() })) } as any ); expect(resourceManagerService.registerPluginResource).toHaveBeenCalledTimes(1); diff --git a/packages/docs-drawing/src/controllers/doc-drawing.controller.ts b/packages/docs-drawing/src/controllers/doc-drawing.controller.ts index 3dd1f2fcdc..17a4901a7b 100644 --- a/packages/docs-drawing/src/controllers/doc-drawing.controller.ts +++ b/packages/docs-drawing/src/controllers/doc-drawing.controller.ts @@ -17,8 +17,13 @@ import type { DocumentDataModel, IDocumentData } from '@univerjs/core'; import type { IDrawingMapItem, IDrawingMapItemData } from '@univerjs/drawing'; import type { IDocDrawing } from '../services/doc-drawing.service'; -import { BooleanNumber, Disposable, IResourceManagerService, IUniverInstanceService, PositionedObjectLayoutType, UniverInstanceType } from '@univerjs/core'; +import { BooleanNumber, Disposable, ICommandService, IResourceManagerService, IUniverInstanceService, PositionedObjectLayoutType, UniverInstanceType } from '@univerjs/core'; import { IDrawingManagerService } from '@univerjs/drawing'; +import { InsertDocDrawingCommand } from '../commands/commands/insert-doc-drawing.command'; +import { RemoveDocDrawingCommand } from '../commands/commands/remove-doc-drawing.command'; +import { SetDocDrawingArrangeCommand } from '../commands/commands/set-drawing-arrange.command'; +import { UpdateDrawingDocTransformCommand } from '../commands/commands/update-doc-drawing-transform.command'; +import { UpdateDocDrawingWrappingStyleCommand } from '../commands/commands/update-doc-drawing-wrapping-style.command'; import { IDocDrawingService } from '../services/doc-drawing.service'; export const DOCS_DRAWING_PLUGIN = 'DOC_DRAWING_PLUGIN'; @@ -52,7 +57,8 @@ export class DocDrawingController extends Disposable { @IDocDrawingService private readonly _docDrawingService: IDocDrawingService, @IDrawingManagerService private readonly _drawingManagerService: IDrawingManagerService, @IResourceManagerService private _resourceManagerService: IResourceManagerService, - @IUniverInstanceService private _univerInstanceService: IUniverInstanceService + @IUniverInstanceService private _univerInstanceService: IUniverInstanceService, + @ICommandService private readonly _commandService: ICommandService ) { super(); @@ -61,6 +67,7 @@ export class DocDrawingController extends Disposable { private _init(): void { this._initSnapshot(); + this._initCommands(); } private _initSnapshot() { @@ -151,4 +158,14 @@ export class DocDrawingController extends Disposable { this._drawingManagerService.registerDrawingData(unitId, renderSubDrawings); return true; } + + private _initCommands() { + [ + InsertDocDrawingCommand, + RemoveDocDrawingCommand, + UpdateDrawingDocTransformCommand, + UpdateDocDrawingWrappingStyleCommand, + SetDocDrawingArrangeCommand, + ].forEach((command) => this.disposeWithMe(this._commandService.registerCommand(command))); + } } diff --git a/packages/docs-drawing/src/facade/__tests__/create-test-bed.ts b/packages/docs-drawing/src/facade/__tests__/create-test-bed.ts new file mode 100644 index 0000000000..9ccbcebddb --- /dev/null +++ b/packages/docs-drawing/src/facade/__tests__/create-test-bed.ts @@ -0,0 +1,61 @@ +/** + * 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, IDocumentData } from '@univerjs/core'; +import { DocumentFlavor, ILogService, IUniverInstanceService, LogLevel, Univer, UniverInstanceType } from '@univerjs/core'; +import { FUniver } from '@univerjs/core/facade'; +import { UniverDocsPlugin } from '@univerjs/docs'; +import { UniverDrawingPlugin } from '@univerjs/drawing'; +import { IRenderManagerService, RenderManagerService } from '@univerjs/engine-render'; +import { UniverDocsDrawingPlugin } from '../../plugin'; +import '../index'; + +const DEFAULT_DOC_DATA: IDocumentData = { + id: 'test-doc', + documentStyle: { documentFlavor: DocumentFlavor.TRADITIONAL }, + body: { + dataStream: 'Hello world\r\n', + paragraphs: [{ startIndex: 11, paragraphId: 'paragraph-1' }], + sectionBreaks: [], + customBlocks: [], + }, + drawings: {}, + drawingsOrder: [], +}; + +export function createFacadeTestBed() { + const univer = new Univer(); + const injector = univer.__getInjector(); + + injector.add([IRenderManagerService, { useClass: RenderManagerService }]); + univer.registerPlugin(UniverDocsPlugin); + univer.registerPlugin(UniverDrawingPlugin); + univer.registerPlugin(UniverDocsDrawingPlugin); + + const documentDataModel = univer.createUnit( + UniverInstanceType.UNIVER_DOC, + DEFAULT_DOC_DATA + ); + injector.get(IUniverInstanceService).focusUnit(documentDataModel.getUnitId()); + injector.get(ILogService).setLogLevel(LogLevel.SILENT); + + return { + univer, + injector, + documentDataModel, + document: FUniver.newAPI(injector).getActiveDocument()!, + }; +} diff --git a/packages/docs-drawing/src/facade/__tests__/f-document-image.spec.ts b/packages/docs-drawing/src/facade/__tests__/f-document-image.spec.ts new file mode 100644 index 0000000000..0b68bd3c38 --- /dev/null +++ b/packages/docs-drawing/src/facade/__tests__/f-document-image.spec.ts @@ -0,0 +1,277 @@ +/** + * 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 { IObjectPositionH, IObjectPositionV } from '@univerjs/core'; +import { + ArrangeTypeEnum, + BooleanNumber, + ICommandService, + ImageSourceType, + ObjectRelativeFromH, + ObjectRelativeFromV, + PositionedObjectLayoutType, +} from '@univerjs/core'; +import { DocSelectionManagerService } from '@univerjs/docs'; +import { TextWrappingStyle, UpdateDocDrawingWrappingStyleCommand } from '@univerjs/docs-drawing'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { RemoveDocDrawingCommand } from '../../commands/commands/remove-doc-drawing.command'; +import { SetDocDrawingArrangeCommand } from '../../commands/commands/set-drawing-arrange.command'; +import { UpdateDrawingDocTransformCommand } from '../../commands/commands/update-doc-drawing-transform.command'; +import { createFacadeTestBed } from './create-test-bed'; + +class MockImage { + width = 800; + height = 400; + onload: (() => void) | null = null; + onerror: (() => void) | null = null; + + get src(): string { + return ''; + } + + set src(_value: string) { + queueMicrotask(() => this.onload?.()); + } +} + +describe('FDocument image facade', () => { + let testBed: ReturnType; + + beforeEach(() => { + vi.stubGlobal('Image', MockImage); + testBed = createFacadeTestBed(); + }); + + afterEach(() => { + testBed.univer.dispose(); + vi.unstubAllGlobals(); + vi.restoreAllMocks(); + }); + + it('inserts an image with optional transform and text range options', async () => { + const positionH: IObjectPositionH = { + relativeFrom: ObjectRelativeFromH.MARGIN, + posOffset: 12, + }; + const positionV: IObjectPositionV = { + relativeFrom: ObjectRelativeFromV.PARAGRAPH, + posOffset: 34, + }; + + const image = await testBed.document.insertImage({ + source: 'data:image/png;base64,image', + imageSourceType: ImageSourceType.BASE64, + width: 200, + angle: 15, + positionH, + positionV, + textRange: { + startOffset: 5, + endOffset: 5, + collapsed: true, + segmentId: '', + }, + }); + + expect(image).not.toBeNull(); + expect(image?.getSource()).toBe('data:image/png;base64,image'); + expect(image?.getSourceType()).toBe(ImageSourceType.BASE64); + expect(image?.getSize()).toEqual({ width: 200, height: 100 }); + expect(image?.getAngle()).toBe(15); + expect(image?.getPositionH()).toEqual(positionH); + expect(image?.getPositionV()).toEqual(positionV); + expect(image?.getImageData()).toMatchObject({ + drawingId: image?.getId(), + source: 'data:image/png;base64,image', + imageSourceType: ImageSourceType.BASE64, + }); + expect(testBed.document.save().body?.dataStream).toBe('Hello\b world\r\n'); + expect(testBed.document.getImage(image!.getId())).not.toBeNull(); + expect(testBed.document.getImages().map((item) => item.getId())).toEqual([image!.getId()]); + }); + + it('resolves the insertion range only once', async () => { + const selectionManager = testBed.injector.get(DocSelectionManagerService); + const getActiveTextRange = vi.spyOn(selectionManager, 'getActiveTextRange'); + + await testBed.document.insertImage({ + source: 'data:image/png;base64,image', + imageSourceType: ImageSourceType.BASE64, + textRange: { + startOffset: 0, + endOffset: 0, + collapsed: true, + segmentId: '', + }, + }); + + expect(getActiveTextRange).toHaveBeenCalledTimes(1); + }); + + it('uses a capped intrinsic size when width and height are omitted', async () => { + const image = await testBed.document.insertImage({ + source: 'data:image/png;base64,image', + imageSourceType: ImageSourceType.BASE64, + textRange: { + startOffset: 0, + endOffset: 0, + collapsed: true, + segmentId: '', + }, + }); + + expect(image?.getSize()).toEqual({ width: 500, height: 250 }); + }); + + it('inserts an image with a non-inline wrapping style', async () => { + const image = await testBed.document.insertImage({ + source: 'data:image/png;base64,image', + imageSourceType: ImageSourceType.BASE64, + wrappingStyle: TextWrappingStyle.BEHIND_TEXT, + textRange: { + startOffset: 2, + endOffset: 2, + collapsed: true, + segmentId: '', + }, + }); + + expect(image?.getImageData()).toMatchObject({ + layoutType: PositionedObjectLayoutType.WRAP_NONE, + behindDoc: BooleanNumber.TRUE, + }); + }); + + it('inserts a header image with multi-page transforms', async () => { + const segmentId = testBed.document.ensurePageHeader(); + const image = await testBed.document.insertImage({ + source: 'data:image/png;base64,image', + imageSourceType: ImageSourceType.BASE64, + width: 160, + height: 90, + textRange: { + startOffset: 0, + endOffset: 0, + collapsed: true, + segmentId, + }, + }); + const imageData = image?.getImageData(); + + expect(testBed.document.save().headers?.[segmentId].body.dataStream).toBe('\b\r\n'); + expect(imageData?.isMultiTransform).toBe(BooleanNumber.TRUE); + expect(imageData?.transforms).toEqual(imageData?.transform ? [imageData.transform] : null); + }); + + it('routes image updates, arranging, and removal through docs-drawing commands', async () => { + const image = await testBed.document.insertImage({ + source: 'data:image/png;base64,image', + imageSourceType: ImageSourceType.BASE64, + width: 160, + height: 90, + textRange: { + startOffset: 3, + endOffset: 3, + collapsed: true, + segmentId: '', + }, + }); + const commandService = testBed.injector.get(ICommandService); + const execute = vi.spyOn(commandService, 'syncExecuteCommand').mockReturnValue(true); + const positionH: IObjectPositionH = { relativeFrom: ObjectRelativeFromH.PAGE, posOffset: 20 }; + const positionV: IObjectPositionV = { relativeFrom: ObjectRelativeFromV.PAGE, posOffset: 30 }; + const drawingId = image!.getId(); + + expect(image!.setSize(320, 180)).toBe(true); + expect(image!.setRotate(25)).toBe(true); + expect(image!.setPositionH(positionH)).toBe(true); + expect(image!.setPositionV(positionV)).toBe(true); + expect(image!.setWrappingStyle(TextWrappingStyle.WRAP_SQUARE)).toBe(true); + expect(image!.setForward()).toBe(true); + expect(image!.setBackward()).toBe(true); + expect(image!.setBack()).toBe(true); + expect(image!.setFront()).toBe(true); + expect(image!.remove()).toBe(true); + + expect(execute.mock.calls).toEqual([ + [UpdateDrawingDocTransformCommand.id, { + unitId: 'test-doc', + subUnitId: 'test-doc', + drawings: [{ drawingId, key: 'size', value: { width: 320, height: 180 } }], + }], + [UpdateDrawingDocTransformCommand.id, { + unitId: 'test-doc', + subUnitId: 'test-doc', + drawings: [{ drawingId, key: 'angle', value: 25 }], + }], + [UpdateDrawingDocTransformCommand.id, { + unitId: 'test-doc', + subUnitId: 'test-doc', + drawings: [{ drawingId, key: 'positionH', value: positionH }], + }], + [UpdateDrawingDocTransformCommand.id, { + unitId: 'test-doc', + subUnitId: 'test-doc', + drawings: [{ drawingId, key: 'positionV', value: positionV }], + }], + [UpdateDocDrawingWrappingStyleCommand.id, { + unitId: 'test-doc', + subUnitId: 'test-doc', + drawings: [image!.getImageData()], + wrappingStyle: TextWrappingStyle.WRAP_SQUARE, + }], + [SetDocDrawingArrangeCommand.id, { + unitId: 'test-doc', + subUnitId: 'test-doc', + drawingIds: [drawingId], + arrangeType: ArrangeTypeEnum.forward, + }], + [SetDocDrawingArrangeCommand.id, { + unitId: 'test-doc', + subUnitId: 'test-doc', + drawingIds: [drawingId], + arrangeType: ArrangeTypeEnum.backward, + }], + [SetDocDrawingArrangeCommand.id, { + unitId: 'test-doc', + subUnitId: 'test-doc', + drawingIds: [drawingId], + arrangeType: ArrangeTypeEnum.back, + }], + [SetDocDrawingArrangeCommand.id, { + unitId: 'test-doc', + subUnitId: 'test-doc', + drawingIds: [drawingId], + arrangeType: ArrangeTypeEnum.front, + }], + [RemoveDocDrawingCommand.id, { + unitId: 'test-doc', + drawings: [{ + unitId: 'test-doc', + subUnitId: 'test-doc', + drawingId, + drawingType: 0, + }], + textRange: { + startOffset: 3, + endOffset: 3, + collapsed: true, + segmentId: '', + }, + }], + ]); + }); +}); diff --git a/packages/docs-drawing/src/facade/f-document-image.ts b/packages/docs-drawing/src/facade/f-document-image.ts new file mode 100644 index 0000000000..1f671eb28e --- /dev/null +++ b/packages/docs-drawing/src/facade/f-document-image.ts @@ -0,0 +1,468 @@ +/** + * 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 { ImageSourceType, Injector, IObjectPositionH, IObjectPositionV, ISize, ITextRangeParam } from '@univerjs/core'; +import type { IDocImage, IUpdateDocDrawingWrappingStyleParams, TextWrappingStyle } from '@univerjs/docs-drawing'; +import type { FDocument } from '@univerjs/docs/facade'; +import { + ArrangeTypeEnum, + DrawingTypeEnum, + ICommandService, +} from '@univerjs/core'; +import { + RemoveDocDrawingCommand, + SetDocDrawingArrangeCommand, + UpdateDocDrawingWrappingStyleCommand, + UpdateDrawingDocTransformCommand, +} from '@univerjs/docs-drawing'; + +/** + * Facade API for an image in a document. + * @hideconstructor + */ +export class FDocumentImage { + constructor( + private readonly _document: FDocument, + private readonly _imageId: string, + private readonly _injector: Injector + ) {} + + /** + * Gets the id of the document containing the image. + * @returns {string} The document unit id. + * @example + * ```ts + * const fDocument = univerAPI.getActiveDocument(); + * const image = fDocument.getImages()[0]; + * + * if (image) { + * console.log(image.getUnitId()); + * } + * ``` + */ + getUnitId(): string { + return this._document.getId(); + } + + /** + * Gets the drawing id of the image. + * @returns {string} The drawing id. + * @example + * ```ts + * const fDocument = univerAPI.getActiveDocument(); + * const image = fDocument.getImages()[0]; + * + * if (image) { + * console.log(image.getId()); + * } + * ``` + */ + getId(): string { + return this._imageId; + } + + /** + * Gets the image source supplied at insertion time. + * @returns {string | undefined} The image source, or `undefined` when the image no longer exists. + * @example + * ```ts + * const fDocument = univerAPI.getActiveDocument(); + * const image = fDocument.getImages()[0]; + * + * if (image) { + * console.log(image.getSource()); + * } + * ``` + */ + getSource(): string | undefined { + return this.getImageData()?.source; + } + + /** + * Gets the image source type supplied at insertion time. + * @returns {ImageSourceType | undefined} The image source type, or `undefined` when the image no longer exists. + * @example + * ```ts + * const fDocument = univerAPI.getActiveDocument(); + * const image = fDocument.getImages()[0]; + * + * if (image) { + * console.log(image.getSourceType()); + * } + * ``` + */ + getSourceType(): ImageSourceType | undefined { + return this.getImageData()?.imageSourceType; + } + + /** + * Gets the current image size. + * @returns {ISize | null} The width and height in pixels, or `null` when the image no longer exists. + * @example + * ```ts + * const fDocument = univerAPI.getActiveDocument(); + * const image = fDocument.getImages()[0]; + * + * if (image) { + * console.log(image.getSize()); + * } + * ``` + */ + getSize(): ISize | null { + return this.getImageData()?.docTransform.size ?? null; + } + + /** + * Gets the current clockwise rotation angle. + * @returns {number | undefined} The rotation angle in degrees, or `undefined` when the image no longer exists. + * @example + * ```ts + * const fDocument = univerAPI.getActiveDocument(); + * const image = fDocument.getImages()[0]; + * + * if (image) { + * console.log(image.getAngle()); + * } + * ``` + */ + getAngle(): number | undefined { + return this.getImageData()?.docTransform.angle; + } + + /** + * Gets the current horizontal position. + * @returns {IObjectPositionH | null} The horizontal position, or `null` when the image no longer exists. + * @example + * ```ts + * const fDocument = univerAPI.getActiveDocument(); + * const image = fDocument.getImages()[0]; + * + * if (image) { + * console.log(image.getPositionH()); + * } + * ``` + */ + getPositionH(): IObjectPositionH | null { + return this.getImageData()?.docTransform.positionH ?? null; + } + + /** + * Gets the current vertical position. + * @returns {IObjectPositionV | null} The vertical position, or `null` when the image no longer exists. + * @example + * ```ts + * const fDocument = univerAPI.getActiveDocument(); + * const image = fDocument.getImages()[0]; + * + * if (image) { + * console.log(image.getPositionV()); + * } + * ``` + */ + getPositionV(): IObjectPositionV | null { + return this.getImageData()?.docTransform.positionV ?? null; + } + + /** + * Gets the current raw document image data. + * @returns {IDocImage | null} The image data, or `null` when the image no longer exists. + * @example + * ```ts + * const fDocument = univerAPI.getActiveDocument(); + * const image = fDocument.getImages()[0]; + * + * if (image) { + * console.log(image.getImageData()); + * } + * ``` + */ + getImageData(): IDocImage | null { + const drawing = this._document.getDocumentDataModel().getDrawings()?.[this._imageId]; + if (!drawing || drawing.drawingType !== DrawingTypeEnum.DRAWING_IMAGE) { + return null; + } + return drawing as IDocImage; + } + + /** + * Sets the image size. + * @param {number} width The width in pixels. + * @param {number} height The height in pixels. + * @returns {boolean} `true` when the update command succeeds; otherwise, `false`. + * @example + * ```ts + * const fDocument = univerAPI.getActiveDocument(); + * const image = fDocument.getImages()[0]; + * + * if (image) { + * const success = image.setSize(400, 300); + * console.log(success); + * } + * ``` + */ + setSize(width: number, height: number): boolean { + return this._updateTransform('size', { width, height }); + } + + /** + * Sets the clockwise rotation angle. + * @param {number} angle The rotation angle in degrees. + * @returns {boolean} `true` when the update command succeeds; otherwise, `false`. + * @example + * ```ts + * const fDocument = univerAPI.getActiveDocument(); + * const image = fDocument.getImages()[0]; + * + * if (image) { + * const success = image.setRotate(45); + * console.log(success); + * } + * ``` + */ + setRotate(angle: number): boolean { + return this._updateTransform('angle', angle); + } + + /** + * Sets the horizontal position of the image. + * Inline images are positioned by their document placeholder, so this has a visible effect only when the image + * wrapping style is not `TextWrappingStyle.INLINE`. + * @param {IObjectPositionH} positionH The horizontal position relative to the document. + * @returns {boolean} `true` when the update command succeeds; otherwise, `false`. + * @example + * ```ts + * const fDocument = univerAPI.getActiveDocument(); + * const image = fDocument.getImages()[0]; + * + * if (image) { + * const success = image.setPositionH({ + * relativeFrom: univerAPI.Enum.DocsImageRelativeFromH.MARGIN, + * posOffset: 100 + * }); + * console.log(success); + * } + * ``` + */ + setPositionH(positionH: IObjectPositionH): boolean { + return this._updateTransform('positionH', positionH); + } + + /** + * Sets the vertical position of the image. + * Inline images are positioned by their document placeholder, so this has a visible effect only when the image + * wrapping style is not `TextWrappingStyle.INLINE`. + * @param {IObjectPositionV} positionV The vertical position relative to the document. + * @returns {boolean} `true` when the update command succeeds; otherwise, `false`. + * @example + * ```ts + * const fDocument = univerAPI.getActiveDocument(); + * const image = fDocument.getImages()[0]; + * + * if (image) { + * const success = image.setPositionV({ + * relativeFrom: univerAPI.Enum.DocsImageRelativeFromV.MARGIN, + * posOffset: 100 + * }); + * console.log(success); + * } + * ``` + */ + setPositionV(positionV: IObjectPositionV): boolean { + return this._updateTransform('positionV', positionV); + } + + /** + * Sets the image wrapping style. + * When switching from inline to a floating style in a UI environment, the current visual position is preserved. + * @param {TextWrappingStyle} wrappingStyle The wrapping style to apply. + * @returns {boolean} `true` when the update command succeeds; otherwise, `false`. + * @example + * ```ts + * const fDocument = univerAPI.getActiveDocument(); + * const image = fDocument.getImages()[0]; + * + * if (image) { + * const success = image.setWrappingStyle(univerAPI.Enum.DocsImageWrappingStyle.WRAP_SQUARE); + * console.log(success); + * } + * ``` + */ + setWrappingStyle(wrappingStyle: TextWrappingStyle): boolean { + const image = this.getImageData(); + if (!image) { + return false; + } + + return this._injector.get(ICommandService).syncExecuteCommand( + UpdateDocDrawingWrappingStyleCommand.id, + { + unitId: this.getUnitId(), + subUnitId: this.getUnitId(), + drawings: [image], + wrappingStyle, + } + ); + } + + /** + * Moves the image forward by one level in the drawing order. + * @returns {boolean} `true` when the arrange command succeeds; otherwise, `false`. + * @example + * ```ts + * const fDocument = univerAPI.getActiveDocument(); + * const image = fDocument.getImages()[0]; + * + * if (image) { + * const success = image.setForward(); + * console.log(success); + * } + * ``` + */ + setForward(): boolean { + return this._arrange(ArrangeTypeEnum.forward); + } + + /** + * Moves the image backward by one level in the drawing order. + * @returns {boolean} `true` when the arrange command succeeds; otherwise, `false`. + * @example + * ```ts + * const fDocument = univerAPI.getActiveDocument(); + * const image = fDocument.getImages()[0]; + * + * if (image) { + * const success = image.setBackward(); + * console.log(success); + * } + * ``` + */ + setBackward(): boolean { + return this._arrange(ArrangeTypeEnum.backward); + } + + /** + * Moves the image to the back of the drawing order. + * @returns {boolean} `true` when the arrange command succeeds; otherwise, `false`. + * @example + * ```ts + * const fDocument = univerAPI.getActiveDocument(); + * const image = fDocument.getImages()[0]; + * + * if (image) { + * const success = image.setBack(); + * console.log(success); + * } + * ``` + */ + setBack(): boolean { + return this._arrange(ArrangeTypeEnum.back); + } + + /** + * Moves the image to the front of the drawing order. + * @returns {boolean} `true` when the arrange command succeeds; otherwise, `false`. + * @example + * ```ts + * const fDocument = univerAPI.getActiveDocument(); + * const image = fDocument.getImages()[0]; + * + * if (image) { + * const success = image.setFront(); + * console.log(success); + * } + * ``` + */ + setFront(): boolean { + return this._arrange(ArrangeTypeEnum.front); + } + + /** + * Removes the image and its document placeholder. + * @returns {boolean} `true` when the remove command succeeds; otherwise, `false`. + * @example + * ```ts + * const fDocument = univerAPI.getActiveDocument(); + * const image = fDocument.getImages()[0]; + * + * if (image) { + * const success = image.remove(); + * console.log(success); + * } + * ``` + */ + remove(): boolean { + const image = this.getImageData(); + const textRange = this._getTextRange(); + if (!image || !textRange) { + return false; + } + + return this._injector.get(ICommandService).syncExecuteCommand(RemoveDocDrawingCommand.id, { + unitId: this.getUnitId(), + drawings: [{ + unitId: this.getUnitId(), + subUnitId: this.getUnitId(), + drawingId: this._imageId, + drawingType: DrawingTypeEnum.DRAWING_IMAGE, + }], + textRange, + }); + } + + private _updateTransform( + key: 'size' | 'angle' | 'positionH' | 'positionV', + value: ISize | number | IObjectPositionH | IObjectPositionV + ): boolean { + return this._injector.get(ICommandService).syncExecuteCommand(UpdateDrawingDocTransformCommand.id, { + unitId: this.getUnitId(), + subUnitId: this.getUnitId(), + drawings: [{ drawingId: this._imageId, key, value }], + }); + } + + private _arrange(arrangeType: ArrangeTypeEnum): boolean { + return this._injector.get(ICommandService).syncExecuteCommand(SetDocDrawingArrangeCommand.id, { + unitId: this.getUnitId(), + subUnitId: this.getUnitId(), + drawingIds: [this._imageId], + arrangeType, + }); + } + + private _getTextRange(): ITextRangeParam | null { + const snapshot = this._document.getDocumentDataModel().getSnapshot(); + const { body, headers = {}, footers = {} } = snapshot; + const segments = [ + { segmentId: '', body }, + ...Object.entries(headers).map(([segmentId, header]) => ({ segmentId, body: header.body })), + ...Object.entries(footers).map(([segmentId, footer]) => ({ segmentId, body: footer.body })), + ]; + + for (const { segmentId, body } of segments) { + const customBlock = body?.customBlocks?.find((block) => block.blockId === this._imageId); + if (customBlock) { + return { + startOffset: customBlock.startIndex, + endOffset: customBlock.startIndex, + collapsed: true, + segmentId, + }; + } + } + + return null; + } +} diff --git a/packages/docs-drawing/src/facade/f-document.ts b/packages/docs-drawing/src/facade/f-document.ts new file mode 100644 index 0000000000..57eb65a7f8 --- /dev/null +++ b/packages/docs-drawing/src/facade/f-document.ts @@ -0,0 +1,247 @@ +/** + * 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 { IObjectPositionH, IObjectPositionV, ISize, ITextRangeParam } from '@univerjs/core'; +import type { IDocImage } from '@univerjs/docs-drawing'; +import { + BooleanNumber, + DrawingTypeEnum, + generateRandomId, + ICommandService, + IImageIoService, + ImageSourceType, + IURLImageService, + WrapTextType, +} from '@univerjs/core'; +import { buildDocTransform, docDrawingPositionToTransform } from '@univerjs/docs'; +import { + InsertDocDrawingCommand, + TextWrappingStyle, + WRAPPING_STYLE_TO_LAYOUT_TYPE, +} from '@univerjs/docs-drawing'; +import { FDocument } from '@univerjs/docs/facade'; +import { DRAWING_IMAGE_HEIGHT_LIMIT, DRAWING_IMAGE_WIDTH_LIMIT, getImageSize } from '@univerjs/drawing'; +import { FDocumentImage } from './f-document-image'; + +/** Options for inserting an image into a document. */ +export interface IFDocumentInsertImageOptions { + /** The image source. It cannot be changed after insertion. */ + source: string; + /** The image source type. It cannot be changed after insertion. */ + imageSourceType: ImageSourceType; + /** The width in pixels. When only width is provided, height is calculated from the intrinsic aspect ratio. */ + width?: number; + /** The height in pixels. When only height is provided, width is calculated from the intrinsic aspect ratio. */ + height?: number; + /** The clockwise rotation angle in degrees. Defaults to `0`. */ + angle?: number; + /** + * The horizontal position relative to the document. Defaults to the page with an offset of `0`. + * It has a visible positioning effect only when `wrappingStyle` is not `INLINE`. + */ + positionH?: IObjectPositionH; + /** + * The vertical position relative to the document. Defaults to the paragraph with an offset of `0`. + * It has a visible positioning effect only when `wrappingStyle` is not `INLINE`. + */ + positionV?: IObjectPositionV; + /** The image wrapping style. Defaults to `TextWrappingStyle.INLINE`. */ + wrappingStyle?: TextWrappingStyle; + /** The document range at which to insert the image. The current selection is used when omitted. */ + textRange?: ITextRangeParam; +} + +/** Image facade methods mixed into `FDocument`. */ +export interface IFDocumentImageMixin { + /** + * Inserts an image into the document. + * + * When width and height are both omitted, the intrinsic size is proportionally limited to 500 by 500 pixels. + * + * @param {IFDocumentInsertImageOptions} options The image source, optional transform, and insertion range. + * @returns {Promise} The inserted image facade, or `null` when the insertion command fails. + * @example + * ```ts + * const fDocument = univerAPI.getActiveDocument(); + * const image = await fDocument.insertImage({ + * source: 'https://avatars.githubusercontent.com/u/61444807?s=48&v=4', + * imageSourceType: univerAPI.Enum.ImageSourceType.URL, + * width: 320, + * textRange: { + * startOffset: 30, + * }, + * }); + * console.log(image); + * ``` + * + * @example + * ```ts + * const fDocument = univerAPI.getActiveDocument(); + * const image = await fDocument.insertImage({ + * source: 'https://avatars.githubusercontent.com/u/61444807?s=48&v=4', + * imageSourceType: univerAPI.Enum.ImageSourceType.URL, + * width: 320, + * wrappingStyle: univerAPI.Enum.DocsImageWrappingStyle.WRAP_SQUARE, + * textRange: { + * startOffset: 30, + * }, + * }); + * console.log(image); + * ``` + */ + insertImage(options: IFDocumentInsertImageOptions): Promise; + + /** + * Gets an image by its drawing id. + * @param {string} imageId The drawing id of the image. + * @returns {FDocumentImage | null} The image facade, or `null` when the image does not exist. + * @example + * ```ts + * const fDocument = univerAPI.getActiveDocument(); + * const image = fDocument.getImage('image-1'); + * console.log(image); + * ``` + */ + getImage(imageId: string): FDocumentImage | null; + + /** + * Gets all images in the document in drawing order. + * @returns {FDocumentImage[]} The image facades in drawing order. + * @example + * ```ts + * const fDocument = univerAPI.getActiveDocument(); + * const images = fDocument.getImages(); + * console.log(images); + * ``` + */ + getImages(): FDocumentImage[]; +} + +export class FDocumentImageMixin extends FDocument implements IFDocumentImageMixin { + override async insertImage(options: IFDocumentInsertImageOptions): Promise { + const unitId = this.getId(); + const imageId = generateRandomId(6); + const intrinsicSize = await this._getIntrinsicSize(options.source, options.imageSourceType); + const size = resolveImageSize(intrinsicSize, options); + const defaultTransform = buildDocTransform(size.width, size.height); + const wrappingStyle = options.wrappingStyle ?? TextWrappingStyle.INLINE; + const docTransform = { + ...defaultTransform, + angle: options.angle ?? defaultTransform.angle, + positionH: options.positionH ?? defaultTransform.positionH, + positionV: options.positionV ?? defaultTransform.positionV, + }; + const transform = docDrawingPositionToTransform(docTransform); + const drawing: IDocImage = { + unitId, + subUnitId: unitId, + drawingId: imageId, + drawingType: DrawingTypeEnum.DRAWING_IMAGE, + imageSourceType: options.imageSourceType, + source: options.source, + transform, + docTransform, + behindDoc: wrappingStyle === TextWrappingStyle.BEHIND_TEXT ? BooleanNumber.TRUE : BooleanNumber.FALSE, + title: '', + description: '', + layoutType: WRAPPING_STYLE_TO_LAYOUT_TYPE[wrappingStyle], + wrapText: WrapTextType.BOTH_SIDES, + distB: 0, + distL: 0, + distR: 0, + distT: 0, + }; + + const inserted = this._injector.get(ICommandService).syncExecuteCommand(InsertDocDrawingCommand.id, { + unitId, + drawings: [drawing], + textRange: options.textRange, + }); + + if (!inserted) { + return null; + } + + return this._injector.createInstance(FDocumentImage, this, imageId, this._injector); + } + + override getImage(imageId: string): FDocumentImage | null { + const drawing = this.getDocumentDataModel().getDrawings()?.[imageId]; + if (!drawing || drawing.drawingType !== DrawingTypeEnum.DRAWING_IMAGE) { + return null; + } + return this._injector.createInstance(FDocumentImage, this, imageId, this._injector); + } + + override getImages(): FDocumentImage[] { + const documentDataModel = this.getDocumentDataModel(); + const drawings = documentDataModel.getDrawings() ?? {}; + const drawingIds = documentDataModel.getDrawingsOrder() ?? Object.keys(drawings); + return drawingIds + .filter((drawingId) => drawings[drawingId]?.drawingType === DrawingTypeEnum.DRAWING_IMAGE) + .map((drawingId) => this._injector.createInstance(FDocumentImage, this, drawingId, this._injector)); + } + + private async _getIntrinsicSize(source: string, imageSourceType: ImageSourceType): Promise> { + const imageIoService = this._injector.has(IImageIoService) ? this._injector.get(IImageIoService) : null; + let resolvedSource = source; + + if (imageSourceType === ImageSourceType.UUID && imageIoService) { + resolvedSource = await imageIoService.getImage(source); + } else if (imageSourceType === ImageSourceType.URL && this._injector.has(IURLImageService)) { + try { + resolvedSource = await this._injector.get(IURLImageService).getImage(source); + } catch { + resolvedSource = source; + } + } + + const { width, height, image } = await getImageSize(resolvedSource); + imageIoService?.addImageSourceCache(source, imageSourceType, image); + return { width, height }; + } +} + +function resolveImageSize( + intrinsicSize: Required, + options: Pick +): Required { + const { width: intrinsicWidth, height: intrinsicHeight } = intrinsicSize; + if (options.width != null && options.height != null) { + return { width: options.width, height: options.height }; + } + + if (options.width != null) { + return { width: options.width, height: intrinsicHeight * options.width / intrinsicWidth }; + } + + if (options.height != null) { + return { width: intrinsicWidth * options.height / intrinsicHeight, height: options.height }; + } + + const scale = Math.min( + 1, + DRAWING_IMAGE_WIDTH_LIMIT / intrinsicWidth, + DRAWING_IMAGE_HEIGHT_LIMIT / intrinsicHeight + ); + return { width: intrinsicWidth * scale, height: intrinsicHeight * scale }; +} + +FDocument.extend(FDocumentImageMixin); +declare module '@univerjs/docs/facade' { + // eslint-disable-next-line ts/naming-convention + interface FDocument extends IFDocumentImageMixin {} +} diff --git a/packages/docs-drawing/src/facade/f-enum.ts b/packages/docs-drawing/src/facade/f-enum.ts new file mode 100644 index 0000000000..519edfbb35 --- /dev/null +++ b/packages/docs-drawing/src/facade/f-enum.ts @@ -0,0 +1,99 @@ +/** + * 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 { ObjectRelativeFromH, ObjectRelativeFromV } from '@univerjs/core'; +import { FEnum } from '@univerjs/core/facade'; +import { TextWrappingStyle } from '@univerjs/docs-drawing'; + +/** + * @ignore + */ +export interface IFDocumentImageEnumMixin { + /** + * Represents the wrapping styles used by docs image operations. + * @example + * ```ts + * const fDocument = univerAPI.getActiveDocument(); + * const image = await fDocument.insertImage({ + * source: 'https://avatars.githubusercontent.com/u/61444807?s=48&v=4', + * imageSourceType: univerAPI.Enum.ImageSourceType.URL, + * width: 320, + * wrappingStyle: univerAPI.Enum.DocsImageWrappingStyle.WRAP_SQUARE, + * textRange: { + * startOffset: 30, + * }, + * }); + * console.log(image); + * ``` + */ + DocsImageWrappingStyle: typeof TextWrappingStyle; + + /** + * Represents the horizontal position types used by docs image operations. + * @example + * ```ts + * const fDocument = univerAPI.getActiveDocument(); + * const image = fDocument.getImages()[0]; + * + * if (image) { + * const success = image.setPositionH({ + * relativeFrom: univerAPI.Enum.DocsImageRelativeFromH.MARGIN, + * posOffset: 100 + * }); + * console.log(success); + * } + * ``` + */ + DocsImageRelativeFromH: typeof ObjectRelativeFromH; + + /** + * Represents the vertical position types used by docs image operations. + * @example + * ```ts + * const fDocument = univerAPI.getActiveDocument(); + * const image = fDocument.getImages()[0]; + * + * if (image) { + * const success = image.setPositionV({ + * relativeFrom: univerAPI.Enum.DocsImageRelativeFromV.MARGIN, + * posOffset: 100 + * }); + * console.log(success); + * } + * ``` + */ + DocsImageRelativeFromV: typeof ObjectRelativeFromV; +} + +export class FDocumentImageEnumMixin extends FEnum implements IFDocumentImageEnumMixin { + override get DocsImageWrappingStyle(): typeof TextWrappingStyle { + return TextWrappingStyle; + } + + override get DocsImageRelativeFromH(): typeof ObjectRelativeFromH { + return ObjectRelativeFromH; + } + + override get DocsImageRelativeFromV(): typeof ObjectRelativeFromV { + return ObjectRelativeFromV; + } +} + +FEnum.extend(FDocumentImageEnumMixin); +declare module '@univerjs/core/facade' { + // eslint-disable-next-line ts/naming-convention + interface FEnum extends IFDocumentImageEnumMixin {} +} diff --git a/packages/docs-drawing/src/facade/index.ts b/packages/docs-drawing/src/facade/index.ts new file mode 100644 index 0000000000..5b732dfe4a --- /dev/null +++ b/packages/docs-drawing/src/facade/index.ts @@ -0,0 +1,21 @@ +/** + * 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 './f-document'; +import './f-enum'; + +export type { IFDocumentInsertImageOptions } from './f-document'; +export { FDocumentImage } from './f-document-image'; diff --git a/packages/docs-drawing/src/index.ts b/packages/docs-drawing/src/index.ts index 2ee785b675..1859cf9ee0 100644 --- a/packages/docs-drawing/src/index.ts +++ b/packages/docs-drawing/src/index.ts @@ -14,6 +14,16 @@ * limitations under the License. */ +export { InsertDocDrawingCommand } from './commands/commands/insert-doc-drawing.command'; +export type { IInsertDocDrawingCommandParams } from './commands/commands/insert-doc-drawing.command'; +export { RemoveDocDrawingCommand } from './commands/commands/remove-doc-drawing.command'; +export type { IRemoveDocDrawingCommandParam, IRemoveDocDrawingCommandParams } from './commands/commands/remove-doc-drawing.command'; +export { SetDocDrawingArrangeCommand } from './commands/commands/set-drawing-arrange.command'; +export type { ISetDocDrawingArrangeCommandParams } from './commands/commands/set-drawing-arrange.command'; +export { UpdateDrawingDocTransformCommand } from './commands/commands/update-doc-drawing-transform.command'; +export type { IDrawingDocTransform, IUpdateDrawingDocTransformCommandParams } from './commands/commands/update-doc-drawing-transform.command'; +export { TextWrappingStyle, UpdateDocDrawingWrappingStyleCommand, WRAPPING_STYLE_TO_LAYOUT_TYPE } from './commands/commands/update-doc-drawing-wrapping-style.command'; +export type { IUpdateDocDrawingWrappingStyleParams } from './commands/commands/update-doc-drawing-wrapping-style.command'; export type { IUniverDocsDrawingConfig } from './config/config'; export { DOCS_DRAWING_PLUGIN, getDocDrawingRenderOrder } from './controllers/doc-drawing.controller'; export type { IDocDrawingModel } from './controllers/doc-drawing.controller'; diff --git a/packages/docs-ui/src/commands/commands/clipboard.inner.command.ts b/packages/docs-ui/src/commands/commands/clipboard.inner.command.ts index 17dfd60d13..fb6d4f5699 100644 --- a/packages/docs-ui/src/commands/commands/clipboard.inner.command.ts +++ b/packages/docs-ui/src/commands/commands/clipboard.inner.command.ts @@ -20,7 +20,6 @@ import type { ICommand, ICustomTable, IDisposable, - IDocumentBody, IDocumentData, IDrawingParam, IMutationInfo, @@ -30,10 +29,12 @@ import type { } from '@univerjs/core'; import type { IRichTextEditingMutationParams } from '@univerjs/docs'; import type { DocumentViewModel, IRectRangeWithStyle, ITextRangeWithStyle } from '@univerjs/engine-render'; +import type { IDocClipboardPasteCustomBlockMapping } from '../../services/clipboard/doc-paste-mutation-adapter.service'; import { BuildTextUtils, CommandType, generateRandomId, + getCustomBlockIdsInSelections, getRichTextEditPath, ICommandService, IUndoRedoService, @@ -48,34 +49,10 @@ import { } from '@univerjs/core'; import { DocSelectionManagerService, RichTextEditingMutation } from '@univerjs/docs'; import { getCustomDecorationAtPosition, getCustomRangeAtPosition } from '../../basics/paragraph'; -import type { IDocClipboardPasteCustomBlockMapping } from '../../services/clipboard/doc-paste-mutation-adapter.service'; import { IDocClipboardPasteAdapterService } from '../../services/clipboard/doc-paste-mutation-adapter.service'; import { getCommandSkeleton } from '../util'; import { getDeleteRowContentActionParams, getDeleteRowsActionsParams, getDeleteTableActionParams } from './table/table'; -export function getCustomBlockIdsInSelections(body: IDocumentBody, selections: ITextRange[]): string[] { - const customBlockIds: string[] = []; - const { customBlocks = [] } = body; - - for (const selection of selections) { - const { startOffset, endOffset } = selection; - - if (startOffset == null || endOffset == null) { - continue; - } - - for (const customBlock of customBlocks) { - const { startIndex } = customBlock; - - if (startIndex >= startOffset && startIndex < endOffset) { - customBlockIds.push(customBlock.blockId); - } - } - } - - return customBlockIds; -} - function hasRangeInTable(ranges: ITextRangeWithStyle[]): boolean { return ranges.some((range) => { const { startNodePosition } = range; diff --git a/packages/docs-ui/src/index.ts b/packages/docs-ui/src/index.ts index ed8ae98df4..2f8646e13b 100644 --- a/packages/docs-ui/src/index.ts +++ b/packages/docs-ui/src/index.ts @@ -33,7 +33,7 @@ export { DocCopyCommand, DocCutCommand, DocPasteCommand } from './commands/comma export { CutContentCommand, InnerPasteCommand } from './commands/commands/clipboard.inner.command'; export type { IInnerPasteCommandParams } from './commands/commands/clipboard.inner.command'; export type { IInnerCutCommandParams } from './commands/commands/clipboard.inner.command'; -export { getCustomBlockIdsInSelections, getCutActionsFromDocRanges } from './commands/commands/clipboard.inner.command'; +export { getCutActionsFromDocRanges } from './commands/commands/clipboard.inner.command'; export { buildMoveDocBlockActions, MoveDocBlockCommand } from './commands/commands/doc-block-move.command'; export type { IMoveDocBlockCommandParams } from './commands/commands/doc-block-move.command'; export { diff --git a/packages/docs/src/index.ts b/packages/docs/src/index.ts index 51e7cbc2fd..99edae6b45 100644 --- a/packages/docs/src/index.ts +++ b/packages/docs/src/index.ts @@ -89,4 +89,4 @@ export { replaceSelectionFactory } from './utils/replace-selection-factory'; export { createSectionColumnProperties } from './utils/section-columns'; export { getTopLevelSectionBreaks } from './utils/sections'; export { buildDocTransform, docDrawingPositionToTransform, transformToDocDrawingPosition } from './utils/transform-position'; -export { consumeContentInsertRange, isHeaderFooterSelection } from './utils/util'; +export { consumeContentInsertRange, getContentInsertRange, isHeaderFooterSelection, normalizeTextRange } from './utils/util'; diff --git a/packages/docs/src/utils/util.ts b/packages/docs/src/utils/util.ts index d51bcf87c6..49be78e3f4 100644 --- a/packages/docs/src/utils/util.ts +++ b/packages/docs/src/utils/util.ts @@ -14,11 +14,13 @@ * limitations under the License. */ -import type { IAccessor } from '@univerjs/core'; +import type { DocumentDataModel, IAccessor, ITextRangeParam } from '@univerjs/core'; import type { ITextRangeWithStyle } from '@univerjs/engine-render'; +import type { IDocContentInsertRange } from '../services/doc-content-insert.service'; +import { IUniverInstanceService, UniverInstanceType } from '@univerjs/core'; import { DocContentInsertService } from '../services/doc-content-insert.service'; -export function consumeContentInsertRange(accessor: IAccessor, unitId: string) { +export function consumeContentInsertRange(accessor: IAccessor, unitId: string): IDocContentInsertRange | null { try { return accessor.get(DocContentInsertService).consumeInsertRange(unitId); } catch { @@ -26,6 +28,36 @@ export function consumeContentInsertRange(accessor: IAccessor, unitId: string) { } } +export function getContentInsertRange(accessor: IAccessor, unitId?: string): (IDocContentInsertRange & { + collapsed: boolean; +}) | null { + const _unitId = unitId ?? accessor.get(IUniverInstanceService).getCurrentUnitOfType(UniverInstanceType.UNIVER_DOC)?.getUnitId(); + if (!_unitId) { + return null; + } + + const insertRange = consumeContentInsertRange(accessor, _unitId); + if (!insertRange) { + return null; + } + + return { + ...insertRange, + collapsed: insertRange?.startOffset === insertRange?.endOffset, + }; +} + export function isHeaderFooterSelection(range?: ITextRangeWithStyle): boolean { return Boolean(range?.segmentId); } + +export function normalizeTextRange(textRange: ITextRangeParam): ITextRangeParam { + const endOffset = textRange.endOffset ?? textRange.startOffset; + + return { + ...textRange, + endOffset, + collapsed: textRange.collapsed ?? textRange.startOffset === endOffset, + segmentId: textRange.segmentId ?? '', + }; +} diff --git a/packages/sheets-drawing-ui/src/commands/commands/delete-drawings.command.ts b/packages/sheets-drawing-ui/src/commands/commands/delete-drawings.command.ts index a59dfe9bd6..9c385d2af2 100644 --- a/packages/sheets-drawing-ui/src/commands/commands/delete-drawings.command.ts +++ b/packages/sheets-drawing-ui/src/commands/commands/delete-drawings.command.ts @@ -15,7 +15,7 @@ */ import type { IAccessor, ICommand } from '@univerjs/core'; -import type { IDeleteDrawingCommandParams, ISheetDrawing } from '@univerjs/sheets-drawing'; +import type { IRemoveSheetDrawingCommandParams, ISheetDrawing } from '@univerjs/sheets-drawing'; import { CommandType, ICommandService } from '@univerjs/core'; import { ISheetDrawingService, RemoveSheetDrawingCommand } from '@univerjs/sheets-drawing'; @@ -44,7 +44,7 @@ export const DeleteDrawingsCommand: ICommand = { drawingType, }; }); - return commandService.executeCommand(RemoveSheetDrawingCommand.id, { + return commandService.executeCommand(RemoveSheetDrawingCommand.id, { unitId, drawings: newDrawings, }); diff --git a/packages/sheets-drawing-ui/src/controllers/sheet-drawing-copy-paste.controller.ts b/packages/sheets-drawing-ui/src/controllers/sheet-drawing-copy-paste.controller.ts index 66683b8dd9..0b35ea96ef 100644 --- a/packages/sheets-drawing-ui/src/controllers/sheet-drawing-copy-paste.controller.ts +++ b/packages/sheets-drawing-ui/src/controllers/sheet-drawing-copy-paste.controller.ts @@ -17,7 +17,7 @@ import type { IMutationInfo, IRange, Nullable } from '@univerjs/core'; import type { IDrawingJsonUndo1 } from '@univerjs/drawing'; import type { IDiscreteRange } from '@univerjs/sheets'; -import type { IDeleteDrawingCommandParams, ISheetDrawing, ISheetImage } from '@univerjs/sheets-drawing'; +import type { IRemoveSheetDrawingCommandParams, ISheetDrawing, ISheetImage } from '@univerjs/sheets-drawing'; import type { IPasteHookValueType, ISheetDiscreteRangeLocation } from '@univerjs/sheets-ui'; import { Disposable, DrawingTypeEnum, generateRandomId, ICommandService, ImageSourceType, Inject } from '@univerjs/core'; import { IDrawingManagerService } from '@univerjs/drawing'; @@ -129,12 +129,12 @@ export class SheetsDrawingCopyPasteController extends Disposable { } if (copyType === COPY_TYPE.CUT) { - const params: IDeleteDrawingCommandParams = { + const params: IRemoveSheetDrawingCommandParams = { unitId, drawings: [drawing], }; // Delete the drawing when it is cut - this._commandService.executeCommand(RemoveSheetDrawingCommand.id, params); + this._commandService.executeCommand(RemoveSheetDrawingCommand.id, params); } setTimeout(() => { diff --git a/packages/sheets-drawing-ui/src/controllers/sheet-drawing-permission.controller.ts b/packages/sheets-drawing-ui/src/controllers/sheet-drawing-permission.controller.ts index 51fdeb6ea2..bb6fdf92a6 100644 --- a/packages/sheets-drawing-ui/src/controllers/sheet-drawing-permission.controller.ts +++ b/packages/sheets-drawing-ui/src/controllers/sheet-drawing-permission.controller.ts @@ -16,8 +16,8 @@ import type { Workbook, Worksheet } from '@univerjs/core'; import type { - IDeleteDrawingCommandParams, - IInsertDrawingCommandParams, + IInsertSheetDrawingCommandParams, + IRemoveSheetDrawingCommandParams, ISetDrawingArrangeCommandParams, ISetDrawingCommandParams, } from '@univerjs/sheets-drawing'; @@ -438,7 +438,7 @@ export class SheetDrawingPermissionController extends Disposable { let subUnitId: string | undefined; if (command.id === InsertSheetDrawingCommand.id || command.id === RemoveSheetDrawingCommand.id || command.id === SetSheetDrawingCommand.id) { - const params = command.params as IInsertDrawingCommandParams | IDeleteDrawingCommandParams | ISetDrawingCommandParams; + const params = command.params as IInsertSheetDrawingCommandParams | IRemoveSheetDrawingCommandParams | ISetDrawingCommandParams; const { drawings } = params; unitId = drawings?.[0]?.unitId; subUnitId = drawings?.[0]?.subUnitId; diff --git a/packages/sheets-drawing-ui/src/controllers/sheet-drawing-update.controller.ts b/packages/sheets-drawing-ui/src/controllers/sheet-drawing-update.controller.ts index 0ab0663c26..23c7fe7e9b 100644 --- a/packages/sheets-drawing-ui/src/controllers/sheet-drawing-update.controller.ts +++ b/packages/sheets-drawing-ui/src/controllers/sheet-drawing-update.controller.ts @@ -19,7 +19,7 @@ import type { IImageData } from '@univerjs/drawing'; import type { IRenderContext, IRenderModule, SpreadsheetSkeleton } from '@univerjs/engine-render'; import type { ISheetLocationBase, WorkbookSelectionModel } from '@univerjs/sheets'; import type { - IInsertDrawingCommandParams, + IInsertSheetDrawingCommandParams, ISetDrawingArrangeCommandParams, ISetDrawingCommandParams, ISheetDrawing, @@ -269,10 +269,10 @@ export class SheetDrawingUpdateController extends Disposable implements IRenderM axisAlignSheetTransform: transformToAxisAlignPosition(newTransform, skeleton) ?? sheetTransform, }; - return this._commandService.executeCommand(InsertSheetDrawingCommand.id, { + return this._commandService.executeCommand(InsertSheetDrawingCommand.id, { unitId, drawings: [sheetDrawingParam], - } as IInsertDrawingCommandParams); + }); } // eslint-disable-next-line max-lines-per-function diff --git a/packages/sheets-drawing-ui/src/facade/f-univer.ts b/packages/sheets-drawing-ui/src/facade/f-univer.ts index 903743b7e1..27b5f69c78 100644 --- a/packages/sheets-drawing-ui/src/facade/f-univer.ts +++ b/packages/sheets-drawing-ui/src/facade/f-univer.ts @@ -15,7 +15,7 @@ */ import type { IDisposable, IDrawingSearch, Injector } from '@univerjs/core'; -import type { IDeleteDrawingCommandParams, IInsertDrawingCommandParams, ISetDrawingCommandParams, ISheetFloatDom } from '@univerjs/sheets-drawing'; +import type { IInsertSheetDrawingCommandParams, IRemoveSheetDrawingCommandParams, ISetDrawingCommandParams, ISheetFloatDom } from '@univerjs/sheets-drawing'; import type { IBeforeFloatDomAddEventParams, IBeforeFloatDomDeleteEventParams, IBeforeFloatDomUpdateEventParams, IFloatDomAddedEventParams, IFloatDomDeletedEventParams, IFloatDomUpdatedEventParams } from './f-event'; import { CanceledError, DrawingTypeEnum, ICommandService, IURLImageService } from '@univerjs/core'; import { FUniver } from '@univerjs/core/facade'; @@ -62,7 +62,7 @@ export class FUniverSheetsDrawingUIMixin extends FUniver implements IFUniverShee () => commandService.beforeCommandExecuted((commandInfo) => { if (commandInfo.id !== InsertSheetDrawingCommand.id) return; - const params = commandInfo.params as IInsertDrawingCommandParams; + const params = commandInfo.params as IInsertSheetDrawingCommandParams; const workbook = this.getActiveWorkbook(); if (workbook == null || params == null) { return; @@ -97,7 +97,7 @@ export class FUniverSheetsDrawingUIMixin extends FUniver implements IFUniverShee () => commandService.onCommandExecuted((commandInfo) => { if (commandInfo.id !== InsertSheetDrawingCommand.id) return; - const params = commandInfo.params as IInsertDrawingCommandParams; + const params = commandInfo.params as IInsertSheetDrawingCommandParams; const workbook = this.getActiveWorkbook(); if (workbook == null || params == null) { return; @@ -208,7 +208,7 @@ export class FUniverSheetsDrawingUIMixin extends FUniver implements IFUniverShee () => commandService.beforeCommandExecuted((commandInfo) => { if (commandInfo.id !== RemoveSheetDrawingCommand.id) return; - const params = commandInfo.params as IDeleteDrawingCommandParams; + const params = commandInfo.params as IRemoveSheetDrawingCommandParams; const workbook = this.getActiveWorkbook(); if (workbook == null || params == null) { return; @@ -247,7 +247,7 @@ export class FUniverSheetsDrawingUIMixin extends FUniver implements IFUniverShee () => commandService.onCommandExecuted((commandInfo) => { if (commandInfo.id !== RemoveSheetDrawingCommand.id) return; - const params = commandInfo.params as IDeleteDrawingCommandParams; + const params = commandInfo.params as IRemoveSheetDrawingCommandParams; const workbook = this.getActiveWorkbook(); if (workbook == null || params == null) { return; diff --git a/packages/sheets-drawing-ui/src/services/canvas-float-dom-manager.service.ts b/packages/sheets-drawing-ui/src/services/canvas-float-dom-manager.service.ts index 45c59baa4a..3b996ef918 100644 --- a/packages/sheets-drawing-ui/src/services/canvas-float-dom-manager.service.ts +++ b/packages/sheets-drawing-ui/src/services/canvas-float-dom-manager.service.ts @@ -42,7 +42,7 @@ import type { } from '@univerjs/sheets'; import type { IFloatDomData, - IInsertDrawingCommandParams, + IInsertSheetDrawingCommandParams, ISetDrawingCommandParams, ISheetDrawing, ISheetDrawingPosition, @@ -1809,10 +1809,10 @@ export class SheetCanvasFloatDomManagerService extends Disposable { // mutation // ---> this._drawingManagerService.add$.subscribe - this._commandService.executeCommand(InsertSheetDrawingCommand.id, { + this._commandService.executeCommand(InsertSheetDrawingCommand.id, { unitId, drawings: [sheetDrawingParam], - } as IInsertDrawingCommandParams); + }); this._add$.next({ unitId, subUnitId, id }); diff --git a/packages/sheets-drawing/src/commands/commands/insert-sheet-drawing.command.ts b/packages/sheets-drawing/src/commands/commands/insert-sheet-drawing.command.ts index 6d69232a54..d78354e65c 100644 --- a/packages/sheets-drawing/src/commands/commands/insert-sheet-drawing.command.ts +++ b/packages/sheets-drawing/src/commands/commands/insert-sheet-drawing.command.ts @@ -28,7 +28,7 @@ import { ISheetDrawingService } from '../../services/sheet-drawing.service'; import { DrawingApplyType, SetDrawingApplyMutation } from '../mutations/set-drawing-apply.mutation'; import { ClearSheetDrawingTransformerOperation } from '../operations/clear-drawing-transformer.operation'; -export interface IInsertDrawingCommandParams { +export interface IInsertSheetDrawingCommandParams { unitId: string; drawings: ISheetDrawing[]; } @@ -36,7 +36,7 @@ export interface IInsertDrawingCommandParams { export const InsertSheetDrawingCommand: ICommand = { id: 'sheet.command.insert-sheet-image', type: CommandType.COMMAND, - handler: (accessor: IAccessor, params?: IInsertDrawingCommandParams) => { + handler: (accessor: IAccessor, params?: IInsertSheetDrawingCommandParams) => { if (!params) return false; const commandService = accessor.get(ICommandService); diff --git a/packages/sheets-drawing/src/commands/commands/remove-sheet-drawing.command.ts b/packages/sheets-drawing/src/commands/commands/remove-sheet-drawing.command.ts index 9444620a79..33ed795c1c 100644 --- a/packages/sheets-drawing/src/commands/commands/remove-sheet-drawing.command.ts +++ b/packages/sheets-drawing/src/commands/commands/remove-sheet-drawing.command.ts @@ -27,22 +27,22 @@ import { ISheetDrawingService } from '../../services/sheet-drawing.service'; import { DrawingApplyType, SetDrawingApplyMutation } from '../mutations/set-drawing-apply.mutation'; import { ClearSheetDrawingTransformerOperation } from '../operations/clear-drawing-transformer.operation'; -export interface IDeleteDrawingCommandParam { +export interface IRemoveSheetDrawingCommandParam { unitId: string; subUnitId: string; drawingId: string; drawingType: DrawingTypeEnum; } -export interface IDeleteDrawingCommandParams { +export interface IRemoveSheetDrawingCommandParams { unitId: string; - drawings: IDeleteDrawingCommandParam[]; + drawings: IRemoveSheetDrawingCommandParam[]; } export const RemoveSheetDrawingCommand: ICommand = { id: 'sheet.command.remove-sheet-image', type: CommandType.COMMAND, - handler: (accessor: IAccessor, params?: IDeleteDrawingCommandParams) => { + handler: (accessor: IAccessor, params?: IRemoveSheetDrawingCommandParams) => { if (!params) return false; const commandService = accessor.get(ICommandService); diff --git a/packages/sheets-drawing/src/facade/f-univer.ts b/packages/sheets-drawing/src/facade/f-univer.ts index 706442c1b3..18212759e0 100644 --- a/packages/sheets-drawing/src/facade/f-univer.ts +++ b/packages/sheets-drawing/src/facade/f-univer.ts @@ -15,7 +15,7 @@ */ import type { IDrawingSearch, Injector } from '@univerjs/core'; -import type { IDeleteDrawingCommandParams, IInsertDrawingCommandParams, ISetDrawingCommandParams, ISheetImage } from '@univerjs/sheets-drawing'; +import type { IInsertSheetDrawingCommandParams, IRemoveSheetDrawingCommandParams, ISetDrawingCommandParams, ISheetImage } from '@univerjs/sheets-drawing'; import type { IBeforeOverGridImageChangeEventParams, IBeforeOverGridImageChangeParamObject, @@ -50,7 +50,7 @@ export class FUniverSheetsDrawingMixin extends FUniver { () => commandService.beforeCommandExecuted((commandInfo) => { if (commandInfo.id !== InsertSheetDrawingCommand.id) return; - const params = commandInfo.params as IInsertDrawingCommandParams; + const params = commandInfo.params as IInsertSheetDrawingCommandParams; const workbook = this.getActiveWorkbook(); if (workbook == null || params == null) { return; @@ -77,7 +77,7 @@ export class FUniverSheetsDrawingMixin extends FUniver { () => commandService.beforeCommandExecuted((commandInfo) => { if (commandInfo.id !== RemoveSheetDrawingCommand.id) return; - const params = commandInfo.params as IDeleteDrawingCommandParams; + const params = commandInfo.params as IRemoveSheetDrawingCommandParams; const workbook = this.getActiveWorkbook(); if (workbook == null || params == null) { return; @@ -186,7 +186,7 @@ export class FUniverSheetsDrawingMixin extends FUniver { () => commandService.onCommandExecuted((commandInfo) => { if (commandInfo.id !== InsertSheetDrawingCommand.id) return; - const params = commandInfo.params as IInsertDrawingCommandParams; + const params = commandInfo.params as IInsertSheetDrawingCommandParams; const workbook = this.getActiveWorkbook(); if (workbook == null || params == null) { return; @@ -208,7 +208,7 @@ export class FUniverSheetsDrawingMixin extends FUniver { () => commandService.onCommandExecuted((commandInfo) => { if (commandInfo.id !== RemoveSheetDrawingCommand.id) return; - const params = commandInfo.params as IDeleteDrawingCommandParams; + const params = commandInfo.params as IRemoveSheetDrawingCommandParams; const workbook = this.getActiveWorkbook(); if (workbook == null || params == null) { return; diff --git a/packages/sheets-drawing/src/index.ts b/packages/sheets-drawing/src/index.ts index 8a6b6ce613..d41b8eb5e2 100644 --- a/packages/sheets-drawing/src/index.ts +++ b/packages/sheets-drawing/src/index.ts @@ -20,12 +20,9 @@ export { transformToDrawingPosition, } from './basics/transform-position'; export { InsertSheetDrawingCommand } from './commands/commands/insert-sheet-drawing.command'; -export type { IInsertDrawingCommandParams } from './commands/commands/insert-sheet-drawing.command'; +export type { IInsertSheetDrawingCommandParams } from './commands/commands/insert-sheet-drawing.command'; export { RemoveSheetDrawingCommand } from './commands/commands/remove-sheet-drawing.command'; -export type { - IDeleteDrawingCommandParam, - IDeleteDrawingCommandParams, -} from './commands/commands/remove-sheet-drawing.command'; +export type { IRemoveSheetDrawingCommandParam, IRemoveSheetDrawingCommandParams } from './commands/commands/remove-sheet-drawing.command'; export { SetDrawingArrangeCommand } from './commands/commands/set-drawing-arrange.command'; export type { ISetDrawingArrangeCommandParams } from './commands/commands/set-drawing-arrange.command'; export { SetSheetDrawingCommand } from './commands/commands/set-sheet-drawing.command'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 34410a6bb5..d4d38bafe5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -952,6 +952,9 @@ importers: '@univerjs/core': specifier: workspace:* version: link:../core + '@univerjs/docs': + specifier: workspace:* + version: link:../docs '@univerjs/drawing': specifier: workspace:* version: link:../drawing @@ -959,6 +962,9 @@ importers: '@univerjs-infra/shared': specifier: workspace:* version: link:../../common/shared + '@univerjs/engine-render': + specifier: workspace:* + version: link:../engine-render typescript: specifier: ^6.0.3 version: 6.0.3 diff --git a/presets/packages/preset-docs-drawing/src/preset.ts b/presets/packages/preset-docs-drawing/src/preset.ts index 392dc17092..6ced50ac1a 100644 --- a/presets/packages/preset-docs-drawing/src/preset.ts +++ b/presets/packages/preset-docs-drawing/src/preset.ts @@ -21,6 +21,10 @@ import { UniverDocsDrawingUIPlugin } from '@univerjs/docs-drawing-ui'; import { UniverDrawingPlugin } from '@univerjs/drawing'; import { UniverDrawingUIPlugin } from '@univerjs/drawing-ui'; +import '@univerjs/docs-drawing/facade'; + +export type * from '@univerjs/docs-drawing/facade'; + export interface IUniverDocsDrawingPresetConfig { collaboration?: boolean; }