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
+148 -25
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 () => { /**
* 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;
}
if (shapes.items.length === 1) {
setStatusMessage("Please select multiple shapes to resize.");
setStatusType("warning");
return false;
}
return true;
};
/**
* 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;
};
/**
* 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++) {
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;
};
/**
* 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) => { await PowerPoint.run(async (context) => {
// Get the selected shapes // Get the selected shapes
const shapes = context.presentation.getSelectedShapes(); const shapes = context.presentation.getSelectedShapes();
shapes.load("items"); shapes.load("items");
await context.sync(); await context.sync();
// Check if shapes are selected // Validate shape selection
if (shapes.items.length === 0) { if (!validateShapeSelection(shapes)) {
setStatusMessage("No shapes are selected. Please select shapes first.");
setStatusType("warning");
return; return;
} }
// Check if there is more than one shape selected // Get the first shape to use as template
if (shapes.items.length === 1) { const sourceShape = await loadSourceShape(shapes.items[0], context, matchType);
setStatusMessage("Please select multiple shapes to resize.");
setStatusType("warning");
return;
}
// Get the first shape's dimensions // Apply size changes to all target shapes
const firstShape = shapes.items[0]; const resizedCount = applySizeChanges(sourceShape, shapes, matchType);
firstShape.load("width,height");
// Sync changes to PowerPoint
await context.sync(); await context.sync();
// Loop through the remaining shapes and resize them // Update status message
for (let i = 1; i < shapes.items.length; i++) { const statusMessage = generateStatusMessage(resizedCount, matchType);
shapes.items[i].width = firstShape.width; setStatusMessage(statusMessage);
shapes.items[i].height = firstShape.height; setStatusType(resizedCount > 0 ? "success" : "error");
}
await context.sync();
setStatusMessage(`Resized ${shapes.items.length - 1} shapes to match the first selected shape.`);
setStatusType("success");
}); });
} catch (error) {
console.error("Error in matchSizeToFirstSelected:", getErrorMessage(error));
setStatusMessage(`Error: ${getErrorMessage(error)}`);
setStatusType("error");
}
}; };
return ( return (
@@ -52,7 +175,7 @@ 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>