diff --git a/packages/sdk/src/controllers/FrameController.ts b/packages/sdk/src/controllers/FrameController.ts index 5e9b5466..e2329c14 100644 --- a/packages/sdk/src/controllers/FrameController.ts +++ b/packages/sdk/src/controllers/FrameController.ts @@ -15,6 +15,9 @@ import { AutoGrowDeltaUpdate, AutoGrowDirection, AutoGrowResetUpdate, + FrameAnchorType, + FrameAnchorProperties, + AnchorTarget, } from '../types/FrameTypes'; import { ColorUsage } from '../types/ColorStyleTypes'; import { ShapeType } from '../types/ShapeTypes'; @@ -127,13 +130,26 @@ export class FrameController { }; /** - * This method will reset the frame size (width and height) to the frame's original value + * This method will reset the frame's transformation properties (x, y, width, height, rotation, anchors) + * to the frame's to be inherited from the parent layout * @param id the id of a specific frame * @returns */ + resetTransformation = async (id: Id) => { + const res = await this.#editorAPI; + return res.resetFrameTransformation(id).then((result) => getEditorResponseData(result)); + }; + + /** + * This method will reset the frame's transformation properties (x, y, width, height, rotation, anchors) + * to the frame's to be inherited from the parent layout + * @param id the id of a specific frame + * @returns + * @deprecated Use 'resetTransformation' instead + */ resetSize = async (id: Id) => { const res = await this.#editorAPI; - return res.resetFrameSize(id).then((result) => getEditorResponseData(result)); + return res.resetFrameTransformation(id).then((result) => getEditorResponseData(result)); }; /** @@ -254,53 +270,63 @@ export class FrameController { return res.resetFrame(id).then((result) => getEditorResponseData(result)); }; /** - * This method will reset the x value of a specific frame to its original value - * @param id the id of the frame that needs to get reset + * This method will reset the frame's transformation properties (x, y, width, height, rotation, anchors) + * to the frame's to be inherited from the parent layout + * @param id the id of a specific frame * @returns + * @deprecated Use 'resetTransformation' instead */ resetX = async (id: Id) => { const res = await this.#editorAPI; - return res.resetFrameX(id).then((result) => getEditorResponseData(result)); + return res.resetFrameTransformation(id).then((result) => getEditorResponseData(result)); }; /** - * This method will reset the y value of a specific frame to its original value - * @param id the id of the frame that needs to get reset + * This method will reset the frame's transformation properties (x, y, width, height, rotation, anchors) + * to the frame's to be inherited from the parent layout + * @param id the id of a specific frame * @returns + * @deprecated Use 'resetTransformation' instead */ resetY = async (id: Id) => { const res = await this.#editorAPI; - return res.resetFrameY(id).then((result) => getEditorResponseData(result)); + return res.resetFrameTransformation(id).then((result) => getEditorResponseData(result)); }; /** - * This method will reset the rotation value of a specific frame to its original value - * @param id the id of the frame that needs to get reset + * This method will reset the frame's transformation properties (x, y, width, height, rotation, anchors) + * to the frame's to be inherited from the parent layout + * @param id the id of a specific frame * @returns + * @deprecated Use 'resetTransformation' instead */ resetRotation = async (id: Id) => { const res = await this.#editorAPI; - return res.resetFrameRotation(id).then((result) => getEditorResponseData(result)); + return res.resetFrameTransformation(id).then((result) => getEditorResponseData(result)); }; /** - * This method will reset the width of a specific frame to its original value - * @param id the id of the frame that needs to get reset + * This method will reset the frame's transformation properties (x, y, width, height, rotation, anchors) + * to the frame's to be inherited from the parent layout + * @param id the id of a specific frame * @returns + * @deprecated Use 'resetTransformation' instead */ resetWidth = async (id: Id) => { const res = await this.#editorAPI; - return res.resetFrameWidth(id).then((result) => getEditorResponseData(result)); + return res.resetFrameTransformation(id).then((result) => getEditorResponseData(result)); }; /** - * This method will reset the height of a specific frame to its original value - * @param id the id of the frame that needs to get reset + * This method will reset the frame's transformation properties (x, y, width, height, rotation, anchors) + * to the frame's to be inherited from the parent layout + * @param id the id of a specific frame * @returns + * @deprecated Use 'resetTransformation' instead */ resetHeight = async (id: Id) => { const res = await this.#editorAPI; - return res.resetFrameHeight(id).then((result) => getEditorResponseData(result)); + return res.resetFrameTransformation(id).then((result) => getEditorResponseData(result)); }; /** @@ -836,4 +862,72 @@ export class FrameController { .resetAutoGrowSettings(id, JSON.stringify(update)) .then((result) => getEditorResponseData(result)); }; + + private setAnchor = async ( + id: Id, + horizontal: boolean, + anchorType: FrameAnchorType, + anchorTarget: AnchorTarget, + endAnchorTarget?: AnchorTarget | null, + ) => { + const properties: FrameAnchorProperties = { + horizontal: horizontal, + type: anchorType, + target: anchorTarget, + endTarget: endAnchorTarget, + }; + + const res = await this.#editorAPI; + return res + .setAnchorProperties(id, JSON.stringify(properties)) + .then((result) => getEditorResponseData(result)); + }; + + /** + * @experimental + * This method will set the vertical anchor to target frame on a specified frame. + * @param id the id of the frame that needs to get a frame anchor set + * @param anchorType the type of positioning to be set to the frame + * @param anchorTarget the target frame to which it is anchored + * @param endAnchorTarget the target (end) frame to which it can be anchored + * @returns + */ + setVerticalAnchor = async ( + id: Id, + anchorType: FrameAnchorType, + anchorTarget: AnchorTarget, + endAnchorTarget?: AnchorTarget | null, + ) => { + return this.setAnchor(id, false, anchorType, anchorTarget, endAnchorTarget); + }; + + /** + * @experimental + * This method will set the horizontal anchor to target frame on a specified frame. + * @param id the id of the frame that needs to get a frame anchor set + * @param anchorType the type of positioning to be set to the frame + * @param anchorTarget the target frame to which it is anchored + * @param endAnchorTarget the target (end) frame to which it can be anchored + * @returns + */ + setHorizontalAnchor = async ( + id: Id, + anchorType: FrameAnchorType, + anchorTarget: AnchorTarget, + endAnchorTarget?: AnchorTarget | null, + ) => { + return this.setAnchor(id, true, anchorType, anchorTarget, endAnchorTarget); + }; + + /** + * This method will reset the frame's transformation properties (x, y, width, height, rotation, anchors) + * to the frame's to be inherited from the parent layout + * @param id the id of a specific frame + * @returns + * @deprecated Use 'resetTransformation' instead + */ + resetAnchoring = async (id: Id) => { + const res = await this.#editorAPI; + return res.resetFrameTransformation(id).then((result) => getEditorResponseData(result)); + }; } diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index ed118108..1b0d9276 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -22,6 +22,9 @@ export { ImageSourceTypeEnum, BarcodeSourceTypeEnum, AutoGrowDirection, + AnchorTargetType, + FrameAnchorType, + AnchorTargetEdgeType, } from './types/FrameTypes'; export { DocumentType } from './types/DocumentTypes'; @@ -49,6 +52,15 @@ export type { ImageFrameVariableSource, ImageFrameUrlSource, AutoGrowSettings, + AnchorTarget, + FrameAnchor, + PageAnchorTarget, + FrameAnchorTarget, + RelativeFrameAnchor, + StartFrameAnchor, + EndFrameAnchor, + StartAndEndFrameAnchor, + CenterFrameAnchor } from './types/FrameTypes'; export type { Variable, diff --git a/packages/sdk/src/tests/controllers/FrameController.test.ts b/packages/sdk/src/tests/controllers/FrameController.test.ts index 7a0d35bb..209f8e03 100644 --- a/packages/sdk/src/tests/controllers/FrameController.test.ts +++ b/packages/sdk/src/tests/controllers/FrameController.test.ts @@ -1,10 +1,15 @@ import { EditorAPI, Id } from '../../types/CommonTypes'; import { + AnchorTargetEdgeType, AutoGrowDirection, BlendMode, FitMode, + FrameAnchorProperties, + FrameAnchorTarget, + FrameAnchorType, FrameTypeEnum, ImageSourceTypeEnum, + PageAnchorTarget, UpdateZIndexMethod, VerticalAlign, } from '../../types/FrameTypes'; @@ -40,12 +45,7 @@ const mockedEditorApi: EditorAPI = { removeFrame: async () => getEditorResponseData(castToEditorResponse(null)), removeFrames: async () => getEditorResponseData(castToEditorResponse(null)), resetFrame: async () => getEditorResponseData(castToEditorResponse(null)), - resetFrameX: async () => getEditorResponseData(castToEditorResponse(null)), - resetFrameY: async () => getEditorResponseData(castToEditorResponse(null)), - resetFrameHeight: async () => getEditorResponseData(castToEditorResponse(null)), - resetFrameWidth: async () => getEditorResponseData(castToEditorResponse(null)), - resetFrameRotation: async () => getEditorResponseData(castToEditorResponse(null)), - resetFrameSize: async () => getEditorResponseData(castToEditorResponse(null)), + resetFrameTransformation: async () => getEditorResponseData(castToEditorResponse(null)), resetImageFrameFitMode: async () => getEditorResponseData(castToEditorResponse(null)), removeImageSource: async () => getEditorResponseData(castToEditorResponse(null)), selectFrames: async () => getEditorResponseData(castToEditorResponse(null)), @@ -72,6 +72,7 @@ const mockedEditorApi: EditorAPI = { cancelCropMode: async () => getEditorResponseData(castToEditorResponse(null)), resetAutoGrowSettings: async () => getEditorResponseData(castToEditorResponse(null)), updateAutoGrowSettings: async () => getEditorResponseData(castToEditorResponse(null)), + setAnchorProperties: async () => getEditorResponseData(castToEditorResponse(null)), }; beforeEach(() => { @@ -96,12 +97,7 @@ beforeEach(() => { jest.spyOn(mockedEditorApi, 'setFrameIsVisible'); jest.spyOn(mockedEditorApi, 'removeFrames'); jest.spyOn(mockedEditorApi, 'resetFrame'); - jest.spyOn(mockedEditorApi, 'resetFrameX'); - jest.spyOn(mockedEditorApi, 'resetFrameY'); - jest.spyOn(mockedEditorApi, 'resetFrameHeight'); - jest.spyOn(mockedEditorApi, 'resetFrameWidth'); - jest.spyOn(mockedEditorApi, 'resetFrameRotation'); - jest.spyOn(mockedEditorApi, 'resetFrameSize'); + jest.spyOn(mockedEditorApi, 'resetFrameTransformation'); jest.spyOn(mockedEditorApi, 'resetImageFrameFitMode'); jest.spyOn(mockedEditorApi, 'removeImageSource'); jest.spyOn(mockedEditorApi, 'selectFrames'); @@ -128,6 +124,7 @@ beforeEach(() => { jest.spyOn(mockedEditorApi, 'cancelCropMode'); jest.spyOn(mockedEditorApi, 'resetAutoGrowSettings'); jest.spyOn(mockedEditorApi, 'updateAutoGrowSettings'); + jest.spyOn(mockedEditorApi, 'setAnchorProperties'); id = mockSelectFrame.id; }); @@ -280,38 +277,44 @@ describe('FrameController', () => { it("Should be possible to reset a frame's x position", async () => { await mockedFrameController.resetX(id); - expect(mockedEditorApi.resetFrameX).toHaveBeenCalledTimes(1); - expect(mockedEditorApi.resetFrameX).toHaveBeenCalledWith(id); + expect(mockedEditorApi.resetFrameTransformation).toHaveBeenCalledTimes(1); + expect(mockedEditorApi.resetFrameTransformation).toHaveBeenCalledWith(id); }); it("Should be possible to reset a frame's y position", async () => { await mockedFrameController.resetY(id); - expect(mockedEditorApi.resetFrameY).toHaveBeenCalledTimes(1); - expect(mockedEditorApi.resetFrameY).toHaveBeenCalledWith(id); + expect(mockedEditorApi.resetFrameTransformation).toHaveBeenCalledTimes(2); + expect(mockedEditorApi.resetFrameTransformation).toHaveBeenCalledWith(id); }); it('Should be possible to reset the frame rotation', async () => { await mockedFrameController.resetRotation(id); - expect(mockedEditorApi.resetFrameRotation).toHaveBeenCalledTimes(1); - expect(mockedEditorApi.resetFrameRotation).toHaveBeenCalledWith(id); + expect(mockedEditorApi.resetFrameTransformation).toHaveBeenCalledTimes(3); + expect(mockedEditorApi.resetFrameTransformation).toHaveBeenCalledWith(id); }); it('Should be possible to reset the frame height', async () => { await mockedFrameController.resetHeight(id); - expect(mockedEditorApi.resetFrameHeight).toHaveBeenCalledTimes(1); - expect(mockedEditorApi.resetFrameHeight).toHaveBeenCalledWith(id); + expect(mockedEditorApi.resetFrameTransformation).toHaveBeenCalledTimes(4); + expect(mockedEditorApi.resetFrameTransformation).toHaveBeenCalledWith(id); }); it('Should be possible to reset the frame width', async () => { await mockedFrameController.resetWidth(id); - expect(mockedEditorApi.resetFrameWidth).toHaveBeenCalledTimes(1); - expect(mockedEditorApi.resetFrameWidth).toHaveBeenCalledWith(id); + expect(mockedEditorApi.resetFrameTransformation).toHaveBeenCalledTimes(5); + expect(mockedEditorApi.resetFrameTransformation).toHaveBeenCalledWith(id); }); it('Should be possible to reset the frame size', async () => { await mockedFrameController.resetSize(id); - expect(mockedEditorApi.resetFrameSize).toHaveBeenCalledTimes(1); - expect(mockedEditorApi.resetFrameSize).toHaveBeenCalledWith(id); + expect(mockedEditorApi.resetFrameTransformation).toHaveBeenCalledTimes(6); + expect(mockedEditorApi.resetFrameTransformation).toHaveBeenCalledWith(id); + }); + + it('Should be possible to reset the frame size', async () => { + await mockedFrameController.resetTransformation(id); + expect(mockedEditorApi.resetFrameTransformation).toHaveBeenCalledTimes(7); + expect(mockedEditorApi.resetFrameTransformation).toHaveBeenCalledWith(id); }); it('Should be possible to reset the image frame fit mode', async () => { @@ -636,3 +639,43 @@ describe('Auto grow updating', () => { ); }); }); + +describe('Anchoring', () => { + it('should be possible to set the vertical anchor settings', async () => { + const anchorType = FrameAnchorType.startAndEnd; + const startTarget = new FrameAnchorTarget('target-id', AnchorTargetEdgeType.end); + const endTarget = new PageAnchorTarget(); + + await mockedFrameController.setVerticalAnchor(id, anchorType, startTarget, endTarget); + expect(mockedEditorApi.setAnchorProperties).toHaveBeenCalledTimes(1); + + const expectedProperties: FrameAnchorProperties = { + horizontal: false, + type: anchorType, + target: startTarget, + endTarget: endTarget, + }; + expect(mockedEditorApi.setAnchorProperties).toHaveBeenCalledWith(id, JSON.stringify(expectedProperties)); + }); + + it('should be possible to set the horizontal anchor settings', async () => { + const anchorType = FrameAnchorType.center; + const startTarget = new FrameAnchorTarget('target-id', AnchorTargetEdgeType.start); + + await mockedFrameController.setHorizontalAnchor(id, anchorType, startTarget); + expect(mockedEditorApi.setAnchorProperties).toHaveBeenCalledTimes(2); + + const expectedProperties: FrameAnchorProperties = { + horizontal: true, + type: anchorType, + target: startTarget, + }; + expect(mockedEditorApi.setAnchorProperties).toHaveBeenCalledWith(id, JSON.stringify(expectedProperties)); + }); + + it('should be possible to reset anchor settings', async () => { + await mockedFrameController.resetAnchoring(id); + expect(mockedEditorApi.resetFrameTransformation).toHaveBeenCalledTimes(1); + expect(mockedEditorApi.resetFrameTransformation).toHaveBeenLastCalledWith(id); + }); +}); diff --git a/packages/sdk/src/types/FrameTypes.ts b/packages/sdk/src/types/FrameTypes.ts index 0017ac9d..d109ef59 100644 --- a/packages/sdk/src/types/FrameTypes.ts +++ b/packages/sdk/src/types/FrameTypes.ts @@ -7,6 +7,8 @@ import { CornerRadiusAll, CornerRadiusNone, CornerRadiusOnly, ShapeType } from ' export type FrameLayoutType = { id: Id; layoutId: Id; + horizontal: FrameAnchor; + vertical: FrameAnchor; x: PropertyState; y: PropertyState; width: PropertyState; @@ -274,3 +276,85 @@ export enum UpdateZIndexMethod { bringForward = 'bringForward', sendBackward = 'sendBackward', } + +export enum FrameAnchorType { + relative = 'relative', + start = 'start', + end = 'end', + startAndEnd = 'startAndEnd', + center = 'center', +} + +export enum AnchorTargetType { + page = 'page', + frame = 'frame', +} + +export enum AnchorTargetEdgeType { + start = 'start', + end = 'end', + center = 'center', +} + +export class PageAnchorTarget { + type = AnchorTargetType.page; +} + +export class FrameAnchorTarget { + frameId: Id; + edge: AnchorTargetEdgeType; + type = AnchorTargetType.frame; + + constructor(id: Id, edge: AnchorTargetEdgeType) { + (this.frameId = id), (this.edge = edge); + } +} + +export type AnchorTarget = PageAnchorTarget | FrameAnchorTarget; + +export type RelativeFrameAnchor = { + start: PropertyState; + end: PropertyState; + target: AnchorTarget; + type: FrameAnchorType.relative; +}; + +export type StartFrameAnchor = { + offset: PropertyState; + target: AnchorTarget; + type: FrameAnchorType.start; +}; + +export type EndFrameAnchor = { + offset: PropertyState; + target: AnchorTarget; + type: FrameAnchorType.end; +}; + +export type StartAndEndFrameAnchor = { + start: PropertyState; + startTarget: AnchorTarget; + end: PropertyState; + endTarget: AnchorTarget; + type: FrameAnchorType.startAndEnd; +}; + +export type CenterFrameAnchor = { + offset: PropertyState; + target: AnchorTarget; + type: FrameAnchorType.center; +}; + +export type FrameAnchor = + | RelativeFrameAnchor + | StartFrameAnchor + | EndFrameAnchor + | StartAndEndFrameAnchor + | CenterFrameAnchor; + +export type FrameAnchorProperties = { + horizontal: boolean; + type: FrameAnchorType; + target: AnchorTarget; + endTarget?: AnchorTarget | null; +};