diff --git a/e2e/visual-comparison/docs/docs-visual-comparison.spec.ts-snapshots/default-doc-ci-chromium-linux.png b/e2e/visual-comparison/docs/docs-visual-comparison.spec.ts-snapshots/default-doc-ci-chromium-linux.png index a9d581e796..94a5378fec 100644 Binary files a/e2e/visual-comparison/docs/docs-visual-comparison.spec.ts-snapshots/default-doc-ci-chromium-linux.png and b/e2e/visual-comparison/docs/docs-visual-comparison.spec.ts-snapshots/default-doc-ci-chromium-linux.png differ diff --git a/packages/core/src/docs/data-model/document-data-model.ts b/packages/core/src/docs/data-model/document-data-model.ts index f8d13cb4e3..35c54cc1e4 100644 --- a/packages/core/src/docs/data-model/document-data-model.ts +++ b/packages/core/src/docs/data-model/document-data-model.ts @@ -132,6 +132,10 @@ class DocumentDataModelSimple extends UnitModel> = { + [PaperType.A3]: { + width: 1123, + height: 1587, + }, + [PaperType.A4]: { + width: 794, + height: 1124, + }, + [PaperType.A5]: { + width: 559, + height: 794, + }, + [PaperType.B4]: { + width: 944, + height: 1344, + }, + [PaperType.B5]: { + width: 665, + height: 944, + }, + [PaperType.Executive]: { + width: 696, + height: 1008, + }, + [PaperType.Folio]: { + width: 816, + height: 1248, + }, + [PaperType.Legal]: { + width: 816, + height: 1344, + }, + [PaperType.Letter]: { + width: 816, + height: 1056, + }, + [PaperType.Statement]: { + width: 528, + height: 816, + }, + [PaperType.Tabloid]: { + width: 1056, + height: 1632, + }, +}; diff --git a/packages/core/src/types/interfaces/i-document-data.ts b/packages/core/src/types/interfaces/i-document-data.ts index 1b04f48792..0f7b02461a 100644 --- a/packages/core/src/types/interfaces/i-document-data.ts +++ b/packages/core/src/types/interfaces/i-document-data.ts @@ -1081,4 +1081,31 @@ export enum PageOrientType { LANDSCAPE, } +export enum PaperType { + Letter = 'Letter', + Tabloid = 'Tabloid', + Legal = 'Legal', + Statement = 'Statement', + Executive = 'Executive', + Folio = 'Folio', + A3 = 'A3', + A4 = 'A4', + A5 = 'A5', + B4 = 'B4', + B5 = 'B5', +} + +export const PAPER_TYPES: PaperType[] = [ + PaperType.A3, + PaperType.A4, + PaperType.A5, + PaperType.B4, + PaperType.B5, + PaperType.Letter, + PaperType.Tabloid, + PaperType.Legal, + PaperType.Statement, + PaperType.Executive, + PaperType.Folio, +]; // #region - tech dept diff --git a/packages/design/src/components/confirm/Confirm.tsx b/packages/design/src/components/confirm/Confirm.tsx index 2ffdf22cf6..2953078aec 100644 --- a/packages/design/src/components/confirm/Confirm.tsx +++ b/packages/design/src/components/confirm/Confirm.tsx @@ -77,7 +77,6 @@ function Footer(props: { locale: ILocale['design']; cancelText?: string; confirm export function Confirm(props: IConfirmProps) { const { children, visible = false, title, cancelText, confirmText, width, onClose, onConfirm } = props; - const { locale } = useContext(ConfigContext); return ( diff --git a/packages/docs-drawing-ui/src/controllers/doc-drawing-printing.controller.tsx b/packages/docs-drawing-ui/src/controllers/doc-drawing-printing.controller.tsx new file mode 100644 index 0000000000..778e950391 --- /dev/null +++ b/packages/docs-drawing-ui/src/controllers/doc-drawing-printing.controller.tsx @@ -0,0 +1,118 @@ +/** + * 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 { IDocFloatDom } from '@univerjs/docs-drawing'; +import type { IPrintingFloatDomProps } from '../views/printing-float-dom'; +import { Disposable, DrawingTypeEnum, Inject, Injector, PRINT_CHART_COMPONENT_KEY } from '@univerjs/core'; +import { render, unmount } from '@univerjs/design'; +import { DocPrintInterceptorService } from '@univerjs/docs-ui'; +import { IDrawingManagerService } from '@univerjs/drawing'; +import { DrawingRenderService } from '@univerjs/drawing-ui'; +import { ComponentManager, connectInjector } from '@univerjs/ui'; +import { DocPrintingFloatDom } from '../views/printing-float-dom'; + +export class DocDrawingPrintingController extends Disposable { + constructor( + @Inject(DocPrintInterceptorService) private readonly _docPrintInterceptorService: DocPrintInterceptorService, + @Inject(DrawingRenderService) private readonly _drawingRenderService: DrawingRenderService, + @IDrawingManagerService private readonly _drawingManagerService: IDrawingManagerService, + @Inject(ComponentManager) private readonly _componetManager: ComponentManager, + @Inject(Injector) private readonly _injector: Injector + ) { + super(); + + this._initPrinting(); + this._initPrintingDom(); + } + + private _initPrinting() { + this.disposeWithMe( + this._docPrintInterceptorService.interceptor.intercept( + this._docPrintInterceptorService.interceptor.getInterceptPoints().PRINTING_COMPONENT_COLLECT, + { + handler: (_param, pos, next) => { + const { unitId, scene } = pos; + const unitData = this._drawingManagerService.getDrawingDataForUnit(unitId); + const subUnitData = unitData?.[unitId]; + if (subUnitData) { + subUnitData.order.forEach((id) => { + const drawing = subUnitData.data[id]; + if (drawing.drawingType !== DrawingTypeEnum.DRAWING_CHART && drawing.drawingType !== DrawingTypeEnum.DRAWING_DOM) { + this._drawingRenderService.renderDrawing(drawing, scene); + } + }); + } + + return next(); + }, + } + ) + ); + } + + private _initPrintingDom() { + this.disposeWithMe( + this._docPrintInterceptorService.interceptor.intercept( + this._docPrintInterceptorService.interceptor.getInterceptPoints().PRINTING_DOM_COLLECT, + { + handler: (disposableCollection, pos, next) => { + const { unitId } = pos; + const unitData = this._drawingManagerService.getDrawingDataForUnit(unitId); + const subUnitData = unitData?.[unitId]; + if (subUnitData) { + const floatDomInfos = subUnitData.order.map((id) => { + const drawing = subUnitData.data[id]; + if (drawing.drawingType === DrawingTypeEnum.DRAWING_CHART) { + return { + ...drawing, + componentKey: this._componetManager.get(PRINT_CHART_COMPONENT_KEY) as any, + }; + } + + if (drawing.drawingType === DrawingTypeEnum.DRAWING_DOM) { + return { + ...drawing, + componentKey: this._componetManager.get((drawing as IDocFloatDom).componentKey) as any, + }; + } + + return null; + }).filter(Boolean) as IDocFloatDom[]; + const PrintingFloatDomInjector = connectInjector(DocPrintingFloatDom, this._injector) as React.ComponentType; + + render( + , + pos.root + ); + + disposableCollection?.add(() => { + unmount(pos.root); + }); + + return next(disposableCollection); + } + }, + } + ) + ); + } +} 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 e8e9d52422..038bb2f996 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 @@ -18,7 +18,7 @@ import type { DocumentDataModel, IDisposable, IDrawingSearch, Nullable } from '@ import type { IDocFloatDom } from '@univerjs/docs-drawing'; import type { ISetDocZoomRatioOperationParams } from '@univerjs/docs-ui'; import type { IDocFloatDomDataBase } from '@univerjs/drawing'; -import type { IRender, Rect } from '@univerjs/engine-render'; +import type { IBoundRectNoAngle, IRender, Rect, Scene } from '@univerjs/engine-render'; import type { IFloatDomLayout } from '@univerjs/ui'; import type { IInsertDrawingCommandParams } from '../commands/commands/interfaces'; import { Disposable, DisposableCollection, DrawingTypeEnum, fromEventSubject, generateRandomId, ICommandService, Inject, IUniverInstanceService, ObjectRelativeFromH, ObjectRelativeFromV, PositionedObjectLayoutType, toDisposable, UniverInstanceType } from '@univerjs/core'; @@ -31,13 +31,16 @@ import { CanvasFloatDomService } from '@univerjs/ui'; import { BehaviorSubject, map, of, switchMap } from 'rxjs'; import { InsertDocDrawingCommand } from '../commands/commands/insert-doc-drawing.command'; -function calcDocFloatDomPosition( - object: Rect, - renderUnit: IRender +export function calcDocFloatDomPositionByRect( + rect: IBoundRectNoAngle, + scene: Scene, + opacity = 1, + angle = 0 ): IFloatDomLayout { - const { top, left, width, height, angle, opacity } = object; - // const - const scene = renderUnit.scene; + const { top, left, bottom, right } = rect; + const width = right - left; + const height = bottom - top; + const viewMain = scene.getViewport(VIEWPORT_KEY.VIEW_MAIN)!; const { viewportScrollX, viewportScrollY } = viewMain; const { scaleX, scaleY } = scene.getAncestorScale(); @@ -58,6 +61,14 @@ function calcDocFloatDomPosition( }; } +function calcDocFloatDomPosition( + object: Rect, + renderUnit: IRender +): IFloatDomLayout { + const { top, left, width, height, angle, opacity } = object; + return calcDocFloatDomPositionByRect({ top, left, bottom: top + height, right: left + width }, renderUnit.scene, opacity, angle); +} + interface ICanvasFloatDomInfo { position$: BehaviorSubject; dispose: IDisposable; diff --git a/packages/docs-drawing-ui/src/plugin.ts b/packages/docs-drawing-ui/src/plugin.ts index b754952f4b..cfad3f2593 100644 --- a/packages/docs-drawing-ui/src/plugin.ts +++ b/packages/docs-drawing-ui/src/plugin.ts @@ -23,9 +23,10 @@ import { UniverDrawingUIPlugin } from '@univerjs/drawing-ui'; import { IRenderManagerService } from '@univerjs/engine-render'; import { UniverUIPlugin } from '@univerjs/ui'; import { defaultPluginConfig, DOCS_DRAWING_UI_PLUGIN_CONFIG_KEY } from './controllers/config.schema'; -import { DocDrawingAddRemoveController } from './controllers/doc-drawing-notification.controller'; -import { DocDrawingTransformerController } from './controllers/doc-drawing-transformer-update.controller'; import { DocDrawingUIController } from './controllers/doc-drawing.controller'; +import { DocDrawingAddRemoveController } from './controllers/doc-drawing-notification.controller'; +import { DocDrawingPrintingController } from './controllers/doc-drawing-printing.controller'; +import { DocDrawingTransformerController } from './controllers/doc-drawing-transformer-update.controller'; import { DocFloatDomController } from './controllers/doc-float-dom.controller'; import { DocDrawingPopupMenuController } from './controllers/drawing-popup-menu.controller'; import { DocDrawingTransformUpdateController } from './controllers/render-controllers/doc-drawing-transform-update.controller'; @@ -64,6 +65,7 @@ export class UniverDocsDrawingUIPlugin extends Plugin { [DocDrawingAddRemoveController], [DocRefreshDrawingsService], [DocFloatDomController], + [DocDrawingPrintingController], ]; dependencies.forEach((dependency) => this._injector.add(dependency)); @@ -78,6 +80,7 @@ export class UniverDocsDrawingUIPlugin extends Plugin { this._injector.get(DocDrawingAddRemoveController); this._injector.get(DocDrawingUIController); this._injector.get(DocDrawingTransformerController); + this._injector.get(DocDrawingPrintingController); } override onRendered(): void { diff --git a/packages/docs-drawing-ui/src/views/printing-float-dom/index.tsx b/packages/docs-drawing-ui/src/views/printing-float-dom/index.tsx new file mode 100644 index 0000000000..bbc8933fce --- /dev/null +++ b/packages/docs-drawing-ui/src/views/printing-float-dom/index.tsx @@ -0,0 +1,70 @@ +/** + * 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 { IDocFloatDom } from '@univerjs/docs-drawing'; +import type { DocumentSkeleton, Scene } from '@univerjs/engine-render'; +import type { IFloatDom, IFloatDomLayout } from '@univerjs/ui'; +import { PrintFloatDomSingle } from '@univerjs/ui'; +import { useMemo } from 'react'; +import { BehaviorSubject } from 'rxjs'; +import { calcDocFloatDomPositionByRect } from '../../controllers/doc-float-dom.controller'; + +export interface IPrintingFloatDomProps { + floatDomInfos: IDocFloatDom[]; + scene: Scene; + skeleton: DocumentSkeleton; + unitId: string; + offset: { x: number; y: number }; +}; + +export const DocPrintingFloatDom = (props: IPrintingFloatDomProps) => { + const { floatDomInfos, scene, offset } = props; + const floatDomParams = useMemo(() => floatDomInfos.map((info) => { + const { width, height, left, top } = info.transform!; + const offsetBound = calcDocFloatDomPositionByRect( + { + left: left ?? 0 - offset.x, + right: (left ?? 0) + (width ?? 0) - offset.x, + top: top ?? 0 - offset.y, + bottom: (top ?? 0) + (height ?? 0) - offset.y, + }, + scene + ); + + const domPos: IFloatDomLayout = offsetBound; + const floatDom: IFloatDom & { position: IFloatDomLayout } = { + position$: new BehaviorSubject(domPos), + position: domPos, + id: info.drawingId, + componentKey: info.componentKey, + onPointerMove: () => { }, + onPointerDown: () => { }, + onPointerUp: () => { }, + onWheel: () => { }, + unitId: info.unitId, + }; + + return [info.drawingId, floatDom] as const; + }), [floatDomInfos, scene, offset]); + + return ( +
+ {floatDomParams.map(([id, floatDom]) => ( + + ))} +
+ ); +}; diff --git a/packages/docs-ui/src/commands/commands/doc-page-setup.command.ts b/packages/docs-ui/src/commands/commands/doc-page-setup.command.ts new file mode 100644 index 0000000000..e4de7f056f --- /dev/null +++ b/packages/docs-ui/src/commands/commands/doc-page-setup.command.ts @@ -0,0 +1,122 @@ +/** + * 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, ICommand, ICommandInfo, ISize, JSONXActions, PageOrientType } from '@univerjs/core'; +import type { IRichTextEditingMutationParams } from '@univerjs/docs'; +import { CommandType, ICommandService, IUniverInstanceService, JSONX, UniverInstanceType } from '@univerjs/core'; +import { RichTextEditingMutation } from '@univerjs/docs'; + +export interface IDocPageSetupCommandParams { + pageSize: ISize; + pageOrient: PageOrientType; + marginTop: number; + marginBottom: number; + marginLeft: number; + marginRight: number; +} + +export const DocPageSetupCommand: ICommand = { + id: 'docs.command.page-setup', + type: CommandType.COMMAND, + // eslint-disable-next-line complexity, max-lines-per-function + handler: (accessor, params) => { + if (!params) return false; + const univerInstanceService = accessor.get(IUniverInstanceService); + const commandService = accessor.get(ICommandService); + const docDataModel = univerInstanceService.getCurrentUnitOfType(UniverInstanceType.UNIVER_DOC); + if (!docDataModel) return false; + const { marginLeft, marginRight, marginBottom, marginTop, pageOrient, pageSize } = params; + const jsonX = JSONX.getInstance(); + const documentStyle = docDataModel.getDocumentStyle(); + const { + marginBottom: oldMarginBottom, + marginLeft: oldMarginLeft, + marginRight: oldMarginRight, + marginTop: oldMarginTop, + pageOrient: oldPageOrient, + pageSize: oldPageSize, + } = documentStyle; + const rawActions: JSONXActions = []; + + if (oldMarginBottom === undefined) { + const action = jsonX.insertOp(['documentStyle', 'marginBottom'], marginBottom); + action && rawActions.push(action); + } else { + const action = jsonX.replaceOp(['documentStyle', 'marginBottom'], oldMarginBottom, marginBottom); + action && rawActions.push(action); + } + + if (oldMarginLeft === undefined) { + const action = jsonX.insertOp(['documentStyle', 'marginLeft'], marginLeft); + action && rawActions.push(action); + } else { + const action = jsonX.replaceOp(['documentStyle', 'marginLeft'], oldMarginLeft, marginLeft); + action && rawActions.push(action); + } + + if (oldMarginRight === undefined) { + const action = jsonX.insertOp(['documentStyle', 'marginRight'], marginRight); + action && rawActions.push(action); + } else { + const action = jsonX.replaceOp(['documentStyle', 'marginRight'], oldMarginRight, marginRight); + action && rawActions.push(action); + } + + if (oldMarginTop === undefined) { + const action = jsonX.insertOp(['documentStyle', 'marginTop'], marginTop); + action && rawActions.push(action); + } else { + const action = jsonX.replaceOp(['documentStyle', 'marginTop'], oldMarginTop, marginTop); + action && rawActions.push(action); + } + + if (oldPageSize === undefined) { + const action = jsonX.insertOp(['documentStyle', 'pageSize'], pageSize); + action && rawActions.push(action); + } else { + const action = jsonX.replaceOp(['documentStyle', 'pageSize'], oldPageSize, pageSize); + action && rawActions.push(action); + } + + if (oldPageOrient === undefined) { + const action = jsonX.insertOp(['documentStyle', 'pageOrient'], pageOrient); + action && rawActions.push(action); + } else { + const action = jsonX.replaceOp(['documentStyle', 'pageOrient'], oldPageOrient, pageOrient); + action && rawActions.push(action); + } + + const doMutation: ICommandInfo = { + id: RichTextEditingMutation.id, + params: { + unitId: docDataModel.getUnitId(), + actions: [], + textRanges: undefined, + }, + }; + + 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); + + return Boolean(result); + }, +}; diff --git a/packages/docs-ui/src/commands/operations/open-page-setting.operation.ts b/packages/docs-ui/src/commands/operations/open-page-setting.operation.ts new file mode 100644 index 0000000000..436e926b6a --- /dev/null +++ b/packages/docs-ui/src/commands/operations/open-page-setting.operation.ts @@ -0,0 +1,60 @@ +/** + * 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 { ICommand, PaperType } from '@univerjs/core'; +import type { IDocPageSetupCommandParams } from '../commands/doc-page-setup.command'; +import { CommandType, ICommandService, LocaleService, PAGE_SIZE } from '@univerjs/core'; +import { IConfirmService } from '@univerjs/ui'; +import { PAGE_SETTING_COMPONENT_ID } from '../../views/page-settings'; +import { DocPageSetupCommand } from '../commands/doc-page-setup.command'; + +export const DocOpenPageSettingCommand: ICommand = { + id: 'docs.operation.open-page-setting', + type: CommandType.OPERATION, + handler: (accessor) => { + const confirmService = accessor.get(IConfirmService); + const commandService = accessor.get(ICommandService); + const localeService = accessor.get(LocaleService); + const disposable = confirmService.open({ + id: PAGE_SETTING_COMPONENT_ID, + title: { + label: localeService.t('page-settings.document-setting'), + }, + children: { + label: PAGE_SETTING_COMPONENT_ID, + }, + width: 528, + onClose: () => { + disposable.dispose(); + }, + onConfirm: (result) => { + disposable.dispose(); + if (!result) return; + const paperSize = PAGE_SIZE[result.paperSize as PaperType]; + commandService.executeCommand(DocPageSetupCommand.id, { + pageOrient: result.orientation, + marginTop: result.margins.top, + marginBottom: result.margins.bottom, + marginLeft: result.margins.left, + marginRight: result.margins.right, + pageSize: paperSize, + } as IDocPageSetupCommandParams); + }, + }); + + return true; + }, +}; diff --git a/packages/docs-ui/src/controllers/doc-ui.controller.ts b/packages/docs-ui/src/controllers/doc-ui.controller.ts index e43102e8d0..14e91ab32a 100644 --- a/packages/docs-ui/src/controllers/doc-ui.controller.ts +++ b/packages/docs-ui/src/controllers/doc-ui.controller.ts @@ -25,7 +25,7 @@ import { } from '@univerjs/core'; import { IRenderManagerService } from '@univerjs/engine-render'; -import { CutSingle, DeleteSingle, TodoList } from '@univerjs/icons'; +import { CutSingle, DeleteSingle, DocSettingSingle, TodoList } from '@univerjs/icons'; import { BuiltInUIPart, ComponentManager, connectInjector, ILayoutService, IMenuManagerService, IShortcutService, IUIPartsService } from '@univerjs/ui'; import { CoreHeaderFooterCommand, OpenHeaderFooterPanelCommand } from '../commands/commands/doc-header-footer.command'; import { SidebarDocHeaderFooterPanelOperation } from '../commands/operations/doc-header-footer-panel.operation'; @@ -56,6 +56,7 @@ import { UnderlineShortCut, } from '../shortcuts/toolbar.shortcut'; import { DocFooter } from '../views/doc-footer'; +import { PAGE_SETTING_COMPONENT_ID, PageSettings } from '../views/page-settings'; import { DocSideMenu } from '../views/side-menu'; import { menuSchema } from './menu.schema'; @@ -88,6 +89,8 @@ export class DocUIController extends Disposable { this.disposeWithMe(componentManager.register('doc.paragraph.menu', ParagraphMenu)); this.disposeWithMe(componentManager.register('CutSingle', CutSingle)); this.disposeWithMe(componentManager.register('DeleteSingle', DeleteSingle)); + this.disposeWithMe(componentManager.register(PAGE_SETTING_COMPONENT_ID, PageSettings)); + this.disposeWithMe(componentManager.register('DocumentSettingSingle', DocSettingSingle)); } private _initUiParts() { diff --git a/packages/docs-ui/src/controllers/menu.schema.ts b/packages/docs-ui/src/controllers/menu.schema.ts index 4856ad9ab2..2c24d06635 100644 --- a/packages/docs-ui/src/controllers/menu.schema.ts +++ b/packages/docs-ui/src/controllers/menu.schema.ts @@ -20,8 +20,8 @@ import { DocCopyCommand, DocCopyCurrentParagraphCommand, DocCutCommand, DocCutCu import { DeleteCurrentParagraphCommand, DeleteLeftCommand } from '../commands/commands/doc-delete.command'; import { OpenHeaderFooterPanelCommand } from '../commands/commands/doc-header-footer.command'; import { HorizontalLineCommand, InsertHorizontalLineBellowCommand } from '../commands/commands/doc-horizontal-line.command'; -import { ResetInlineFormatTextBackgroundColorCommand, SetInlineFormatBoldCommand, SetInlineFormatFontFamilyCommand, SetInlineFormatFontSizeCommand, SetInlineFormatItalicCommand, SetInlineFormatStrikethroughCommand, SetInlineFormatSubscriptCommand, SetInlineFormatSuperscriptCommand, SetInlineFormatTextBackgroundColorCommand, SetInlineFormatTextColorCommand, SetInlineFormatUnderlineCommand } from '../commands/commands/inline-format.command'; +import { ResetInlineFormatTextBackgroundColorCommand, SetInlineFormatBoldCommand, SetInlineFormatFontFamilyCommand, SetInlineFormatFontSizeCommand, SetInlineFormatItalicCommand, SetInlineFormatStrikethroughCommand, SetInlineFormatSubscriptCommand, SetInlineFormatSuperscriptCommand, SetInlineFormatTextBackgroundColorCommand, SetInlineFormatTextColorCommand, SetInlineFormatUnderlineCommand } from '../commands/commands/inline-format.command'; import { BulletListCommand, CheckListCommand, InsertBulletListBellowCommand, InsertCheckListBellowCommand, InsertOrderListBellowCommand, OrderListCommand } from '../commands/commands/list.command'; import { AlignCenterCommand, AlignJustifyCommand, AlignLeftCommand, AlignRightCommand } from '../commands/commands/paragraph-align.command'; import { H1HeadingCommand, H2HeadingCommand, H3HeadingCommand, H4HeadingCommand, H5HeadingCommand, NormalTextHeadingCommand, SetParagraphNamedStyleCommand } from '../commands/commands/set-heading.command'; @@ -30,6 +30,7 @@ import { DocTableDeleteColumnsCommand, DocTableDeleteRowsCommand, DocTableDelete import { DocTableInsertColumnLeftCommand, DocTableInsertColumnRightCommand, DocTableInsertRowAboveCommand, DocTableInsertRowBellowCommand } from '../commands/commands/table/doc-table-insert.command'; import { DocCreateTableOperation } from '../commands/operations/doc-create-table.operation'; import { DocParagraphSettingPanelOperation } from '../commands/operations/doc-paragraph-setting-panel.operation'; +import { DocOpenPageSettingCommand } from '../commands/operations/open-page-setting.operation'; import { CopyMenuFactory, CutMenuFactory, @@ -66,6 +67,7 @@ import { InsertTableMenuFactory, ItalicMenuItemFactory, OrderListMenuItemFactory, + PageSettingMenuItemFactory, ResetBackgroundColorMenuItemFactory, StrikeThroughMenuItemFactory, SubscriptMenuItemFactory, @@ -177,6 +179,10 @@ export const menuSchema: MenuSchemaType = { order: 11, menuItemFactory: DocSwitchModeMenuItemFactory, }, + [DocOpenPageSettingCommand.id]: { + order: 12, + menuItemFactory: PageSettingMenuItemFactory, + }, }, [ContextMenuPosition.MAIN_AREA]: { [ContextMenuGroup.FORMAT]: { diff --git a/packages/docs-ui/src/controllers/menu/menu.ts b/packages/docs-ui/src/controllers/menu/menu.ts index e76df1a338..f73d99ddb5 100644 --- a/packages/docs-ui/src/controllers/menu/menu.ts +++ b/packages/docs-ui/src/controllers/menu/menu.ts @@ -60,6 +60,7 @@ import { AlignCenterCommand, AlignJustifyCommand, AlignLeftCommand, AlignOperati import { SetParagraphNamedStyleCommand } from '../../commands/commands/set-heading.command'; import { SwitchDocModeCommand } from '../../commands/commands/switch-doc-mode.command'; import { DocCreateTableOperation } from '../../commands/operations/doc-create-table.operation'; +import { DocOpenPageSettingCommand } from '../../commands/operations/open-page-setting.operation'; import { getCommandSkeleton } from '../../commands/util'; import { COLOR_PICKER_COMPONENT } from '../../components/color-picker'; import { FONT_FAMILY_COMPONENT, FONT_FAMILY_ITEM_COMPONENT } from '../../components/font-family'; @@ -1096,3 +1097,13 @@ export function getParagraphStyleAtCursor(accessor: IAccessor) { return null; } + +export function PageSettingMenuItemFactory(accessor: IAccessor): IMenuButtonItem { + return { + id: DocOpenPageSettingCommand.id, + type: MenuItemType.BUTTON, + icon: 'DocumentSettingSingle', + tooltip: 'toolbar.pageSetup', + hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_DOC), + }; +} diff --git a/packages/docs-ui/src/docs-ui-plugin.ts b/packages/docs-ui/src/docs-ui-plugin.ts index e59c3b4c19..387e717118 100644 --- a/packages/docs-ui/src/docs-ui-plugin.ts +++ b/packages/docs-ui/src/docs-ui-plugin.ts @@ -42,6 +42,7 @@ import { DeleteCommand, InsertCommand, UpdateCommand } from './commands/commands import { DeleteCurrentParagraphCommand, DeleteCustomBlockCommand, DeleteLeftCommand, DeleteRightCommand, MergeTwoParagraphCommand, RemoveHorizontalLineCommand } from './commands/commands/doc-delete.command'; import { CloseHeaderFooterCommand } from './commands/commands/doc-header-footer.command'; import { HorizontalLineCommand, InsertHorizontalLineBellowCommand } from './commands/commands/doc-horizontal-line.command'; +import { DocPageSetupCommand } from './commands/commands/doc-page-setup.command'; import { DocParagraphSettingCommand } from './commands/commands/doc-paragraph-setting.command'; import { DocSelectAllCommand } from './commands/commands/doc-select-all.command'; import { IMEInputCommand } from './commands/commands/ime-input.command'; @@ -91,6 +92,7 @@ import { import { DocTableTabCommand } from './commands/commands/table/doc-table-tab.command'; import { MoveCursorOperation, MoveSelectionOperation } from './commands/operations/doc-cursor.operation'; import { DocParagraphSettingPanelOperation } from './commands/operations/doc-paragraph-setting-panel.operation'; +import { DocOpenPageSettingCommand } from './commands/operations/open-page-setting.operation'; import { SetDocZoomRatioOperation } from './commands/operations/set-doc-zoom-ratio.operation'; import { AppUIController } from './controllers'; import { defaultPluginConfig, DOCS_UI_PLUGIN_CONFIG_KEY } from './controllers/config.schema'; @@ -119,6 +121,7 @@ import { DocMenuStyleService } from './services/doc-menu-style.service'; import { DocPageLayoutService } from './services/doc-page-layout.service'; import { DocParagraphMenuService } from './services/doc-paragraph-menu.service'; import { DocCanvasPopManagerService } from './services/doc-popup-manager.service'; +import { DocPrintInterceptorService } from './services/doc-print-interceptor-service'; import { DocStateChangeManagerService } from './services/doc-state-change-manager.service'; import { DocsRenderService } from './services/docs-render.service'; import { EditorService, IEditorService } from './services/editor/editor-manager.service'; @@ -283,6 +286,8 @@ export class UniverDocsUIPlugin extends Plugin { InsertOrderListBellowCommand, InsertCheckListBellowCommand, InsertHorizontalLineBellowCommand, + DocPageSetupCommand, + DocOpenPageSettingCommand, ].forEach((e) => { this.disposeWithMe(this._commandService.registerCommand(e)); }); @@ -312,6 +317,7 @@ export class UniverDocsUIPlugin extends Plugin { private _initDependencies(injector: Injector) { const dependencies = mergeOverrideWithDependencies([ + [DocPrintInterceptorService], [DocClipboardController], [DocEditorBridgeController], [DocUIController], diff --git a/packages/docs-ui/src/index.ts b/packages/docs-ui/src/index.ts index c9318414bf..3749a76664 100644 --- a/packages/docs-ui/src/index.ts +++ b/packages/docs-ui/src/index.ts @@ -45,7 +45,7 @@ export { DocsRenderService } from './services/docs-render.service'; export { Editor } from './services/editor/editor'; export { EditorService, IEditorService } from './services/editor/editor-manager.service'; export { isInSameTableCell, isValidRectRange, NodePositionConvertToRectRange } from './services/selection/convert-rect-range'; - +export { DocPrintInterceptorService, type IDocPrintComponentContext, type IDocPrintContext } from './services/doc-print-interceptor-service'; export { NodePositionConvertToCursor } from './services/selection/convert-text-range'; export { getOneTextSelectionRange } from './services/selection/convert-text-range'; export type { IEditorInputConfig } from './services/selection/doc-selection-render.service'; diff --git a/packages/docs-ui/src/locale/en-US.ts b/packages/docs-ui/src/locale/en-US.ts index ec1e9200bf..828ce11772 100644 --- a/packages/docs-ui/src/locale/en-US.ts +++ b/packages/docs-ui/src/locale/en-US.ts @@ -53,6 +53,7 @@ const locale: typeof zhCN = { alignJustify: 'Justify', horizontalLine: 'Horizontal line', headerFooter: 'Header & Footer', + pageSetup: 'Page Setup', }, table: { insert: 'Insert', @@ -104,6 +105,34 @@ const locale: typeof zhCN = { checkList: 'Task list', insertBellow: 'Insert below', }, + 'page-settings': { + 'document-setting': 'Document Setting', + 'paper-size': 'Paper size', + 'page-size': { + main: 'Paper size', + a4: 'A4', + a3: 'A3', + a5: 'A5', + b4: 'B4', + b5: 'B5', + letter: 'Letter', + legal: 'Legal', + tabloid: 'Tabloid', + statement: 'Statement', + executive: 'Executive', + folio: 'Folio', + }, + orientation: 'Orientation', + portrait: 'Portrait', + landscape: 'Landscape', + 'custom-paper-size': 'Custom Paper size', + top: 'Top', + bottom: 'Bottom', + left: 'Left', + right: 'Right', + cancel: 'Cancel', + confirm: 'Confirm', + }, }; export default locale; diff --git a/packages/docs-ui/src/locale/fa-IR.ts b/packages/docs-ui/src/locale/fa-IR.ts index 6192dea312..3ccc76b2f0 100644 --- a/packages/docs-ui/src/locale/fa-IR.ts +++ b/packages/docs-ui/src/locale/fa-IR.ts @@ -53,6 +53,7 @@ const locale: typeof zhCN = { alignJustify: 'توجیه', horizontalLine: 'Horizontal line', headerFooter: 'هدر و فوتر', + pageSetup: 'تنظیمات صفحه', }, table: { insert: 'درج', @@ -104,6 +105,34 @@ const locale: typeof zhCN = { checkList: 'لیست وظیفه', insertBellow: 'درج در پایین', }, + 'page-settings': { + 'document-setting': 'تنظیمات سند', + 'page-size': { + main: 'اندازه کاغذ', + a4: 'A4', + a3: 'A3', + a5: 'A5', + b4: 'B4', + b5: 'B5', + letter: 'نامه آمریکایی', + legal: 'نامه حقوقی', + tabloid: 'روزنامه', + statement: 'کاغذ اظهارنامه', + executive: 'کاغذ اجرایی', + folio: 'کاغذ فولیو', + }, + 'paper-size': 'اندازه کاغذ', + orientation: 'جهت', + portrait: 'عمودی', + landscape: 'افقی', + 'custom-paper-size': 'اندازه کاغذ سفارشی', + top: 'بالا', + bottom: 'پایین', + left: 'چپ', + right: 'راست', + cancel: 'لغو', + confirm: 'تایید', + }, }; export default locale; diff --git a/packages/docs-ui/src/locale/fr-FR.ts b/packages/docs-ui/src/locale/fr-FR.ts index 2dcd84bb40..435c452eaa 100644 --- a/packages/docs-ui/src/locale/fr-FR.ts +++ b/packages/docs-ui/src/locale/fr-FR.ts @@ -53,6 +53,7 @@ const locale: typeof enUS = { alignJustify: 'Justifier', horizontalLine: 'Horizontal line', headerFooter: 'En-tête et pied de page', + pageSetup: 'Paramètres de page', }, table: { insert: 'Insérer', @@ -104,6 +105,34 @@ const locale: typeof enUS = { checkList: 'Liste de tâches', insertBellow: 'Insérer dans le bas', }, + 'page-settings': { + 'document-setting': 'Paramètres du document', + 'paper-size': 'Format du papier', + 'page-size': { + main: 'Format du papier', + a4: 'A4', + a3: 'A3', + a5: 'A5', + b4: 'B4', + b5: 'B5', + letter: 'Format Lettre US', + legal: 'Format Légal US', + tabloid: 'Format Tabloïd', + statement: 'Format Déclaration', + executive: 'Format Exécutif', + folio: 'Format Folio', + }, + orientation: 'Orientation', + portrait: 'Portrait', + landscape: 'Paysage', + 'custom-paper-size': 'Format de papier personnalisé', + top: 'Haut', + bottom: 'Bas', + left: 'Gauche', + right: 'Droite', + cancel: 'Annuler', + confirm: 'Confirmer', + }, }; export default locale; diff --git a/packages/docs-ui/src/locale/ru-RU.ts b/packages/docs-ui/src/locale/ru-RU.ts index 1e14e87f02..1d2d9f56ed 100644 --- a/packages/docs-ui/src/locale/ru-RU.ts +++ b/packages/docs-ui/src/locale/ru-RU.ts @@ -49,10 +49,11 @@ const locale: typeof zhCN = { alignCenter: 'Выровнять по центру', alignRight: 'Выровнять по правому краю', alignJustify: 'Выровнять по ширине', - horizontalLine: 'Horizontal line', - headerFooter: 'Header & Footer', - checklist: 'Task list', - documentFlavor: 'Modern Mode', + horizontalLine: 'Горизонтальная линия', + headerFooter: 'Шапка и подвал', + checklist: 'Список задач', + documentFlavor: 'Современный режим', + pageSetup: 'Настройки страницы', }, table: { insert: 'Insert', @@ -104,6 +105,34 @@ const locale: typeof zhCN = { checkList: 'Список задач', insertBellow: 'Вставить ниже', }, + 'page-settings': { + 'document-setting': 'Настройки документа', + 'page-size': { + main: 'Размер бумаги', + a4: 'A4', + a3: 'A3', + a5: 'A5', + b4: 'B4', + b5: 'B5', + letter: 'Американский формат', + legal: 'Юридический формат', + tabloid: 'Таблоидный формат', + statement: 'Формат заявления', + executive: 'Исполнительный формат', + folio: 'Формат фолио', + }, + 'paper-size': 'Размер бумаги', + orientation: 'Ориентация', + portrait: 'Книжная', + landscape: 'Альбомная', + 'custom-paper-size': 'Пользовательский размер бумаги', + top: 'Верх', + bottom: 'Низ', + left: 'Слева', + right: 'Справа', + cancel: 'Отмена', + confirm: 'Подтвердить', + }, }; export default locale; diff --git a/packages/docs-ui/src/locale/vi-VN.ts b/packages/docs-ui/src/locale/vi-VN.ts index 7404e03601..14d1d7fdce 100644 --- a/packages/docs-ui/src/locale/vi-VN.ts +++ b/packages/docs-ui/src/locale/vi-VN.ts @@ -53,6 +53,7 @@ const locale: typeof zhCN = { headerFooter: 'Đầu trang và chân trang', checklist: 'Task list', documentFlavor: 'Modern Mode', + pageSetup: 'Cài đặt trang', }, table: { insert: 'Insert', @@ -104,6 +105,34 @@ const locale: typeof zhCN = { checkList: 'Danh sách công việc', insertBellow: 'Chèn dưới', }, + 'page-settings': { + 'document-setting': 'Cài đặt tài liệu', + 'paper-size': 'Kích thước giấy', + 'page-size': { + main: 'Kích thước giấy', + a4: 'A4', + a3: 'A3', + a5: 'A5', + b4: 'B4', + b5: 'B5', + letter: 'Giấy thư Mỹ', + legal: 'Giấy pháp lý Mỹ', + tabloid: 'Khổ báo', + statement: 'Giấy tuyên bố', + executive: 'Giấy hành chính', + folio: 'Giấy folio', + }, + orientation: 'Hướng', + portrait: 'Dọc', + landscape: 'Ngang', + 'custom-paper-size': 'Kích thước giấy tùy chỉnh', + top: 'Trên', + bottom: 'Dưới', + left: 'Trái', + right: 'Phải', + cancel: 'Hủy', + confirm: 'Xác nhận', + }, }; export default locale; diff --git a/packages/docs-ui/src/locale/zh-CN.ts b/packages/docs-ui/src/locale/zh-CN.ts index 359afdbc81..65f1883bd2 100644 --- a/packages/docs-ui/src/locale/zh-CN.ts +++ b/packages/docs-ui/src/locale/zh-CN.ts @@ -51,6 +51,7 @@ const locale = { alignJustify: '两端对齐', horizontalLine: '水平分割线', headerFooter: '页眉页脚', + pageSetup: '页面设置', }, table: { insert: '插入', @@ -102,6 +103,34 @@ const locale = { checkList: '任务列表', insertBellow: '在下方插入', }, + 'page-settings': { + 'document-setting': '文档设置', + 'page-size': { + main: '纸张大小', + a4: 'A4', + a3: 'A3', + a5: 'A5', + b4: 'B4', + b5: 'B5', + letter: '美式信纸', + legal: '美式法律用纸', + tabloid: '小报尺寸', + statement: '声明用纸', + executive: '行政用纸', + folio: '对开纸', + }, + 'paper-size': '纸张大小', + orientation: '方向', + portrait: '纵向', + landscape: '横向', + 'custom-paper-size': '自定义纸张大小', + top: '上', + bottom: '下', + left: '左', + right: '右', + cancel: '取消', + confirm: '确认', + }, }; export default locale; diff --git a/packages/docs-ui/src/locale/zh-TW.ts b/packages/docs-ui/src/locale/zh-TW.ts index 7a5773f726..c698c2d244 100644 --- a/packages/docs-ui/src/locale/zh-TW.ts +++ b/packages/docs-ui/src/locale/zh-TW.ts @@ -53,6 +53,7 @@ const locale: typeof zhCN = { headerFooter: '頁眉頁腳', checklist: '任務列表', documentFlavor: '现代模式', + pageSetup: '頁面設置', }, table: { insert: 'Insert', @@ -104,6 +105,34 @@ const locale: typeof zhCN = { checkList: '任務列表', insertBellow: '在下方插入', }, + 'page-settings': { + 'document-setting': '文檔設置', + 'page-size': { + main: '紙張大小', + a4: 'A4', + a3: 'A3', + a5: 'A5', + b4: 'B4', + b5: 'B5', + letter: '美式信紙', + legal: '美式法律用紙', + tabloid: '小報尺寸', + statement: '聲明用紙', + executive: '行政用紙', + folio: '對開紙', + }, + 'paper-size': '紙張大小', + orientation: '方向', + portrait: '縱向', + landscape: '橫向', + 'custom-paper-size': '自定義紙張大小', + top: '上', + bottom: '下', + left: '左', + right: '右', + cancel: '取消', + confirm: '確認', + }, }; export default locale; diff --git a/packages/docs-ui/src/services/doc-print-interceptor-service.ts b/packages/docs-ui/src/services/doc-print-interceptor-service.ts new file mode 100644 index 0000000000..12809d6c7a --- /dev/null +++ b/packages/docs-ui/src/services/doc-print-interceptor-service.ts @@ -0,0 +1,58 @@ +/** + * 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 { DisposableCollection } from '@univerjs/core'; +import type { Documents, DocumentSkeleton, Engine, Scene } from '@univerjs/engine-render'; +import { createInterceptorKey, Disposable, InterceptorManager } from '@univerjs/core'; + +export interface IDocPrintContext { + unitId: string; + scene: Scene; + engine: Engine; + root: HTMLElement; + skeleton: DocumentSkeleton; +} + +export interface IDocPrintDomtContext extends IDocPrintContext { + offset: { x: number; y: number }; +} +export interface IDocPrintComponentContext extends IDocPrintContext { + documents: Documents; +} + +const PRINTING_COMPONENT_COLLECT = createInterceptorKey('PRINTING_COMPONENT_COLLECT'); +const PRINTING_DOM_COLLECT = createInterceptorKey('PRINTING_DOM_COLLECT'); + +export class DocPrintInterceptorService extends Disposable { + readonly interceptor = new InterceptorManager({ + PRINTING_COMPONENT_COLLECT, + PRINTING_DOM_COLLECT, + }); + + constructor() { + super(); + + this.disposeWithMe(this.interceptor.intercept(this.interceptor.getInterceptPoints().PRINTING_COMPONENT_COLLECT, { + priority: -1, + handler: (_value) => _value, + })); + + this.disposeWithMe(this.interceptor.intercept(this.interceptor.getInterceptPoints().PRINTING_DOM_COLLECT, { + priority: -1, + handler: (_value) => _value, + })); + } +} diff --git a/packages/docs-ui/src/views/page-settings/index.tsx b/packages/docs-ui/src/views/page-settings/index.tsx new file mode 100644 index 0000000000..69d3de91e5 --- /dev/null +++ b/packages/docs-ui/src/views/page-settings/index.tsx @@ -0,0 +1,187 @@ +/** + * 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, ISize, PaperType } from '@univerjs/core'; +import type { IConfirmChildrenProps } from '@univerjs/ui'; +import { IUniverInstanceService, LocaleService, PAGE_SIZE, PageOrientType, PAPER_TYPES, UniverInstanceType } from '@univerjs/core'; +import { InputNumber, Select } from '@univerjs/design'; +import { useDependency } from '@univerjs/ui'; +import React, { useEffect, useState } from 'react'; + +export interface IPageSettingsProps { + onClose: () => void; + onConfirm: (settings: IPageSettings) => void; +} + +export interface IPageSettings { + paperSize: string; + orientation: PageOrientType; + margins: { + top: number; + bottom: number; + left: number; + right: number; + }; +} + +const getPaperSize = (size: ISize) => { + const keys = Object.keys(PAGE_SIZE); + + const result = keys.find((key) => { + const { width, height } = PAGE_SIZE[key as PaperType]; + if (size.width === width && size.height === height) { + return true; + } + + return false; + }); + + return result ?? 'A4'; +}; + +export function PageSettings(props: IConfirmChildrenProps) { + const { hooks } = props; + const univerInstanceService = useDependency(IUniverInstanceService); + const currentDoc = univerInstanceService.getCurrentUnitOfType(UniverInstanceType.UNIVER_DOC)!; + const documentStyle = currentDoc.getDocumentStyle(); + const localeService = useDependency(LocaleService); + const [settings, setSettings] = useState(() => ({ + paperSize: getPaperSize(documentStyle.pageSize!), + orientation: documentStyle.pageOrient ?? PageOrientType.PORTRAIT, + margins: { + top: documentStyle.marginTop ?? 0, + bottom: documentStyle.marginBottom ?? 0, + left: documentStyle.marginLeft ?? 0, + right: documentStyle.marginRight ?? 0, + }, + })); + + useEffect(() => { + hooks.beforeClose = () => { + return settings; + }; + + hooks.beforeConfirm = () => { + return settings; + }; + }, [settings]); + + const handlePaperSizeChange = (value: string) => { + setSettings((prev) => ({ ...prev, paperSize: value })); + }; + + const handleOrientationChange = (value: PageOrientType) => { + setSettings((prev) => ({ ...prev, orientation: value })); + }; + + const handleMarginChange = (position: keyof IPageSettings['margins'], value: number | null) => { + setSettings((prev) => ({ + ...prev, + margins: { + ...prev.margins, + [position]: value || 0, + }, + })); + }; + + return ( +
+
+ +