import * as React from "react"; import { Button } from "@fluentui/react-components"; import { ColorRegular } from "@fluentui/react-icons"; import { useStatusContext } from "./App"; import { useCommonStyles } from "./commonStyles"; export const MatchProperties: React.FC = () => { const styles = useCommonStyles(); const { statusMessage, setStatusMessage, statusType, setStatusType, isProcessing, setIsProcessing } = useStatusContext(); const matchPropertiesToFirstSelected = async () => { setIsProcessing(true); try { await PowerPoint.run(async (context) => { // Get the selected shapes const shapes = context.presentation.getSelectedShapes(); shapes.load("items"); await context.sync(); // Check if shapes are selected if (shapes.items.length === 0) { setStatusMessage("No shapes are selected. Please select shapes first."); setStatusType("warning"); return; } // Check if there is more than one shape selected if (shapes.items.length === 1) { setStatusMessage("Please select multiple shapes to copy properties."); setStatusType("warning"); return; } // Get the first shape to use as template const firstShape = shapes.items[0]; // Load all necessary properties from the first shape firstShape.load([ "lineFormat/weight", "lineFormat/dashStyle", "lineFormat/color", "fill/transparency", "fill/foregroundColor", "textFrame" ]); await context.sync(); // Check if the shape has text and load text properties let hasText = false; if (firstShape.textFrame) { firstShape.textFrame.load("textRange"); await context.sync(); if (firstShape.textFrame.textRange) { firstShape.textFrame.textRange.load([ "font/name", "font/size", "font/bold", "font/italic", "font/underline", "font/color" ]); await context.sync(); hasText = true; } } // First, load all text frames in a batch if (hasText) { // Pre-load textFrame for all target shapes for (let i = 1; i < shapes.items.length; i++) { shapes.items[i].load("textFrame"); } await context.sync(); // For shapes that have textFrames, load their textRanges for (let i = 1; i < shapes.items.length; i++) { if (shapes.items[i].textFrame) { shapes.items[i].textFrame.load("textRange"); } } await context.sync(); } // Now apply properties to all shapes let successCount = 0; let textStyleCount = 0; for (let i = 1; i < shapes.items.length; i++) { try { const targetShape = shapes.items[i]; let propertiesApplied = false; // Apply line formatting properties try { // Line weight if (firstShape.lineFormat.weight !== undefined) { targetShape.lineFormat.weight = firstShape.lineFormat.weight; } // Line style if (firstShape.lineFormat.dashStyle !== undefined) { targetShape.lineFormat.dashStyle = firstShape.lineFormat.dashStyle; } // Line color if (firstShape.lineFormat.color !== undefined) { targetShape.lineFormat.color = firstShape.lineFormat.color; } propertiesApplied = true; } catch (err) { console.error(`Error applying line format to shape ${i}:`, err); } // Apply fill properties try { // Fill transparency if (firstShape.fill.transparency !== undefined) { targetShape.fill.transparency = firstShape.fill.transparency; } if (firstShape.fill.foregroundColor !== undefined) { targetShape.fill.foregroundColor = firstShape.fill.foregroundColor; } propertiesApplied = true; } catch (err) { console.error(`Error applying fill format to shape ${i}:`, err); } // Apply text properties if the source has text if (hasText && targetShape.textFrame && targetShape.textFrame.textRange) { try { const sourceFont = firstShape.textFrame.textRange.font; const targetFont = targetShape.textFrame.textRange.font; // Apply font properties in batch if (sourceFont.name !== undefined) { targetFont.name = sourceFont.name; } if (sourceFont.size !== undefined) { targetFont.size = sourceFont.size; } if (sourceFont.bold !== undefined) { targetFont.bold = sourceFont.bold; } if (sourceFont.italic !== undefined) { targetFont.italic = sourceFont.italic; } if (sourceFont.underline !== undefined) { targetFont.underline = sourceFont.underline; } if (sourceFont.color !== undefined) { targetFont.color = sourceFont.color; } textStyleCount++; } catch (err) { console.error(`Error applying text format to shape ${i}:`, err); } } if (propertiesApplied) { successCount++; } } catch (err) { console.error(`Error updating shape ${i}:`, err); } } // Single sync after all property changes await context.sync(); // Final status message based on what was applied if (successCount > 0) { let message = `Applied properties to ${successCount} shapes`; if (textStyleCount > 0) { message += ` (including text styling on ${textStyleCount})`; } setStatusMessage(message); setStatusType("success"); } else { setStatusMessage("Couldn't apply properties. Try selecting different shapes."); setStatusType("error"); } // Timeout is handled in App.tsx now }); } catch (error) { setStatusMessage(`Error: ${error.message}`); setStatusType("error"); console.error("Main error:", error); } finally { setIsProcessing(false); } }; return (
); }; export default MatchProperties;