import * as React from "react"; import { Button, makeStyles, tokens } from "@fluentui/react-components"; import { ColorRegular } from "@fluentui/react-icons"; import { useStatusContext } from "./App"; const useStyles = makeStyles({ container: { display: "flex", flexDirection: "column", width: "100%", }, buttonGroup: { display: "flex", flexDirection: "column", gap: "8px", marginBottom: "2px", }, actionButton: { justifyContent: "flex-start", transitionProperty: "all", transitionDuration: "200ms", transitionTimingFunction: "cubic-bezier(0.33, 0, 0.67, 1)", }, statusContainer: { marginTop: "4px", padding: "8px 12px", fontSize: "13px", borderRadius: "6px", backgroundColor: "#f3f2f1", // Light gray background transition: "all 0.3s ease", }, successStatus: { backgroundColor: "#DFF6DD", // Light green background color: "#107C10", // Green text }, warningStatus: { backgroundColor: "#FFF4CE", // Light yellow background color: "#797673", // Dark gray text }, errorStatus: { backgroundColor: "#FDE7E9", // Light red background color: "#A80000", // Red text }, statusIcon: { marginRight: "8px", }, statusText: { display: "flex", alignItems: "center", }, }); export const MatchProperties: React.FC = () => { const styles = useStyles(); 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; } } let successCount = 0; let textStyleCount = 0; // Loop through remaining shapes and apply properties 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; } await context.sync(); 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; } await context.sync(); propertiesApplied = true; } catch (err) { console.error(`Error applying fill format to shape ${i}:`, err); } // Apply text properties if the source has text if (hasText) { try { // First check if target shape has text targetShape.load("textFrame"); await context.sync(); if (targetShape.textFrame) { targetShape.textFrame.load("textRange"); await context.sync(); if (targetShape.textFrame.textRange) { const sourceFont = firstShape.textFrame.textRange.font; const targetFont = targetShape.textFrame.textRange.font; // Apply font properties 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; } await context.sync(); 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); } } // 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})`; } message += ". Note: Fill colors may not be fully supported in the Office JS API."; 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 (