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 * as React from "react";
import { ArrowFitInRegular } from "@fluentui/react-icons"; import { ArrowFitInRegular, ArrowMaximizeRegular, ArrowMinimizeRegular } from "@fluentui/react-icons";
import { useStatusContext } from "./App"; import { useStatusContext } from "./App";
import { useCommonStyles } from "./commonStyles"; import { useCommonStyles } from "./commonStyles";
import ActionButton from "./ActionButton"; 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 = () => { export const MatchSizes: React.FC = () => {
const styles = useCommonStyles(); const styles = useCommonStyles();
const { setStatusMessage, setStatusType } = useStatusContext(); const { setStatusMessage, setStatusType } = useStatusContext();
const matchSizeToFirstSelected = async () => { /**
await PowerPoint.run(async (context) => { * Validates that multiple shapes are selected
// Get the selected shapes * @param shapes The collection of selected shapes
const shapes = context.presentation.getSelectedShapes(); * @returns True if validation passes, false otherwise
shapes.load("items"); */
await context.sync(); 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 === 1) {
if (shapes.items.length === 0) { setStatusMessage("Please select multiple shapes to resize.");
setStatusMessage("No shapes are selected. Please select shapes first."); setStatusType("warning");
setStatusType("warning"); return false;
return; }
}
// Check if there is more than one shape selected return true;
if (shapes.items.length === 1) { };
setStatusMessage("Please select multiple shapes to resize.");
setStatusType("warning");
return;
}
// Get the first shape's dimensions /**
const firstShape = shapes.items[0]; * Loads dimension properties from the source shape
firstShape.load("width,height"); * @param sourceShape The shape to copy dimensions from
await context.sync(); * @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++) { for (let i = 1; i < shapes.items.length; i++) {
shapes.items[i].width = firstShape.width; const targetShape = shapes.items[i];
shapes.items[i].height = firstShape.height;
// 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.`); * Generates an appropriate status message based on the results
setStatusType("success"); * @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 ( return (
@@ -52,11 +175,11 @@ export const MatchSizes: React.FC = () => {
<ActionButton <ActionButton
title="Match Sizes" title="Match Sizes"
icon={<ArrowFitInRegular />} icon={<ArrowFitInRegular />}
onClick={matchSizeToFirstSelected} onClick={() => matchSizeToFirstSelected(SizeMatchType.Both)}
/> />
</div> </div>
</div> </div>
); );
}; };
export default MatchSizes; export default MatchSizes;