Refactor MatchSizes component for better maintainability and performance
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user