diff --git a/src/taskpane/components/AddConfidential.tsx b/src/taskpane/components/AddConfidential.tsx new file mode 100644 index 00000000..38cff08e --- /dev/null +++ b/src/taskpane/components/AddConfidential.tsx @@ -0,0 +1,167 @@ +import * as React from "react"; +import { + Button +} from "@fluentui/react-components"; +import { + ShieldLockRegular +} from "@fluentui/react-icons"; +import { useStatusContext } from "./App"; +import { useCommonStyles } from "./commonStyles"; + +export const AddConfidential: React.FC = () => { + const styles = useCommonStyles(); + const { + statusMessage, setStatusMessage, + statusType, setStatusType, + isProcessing, setIsProcessing + } = useStatusContext(); + + const addConfidentialMarking = async () => { + setIsProcessing(true); + try { + await PowerPoint.run(async (context) => { + try { + // Get all slides in the presentation + const slides = context.presentation.slides; + slides.load("items"); + await context.sync(); + + // Get a reference slide to determine dimensions + // Since we can't access presentation width/height directly + if (slides.items.length === 0) { + setStatusMessage("No slides found in the presentation."); + setStatusType("warning"); + return; + } + + // Get dimensions from the first slide + const firstSlide = slides.items[0]; + + // Standard PowerPoint slide dimensions (in points) + // We'll use these as fallbacks and adjust if we can get actual dimensions + let slideWidth = 960; // Default slide width (10 inches * 72 points) + let slideHeight = 540; // Default slide height (7.5 inches * 72 points) + + // Process counter + let processedSlides = 0; + let errorSlides = 0; + + // Process each slide + for (let i = 0; i < slides.items.length; i++) { + try { + const slide = slides.items[i]; + + const positionFromTop = slideHeight - 23; + + // Create the textbox - make sure it spans the full width of the slide + // This is important for proper centering + const textBox = slide.shapes.addTextBox(""); + + // Make it span most of the width of the slide (90% centered) + // This ensures we have room for the text to be centered + const textBoxWidth = slideWidth * 1; // 0.9 + textBox.left = (slideWidth - textBoxWidth) / 2; // Center it horizontally + textBox.top = positionFromTop; + textBox.width = textBoxWidth; + textBox.height = 25; // Smaller height for footer text + + console.log("Starting PowerPoint add-in execution."); + console.log(textBox.left); + console.log(textBox.top); + console.log(textBox.width); + console.log(textBox.height); + await context.sync(); + + // Load textFrame to set text properties + textBox.load("textFrame"); + await context.sync(); + + if (textBox.textFrame) { + // Load textRange to set text and properties + textBox.textFrame.load("textRange"); + await context.sync(); + + // Need to load font and paragraphFormat + textBox.textFrame.textRange.load("font,paragraphFormat"); + await context.sync(); + + // Set font properties + try { + // Ensure the font is loaded properly before setting properties + textBox.textFrame.textRange.font.load(); + await context.sync(); + + // Set font properties exactly as in the VBA code + textBox.textFrame.textRange.font.name = "Inter"; + textBox.textFrame.textRange.font.size = 8; + textBox.textFrame.textRange.paragraphFormat.horizontalAlignment = "Center" + + // Set the color to RGB(218, 19, 53) + // Different APIs may need different color formats + textBox.textFrame.textRange.font.color = "#DA1335"; + + // Set the text + textBox.textFrame.textRange.text = "– Confidential –"; + } catch (fontError) { + console.error("Error setting font properties:", fontError); + // Even if we can't set the font properties exactly, continue with default font + } + + // Add a name/tag to the shape for identification + textBox.name = "ConfidentialMarking"; + + await context.sync(); + processedSlides++; + } + } catch (slideError) { + console.error(`Error processing slide ${i+1}:`, slideError); + errorSlides++; + // Continue to the next slide + continue; + } + } + + // Report results + if (processedSlides > 0) { + setStatusMessage(`Added confidential marking to ${processedSlides} slides.`); + setStatusType("success"); + } else if (errorSlides > 0) { + setStatusMessage(`Failed to add markings. Errors on ${errorSlides} slides.`); + setStatusType("error"); + } else { + setStatusMessage("No slides found to add confidential marking."); + setStatusType("warning"); + } + + } catch (innerError) { + console.error("Inner error:", innerError); + throw innerError; // Re-throw to outer catch + } + }); + } catch (error) { + setStatusMessage(`Error: ${error.message}`); + setStatusType("error"); + console.error("Add confidential error:", error); + } finally { + setIsProcessing(false); + } + }; + + return ( +
+
+ +
+
+ ); +}; + +export default AddConfidential; \ No newline at end of file diff --git a/src/taskpane/components/InsertTitles.tsx b/src/taskpane/components/InsertTitles.tsx new file mode 100644 index 00000000..d8272f27 --- /dev/null +++ b/src/taskpane/components/InsertTitles.tsx @@ -0,0 +1,172 @@ +import * as React from "react"; +import { + Button +} from "@fluentui/react-components"; +import { + TextBulletListRegular +} from "@fluentui/react-icons"; +import { useStatusContext } from "./App"; +import { useCommonStyles } from "./commonStyles"; + +export const InsertTitles: React.FC = () => { + const styles = useCommonStyles(); + const { + statusMessage, setStatusMessage, + statusType, setStatusType, + isProcessing, setIsProcessing + } = useStatusContext(); + + const collectAndInsertTitles = async () => { + setIsProcessing(true); + try { + await PowerPoint.run(async (context) => { + try { + // Get the selected shape + const selectedShapes = context.presentation.getSelectedShapes(); + selectedShapes.load("items"); + await context.sync(); + + // Check if any shape is selected + if (selectedShapes.items.length === 0) { + setStatusMessage("Please select a text box to insert slide titles."); + setStatusType("warning"); + return; + } + + // Get the first selected shape + const selectedShape = selectedShapes.items[0]; + + // Check if the selected shape has a text frame + selectedShape.load("textFrame"); + await context.sync(); + + if (!selectedShape.textFrame) { + setStatusMessage("Please select a text box to insert slide titles."); + setStatusType("warning"); + return; + } + + // Get all slides in the presentation + const slides = context.presentation.slides; + slides.load("items"); + await context.sync(); + + // Collect all slide titles using a simple approach + let titleText = ""; + let titlesCollected = 0; + + // Process each slide + for (let i = 0; i < slides.items.length; i++) { + try { + const slide = slides.items[i]; + + // Load only necessary shape properties + slide.load("shapes"); + await context.sync(); + + // Get title text using a simplified approach + // This gets all the shapes and looks for one with text content positioned at the top + const shapes = slide.shapes; + shapes.load("items"); + await context.sync(); + + // Look for shapes with text content + for (let j = 0; j < shapes.items.length; j++) { + try { + const shape = shapes.items[j]; + + // Only load textFrame property initially + shape.load("textFrame"); + await context.sync(); + + // Only proceed with shapes that have text frames + if (shape.textFrame) { + // Load text range to see if there's content + shape.textFrame.load("textRange"); + await context.sync(); + + if (shape.textFrame.textRange) { + shape.textFrame.textRange.load("text"); + await context.sync(); + + const shapeText = shape.textFrame.textRange.text; + + // Check if the shape has text and might be a title + if (shapeText && shapeText.trim() !== "") { + // Load position to see if it's at the top + shape.load("top"); + await context.sync(); + + // Titles are usually at the top of the slide + // Since we can't directly identify a title placeholder, + // we're using position as a heuristic + if (shape.top < 150) { + // Add the title to our collection + // titleText += `Slide ${i+1}: ${shapeText}\n`; + titleText += `${shapeText}\n`; + titlesCollected++; + break; // Only use the first potential title shape on each slide + } + } + } + } + } catch (shapeError) { + console.error(`Error processing shape on slide ${i+1}:`, shapeError); + // Continue to the next shape + continue; + } + } + } catch (slideError) { + console.error(`Error processing slide ${i+1}:`, slideError); + // Continue to the next slide + continue; + } + } + + // Insert the collected titles into the selected text frame + if (titleText) { + // Make sure we have a textRange on the selected shape + selectedShape.textFrame.load("textRange"); + await context.sync(); + + selectedShape.textFrame.textRange.text = titleText; + await context.sync(); + + setStatusMessage(`Collected and inserted ${titlesCollected} slide titles.`); + setStatusType("success"); + } else { + setStatusMessage("No slide titles found to insert."); + setStatusType("warning"); + } + } catch (innerError) { + console.error("Inner error:", innerError); + throw innerError; // Re-throw to outer catch + } + }); + } catch (error) { + setStatusMessage(`Error: ${error.message}`); + setStatusType("error"); + console.error("Collect titles error:", error); + } finally { + setIsProcessing(false); + } + }; + + return ( +
+
+ +
+
+ ); +}; + +export default InsertTitles; \ No newline at end of file diff --git a/src/taskpane/components/RemoveConfidential.tsx b/src/taskpane/components/RemoveConfidential.tsx new file mode 100644 index 00000000..9794d611 --- /dev/null +++ b/src/taskpane/components/RemoveConfidential.tsx @@ -0,0 +1,115 @@ +import * as React from "react"; +import { + Button +} from "@fluentui/react-components"; +import { + ShieldDismissRegular +} from "@fluentui/react-icons"; +import { useStatusContext } from "./App"; +import { useCommonStyles } from "./commonStyles"; + +export const RemoveConfidential: React.FC = () => { + const styles = useCommonStyles(); + const { + statusMessage, setStatusMessage, + statusType, setStatusType, + isProcessing, setIsProcessing + } = useStatusContext(); + + const removeConfidentialMarking = async () => { + setIsProcessing(true); + try { + await PowerPoint.run(async (context) => { + try { + // Get all slides in the presentation + const slides = context.presentation.slides; + slides.load("items"); + await context.sync(); + + if (slides.items.length === 0) { + setStatusMessage("No slides found in the presentation."); + setStatusType("warning"); + return; + } + + // Process counter + let processedSlides = 0; + let errorSlides = 0; + let removedCount = 0; + + // Process each slide + for (let i = 0; i < slides.items.length; i++) { + try { + const slide = slides.items[i]; + + // Load all shapes on the slide + slide.shapes.load("items"); + await context.sync(); + + // Find shapes with name "ConfidentialMarking" + for (let j = 0; j < slide.shapes.items.length; j++) { + const shape = slide.shapes.items[j]; + shape.load("name"); + await context.sync(); + + if (shape.name === "ConfidentialMarking") { + // Delete the confidential marking shape + shape.delete(); + removedCount++; + } + } + + await context.sync(); + processedSlides++; + } catch (slideError) { + console.error(`Error processing slide ${i+1}:`, slideError); + errorSlides++; + // Continue to the next slide + continue; + } + } + + // Report results + if (removedCount > 0) { + setStatusMessage(`Removed ${removedCount} confidential markings from ${processedSlides} slides.`); + setStatusType("success"); + } else if (errorSlides > 0) { + setStatusMessage(`Failed to remove markings. Errors on ${errorSlides} slides.`); + setStatusType("error"); + } else { + setStatusMessage("No confidential markings found to remove."); + setStatusType("info"); + } + + } catch (innerError) { + console.error("Inner error:", innerError); + throw innerError; // Re-throw to outer catch + } + }); + } catch (error) { + setStatusMessage(`Error: ${error.message}`); + setStatusType("error"); + console.error("Remove confidential error:", error); + } finally { + setIsProcessing(false); + } + }; + + return ( +
+
+ +
+
+ ); +}; + +export default RemoveConfidential; \ No newline at end of file