Files
powerpoint-toolbox/src/taskpane/components/RoundImage.tsx
T
schihei 1aeef10ebe Standardize documentation across all *.tsx files
- Add file-level JSDoc comments to components that were missing them
- Ensure consistent documentation format across all components
- Improve code maintainability and readability
2025-03-22 19:53:09 +01:00

175 lines
5.5 KiB
TypeScript

/**
* @file RoundImage.tsx
* @description Component that provides functionality to create circular or rounded images
* in PowerPoint presentations. This tool creates a mask shape that can be used with
* PowerPoint's built-in shape intersection feature to crop images into a circular shape.
*/
import * as React from "react";
import { CircleRegular } from "@fluentui/react-icons";
import { useStatusContext } from "./App";
import { useCommonStyles } from "./commonStyles";
import ActionButton from "./ActionButton";
import { getErrorMessage, isPictureShape, getFirstSelectedSlide, selectShapesById } from "../types/office-types";
// Configuration constants
const MASK_COLOR = "red";
const SUCCESS_MESSAGE = "Created mask shape. Please select both the image and the oval, then use the 'Shape Format > Merge Shapes > Intersect' command in PowerPoint.";
export const RoundImage: React.FC = () => {
const styles = useCommonStyles();
const { setStatusMessage, setStatusType } = useStatusContext();
/**
* Validates that an image shape is selected
* @param shapes The collection of selected shapes
* @param context The PowerPoint request context
* @returns The selected image shape or null if validation fails
*/
const validateShapeSelection = async (
shapes: PowerPoint.ShapeScopedCollection,
context: PowerPoint.RequestContext
): Promise<PowerPoint.Shape | null> => {
// Check if any shape is selected
if (shapes.items.length === 0) {
setStatusMessage("No shapes are selected. Please select an image.");
setStatusType("warning");
return null;
}
// Get the first selected shape
const shape = shapes.items[0];
// Load essential properties
shape.load(["type"]);
await context.sync();
// Ensure the shape is a picture using our type-safe utility
if (!isPictureShape(shape)) {
setStatusMessage("Please select an image.");
setStatusType("warning");
return null;
}
return shape;
};
/**
* Creates an elliptical mask shape for the image
* @param slide The slide to add the mask to
* @param imageShape The image shape to mask
* @param context The PowerPoint request context
* @returns The created mask shape
*/
const createMaskShape = async (
slide: PowerPoint.Slide,
imageShape: PowerPoint.Shape,
context: PowerPoint.RequestContext
): Promise<PowerPoint.Shape> => {
// Load current dimensions to maintain aspect ratio
imageShape.load(["width", "height", "left", "top", "id"]);
await context.sync();
// Create elliptical mask with proper type
const maskShape = slide.shapes.addGeometricShape(PowerPoint.GeometricShapeType.ellipse);
maskShape.load(["width", "height", "left", "top", "id"]);
await context.sync();
// Position the mask to match the image
maskShape.left = imageShape.left;
imageShape.lineFormat.load(["weight"]);
await context.sync();
maskShape.top = imageShape.top;
maskShape.width = imageShape.width;
maskShape.height = imageShape.height;
// Style the mask
maskShape.fill.setSolidColor(MASK_COLOR);
maskShape.lineFormat.visible = false;
return maskShape;
};
/**
* Applies the mask to the image and selects both shapes
* @param slide The slide containing the shapes
* @param imageShape The image shape to mask
* @param maskShape The mask shape
* @param context The PowerPoint request context
*/
const applyMaskToImage = async (
slide: PowerPoint.Slide,
imageShape: PowerPoint.Shape,
maskShape: PowerPoint.Shape,
context: PowerPoint.RequestContext
): Promise<void> => {
// Store original dimensions to maintain after selection
const width = imageShape.width;
const height = imageShape.height;
// Select both shapes for the user to apply the intersection
selectShapesById(slide, [imageShape.id, maskShape.id]);
// Ensure we maintain the same size
imageShape.width = width;
imageShape.height = height;
await context.sync();
// Update status message with instructions
setStatusMessage(SUCCESS_MESSAGE);
setStatusType("warning");
};
/**
* Main function to convert an image to a round image
*/
const convertToRoundImage = async (): Promise<void> => {
try {
await PowerPoint.run(async (context) => {
// Get the selected shapes
const shapes = context.presentation.getSelectedShapes();
shapes.load("items");
await context.sync();
// Validate selection and get the image shape
const imageShape = await validateShapeSelection(shapes, context);
if (!imageShape) {
return;
}
// Get the current slide using our type-safe utility
const slide = getFirstSelectedSlide(context);
// Create the mask shape
const maskShape = await createMaskShape(slide, imageShape, context);
// Apply the mask and select both shapes
await applyMaskToImage(slide, imageShape, maskShape, context);
});
} catch (error: unknown) {
const errorMessage = getErrorMessage(error);
setStatusMessage(`Error: ${errorMessage}`);
setStatusType("error");
console.error("Round image error:", error);
}
};
return (
<div className={styles.container}>
<div className={styles.buttonGroup}>
<ActionButton
title="Round Image"
icon={<CircleRegular />}
onClick={convertToRoundImage}
/>
</div>
</div>
);
};
export default RoundImage;