Refactor MatchSizes component for better maintainability and performance
This commit is contained in:
@@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
await context.sync();
|
// Apply width if needed
|
||||||
setStatusMessage(`Resized ${shapes.items.length - 1} shapes to match the first selected shape.`);
|
if (matchType === SizeMatchType.Both || matchType === SizeMatchType.Width) {
|
||||||
setStatusType("success");
|
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) => {
|
||||||
|
// 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,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>
|
||||||
|
|||||||
Reference in New Issue
Block a user