Refactor MatchSizes component for better maintainability and performance

This commit is contained in:
2025-03-22 15:54:13 +01:00
parent 04c86c6162
commit 577d5b15c8
+155 -32
View File
@@ -1,49 +1,172 @@
import * as React from "react";
import { ArrowFitInRegular } from "@fluentui/react-icons";
import { ArrowFitInRegular, ArrowMaximizeRegular, ArrowMinimizeRegular } from "@fluentui/react-icons";
import { useStatusContext } from "./App";
import { useCommonStyles } from "./commonStyles";
import ActionButton from "./ActionButton";
import { getErrorMessage } from "../types/office-types";
// Size matching options
enum SizeMatchType {
Both = "both",
Width = "width",
Height = "height"
}
// Dimension properties to load and match
const DIMENSION_PROPERTIES = {
[SizeMatchType.Both]: ["width", "height"],
[SizeMatchType.Width]: ["width"],
[SizeMatchType.Height]: ["height"]
};
export const MatchSizes: React.FC = () => {
const styles = useCommonStyles();
const { setStatusMessage, setStatusType } = useStatusContext();
const matchSizeToFirstSelected = async () => {
await PowerPoint.run(async (context) => {
// Get the selected shapes
const shapes = context.presentation.getSelectedShapes();
shapes.load("items");
await context.sync();
/**
* Validates that multiple shapes are selected
* @param shapes The collection of selected shapes
* @returns True if validation passes, false otherwise
*/
const validateShapeSelection = (shapes: PowerPoint.ShapeScopedCollection): boolean => {
if (shapes.items.length === 0) {
setStatusMessage("No shapes are selected. Please select shapes first.");
setStatusType("warning");
return false;
}
// Check if shapes are selected
if (shapes.items.length === 0) {
setStatusMessage("No shapes are selected. Please select shapes first.");
setStatusType("warning");
return;
}
if (shapes.items.length === 1) {
setStatusMessage("Please select multiple shapes to resize.");
setStatusType("warning");
return false;
}
// Check if there is more than one shape selected
if (shapes.items.length === 1) {
setStatusMessage("Please select multiple shapes to resize.");
setStatusType("warning");
return;
}
return true;
};
// Get the first shape's dimensions
const firstShape = shapes.items[0];
firstShape.load("width,height");
await context.sync();
/**
* Loads dimension properties from the source shape
* @param sourceShape The shape to copy dimensions from
* @param context The PowerPoint request context
* @param matchType The type of dimension matching to perform
* @returns The loaded source shape
*/
const loadSourceShape = async (
sourceShape: PowerPoint.Shape,
context: PowerPoint.RequestContext,
matchType: SizeMatchType = SizeMatchType.Both
): Promise<PowerPoint.Shape> => {
// Load the appropriate dimension properties
sourceShape.load(DIMENSION_PROPERTIES[matchType]);
await context.sync();
return sourceShape;
};
// Loop through the remaining shapes and resize them
/**
* Applies size changes to target shapes
* @param sourceShape The shape to copy dimensions from
* @param shapes The collection of shapes to resize
* @param matchType The type of dimension matching to perform
* @returns The number of shapes that were resized
*/
const applySizeChanges = (
sourceShape: PowerPoint.Shape,
shapes: PowerPoint.ShapeScopedCollection,
matchType: SizeMatchType = SizeMatchType.Both
): number => {
let resizedCount = 0;
try {
// Apply dimensions to all target shapes
for (let i = 1; i < shapes.items.length; i++) {
shapes.items[i].width = firstShape.width;
shapes.items[i].height = firstShape.height;
const targetShape = shapes.items[i];
// Apply width if needed
if (matchType === SizeMatchType.Both || matchType === SizeMatchType.Width) {
targetShape.width = sourceShape.width;
}
// Apply height if needed
if (matchType === SizeMatchType.Both || matchType === SizeMatchType.Height) {
targetShape.height = sourceShape.height;
}
resizedCount++;
}
} catch (err) {
console.error("Error applying size changes:", getErrorMessage(err));
}
return resizedCount;
};
await context.sync();
setStatusMessage(`Resized ${shapes.items.length - 1} shapes to match the first selected shape.`);
setStatusType("success");
});
/**
* Generates an appropriate status message based on the results
* @param resizedCount The number of shapes that were resized
* @param matchType The type of dimension matching performed
* @returns The formatted status message
*/
const generateStatusMessage = (
resizedCount: number,
matchType: SizeMatchType = SizeMatchType.Both
): string => {
if (resizedCount === 0) {
return "No shapes were resized. Please try again with different shapes.";
}
let dimensionText = "";
switch (matchType) {
case SizeMatchType.Width:
dimensionText = "width";
break;
case SizeMatchType.Height:
dimensionText = "height";
break;
default:
dimensionText = "dimensions";
}
return `Resized ${resizedCount} shapes to match the ${dimensionText} of the first selected shape.`;
};
/**
* Main function to match sizes from the first selected shape to others
* @param matchType The type of dimension matching to perform
*/
const matchSizeToFirstSelected = async (
matchType: SizeMatchType = SizeMatchType.Both
): Promise<void> => {
try {
await PowerPoint.run(async (context) => {
// Get the selected shapes
const shapes = context.presentation.getSelectedShapes();
shapes.load("items");
await context.sync();
// Validate shape selection
if (!validateShapeSelection(shapes)) {
return;
}
// Get the first shape to use as template
const sourceShape = await loadSourceShape(shapes.items[0], context, matchType);
// Apply size changes to all target shapes
const resizedCount = applySizeChanges(sourceShape, shapes, matchType);
// Sync changes to PowerPoint
await context.sync();
// Update status message
const statusMessage = generateStatusMessage(resizedCount, matchType);
setStatusMessage(statusMessage);
setStatusType(resizedCount > 0 ? "success" : "error");
});
} catch (error) {
console.error("Error in matchSizeToFirstSelected:", getErrorMessage(error));
setStatusMessage(`Error: ${getErrorMessage(error)}`);
setStatusType("error");
}
};
return (
@@ -52,11 +175,11 @@ export const MatchSizes: React.FC = () => {
<ActionButton
title="Match Sizes"
icon={<ArrowFitInRegular />}
onClick={matchSizeToFirstSelected}
onClick={() => matchSizeToFirstSelected(SizeMatchType.Both)}
/>
</div>
</div>
);
};
export default MatchSizes;
export default MatchSizes;