Optimize ConfidentialButtons component with improved documentation, code structure, and performance

This commit is contained in:
2025-03-22 19:31:07 +01:00
parent fea088fe26
commit 54328baaec
+266 -187
View File
@@ -1,3 +1,9 @@
/**
* @file ConfidentialButtons.tsx
* @description Component that provides functionality to add and remove confidential markings
* to PowerPoint slides. The markings appear as text at the bottom of each slide.
*/
import * as React from "react"; import * as React from "react";
import { import {
Button, Button,
@@ -10,6 +16,35 @@ import {
import { useStatusContext } from "./App"; import { useStatusContext } from "./App";
import { useCommonStyles } from "./commonStyles"; import { useCommonStyles } from "./commonStyles";
/**
* Configuration constants for confidential markings
*/
const CONFIDENTIAL_CONFIG = {
// Marking text that appears on slides
TEXT: " Confidential ",
// Shape name used to identify confidential markings
SHAPE_NAME: "ConfidentialMarking",
// Font configuration
FONT: {
NAME: "Inter",
SIZE: 8,
COLOR: "#DA1335" // RGB(218, 19, 53)
},
// Default slide dimensions in points (if we can't get actual dimensions)
SLIDE: {
WIDTH: 960, // 10 inches * 72 points
HEIGHT: 540, // 7.5 inches * 72 points
},
// Text box configuration
TEXT_BOX: {
HEIGHT: 25,
POSITION_FROM_BOTTOM: 23
}
};
/**
* Component-specific styles
*/
const useStyles = makeStyles({ const useStyles = makeStyles({
buttonGrid: { buttonGrid: {
display: "grid", display: "grid",
@@ -27,220 +62,264 @@ const useStyles = makeStyles({
} }
}); });
/**
* Interface for slide processing results
*/
interface ProcessingResult {
processedSlides: number;
errorSlides: number;
affectedCount: number; // Either added or removed markings
}
/**
* ConfidentialButtons component provides UI and functionality to add or remove
* confidential markings on PowerPoint slides.
*
* @returns React component
*/
export const ConfidentialButtons: React.FC = () => { export const ConfidentialButtons: React.FC = () => {
const styles = useStyles(); const styles = useStyles();
const commonStyles = useCommonStyles(); const commonStyles = useCommonStyles();
const { const {
statusMessage, setStatusMessage, setStatusMessage,
statusType, setStatusType, setStatusType,
isProcessing, setIsProcessing isProcessing,
setIsProcessing
} = useStatusContext(); } = useStatusContext();
const addConfidentialMarking = async () => { /**
* Handles errors and updates the status message
*
* @param error - The error object
* @param operation - The operation being performed (for logging)
*/
const handleError = (error: any, operation: string): void => {
setStatusMessage(`Error: ${error.message || "Unknown error occurred"}`);
setStatusType("error");
console.error(`${operation} error:`, error);
setIsProcessing(false);
};
/**
* Updates the status message based on processing results
*
* @param result - The processing result object
* @param isAddOperation - Whether this was an add operation (true) or remove operation (false)
*/
const updateStatusFromResult = (result: ProcessingResult, isAddOperation: boolean): void => {
const { processedSlides, errorSlides, affectedCount } = result;
if (affectedCount > 0) {
const action = isAddOperation ? "Added" : "Removed";
setStatusMessage(`${action} confidential marking to ${affectedCount} slides.`);
setStatusType("success");
} else if (errorSlides > 0) {
setStatusMessage(`Failed to ${isAddOperation ? "add" : "remove"} markings. Errors on ${errorSlides} slides.`);
setStatusType("error");
} else {
if (isAddOperation) {
setStatusMessage("No slides found to add confidential marking.");
} else {
setStatusMessage("No confidential markings found to remove.");
}
setStatusType(isAddOperation ? "warning" : "info");
}
};
/**
* Adds confidential markings to all slides in the presentation
*/
const addConfidentialMarking = async (): Promise<void> => {
setIsProcessing(true); setIsProcessing(true);
try { try {
await PowerPoint.run(async (context) => { await PowerPoint.run(async (context) => {
try { // Get all slides in the presentation
// Get all slides in the presentation const slides = context.presentation.slides;
const slides = context.presentation.slides; slides.load("items");
slides.load("items"); await context.sync();
await context.sync();
if (slides.items.length === 0) {
// Get a reference slide to determine dimensions setStatusMessage("No slides found in the presentation.");
// Since we can't access presentation width/height directly setStatusType("warning");
if (slides.items.length === 0) { return;
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
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
} }
const result = await processAddMarkings(context, slides.items);
updateStatusFromResult(result, true);
}); });
} catch (error) { } catch (error) {
setStatusMessage(`Error: ${error.message}`); handleError(error, "Add confidential");
setStatusType("error");
console.error("Add confidential error:", error);
} finally { } finally {
setIsProcessing(false); setIsProcessing(false);
} }
}; };
const removeConfidentialMarking = async () => { /**
* Processes all slides to add confidential markings
*
* @param context - The PowerPoint API context
* @param slides - Array of slides to process
* @returns Processing result with counts of processed and error slides
*/
const processAddMarkings = async (
context: PowerPoint.RequestContext,
slides: PowerPoint.Slide[]
): Promise<ProcessingResult> => {
const result: ProcessingResult = {
processedSlides: 0,
errorSlides: 0,
affectedCount: 0
};
const { WIDTH, HEIGHT } = CONFIDENTIAL_CONFIG.SLIDE;
const slideWidth = WIDTH;
const slideHeight = HEIGHT;
// Process each slide
for (let i = 0; i < slides.length; i++) {
try {
const slide = slides[i];
const positionFromTop = slideHeight - CONFIDENTIAL_CONFIG.TEXT_BOX.POSITION_FROM_BOTTOM;
// Create the textbox spanning the full width of the slide
const textBox = slide.shapes.addTextBox("");
textBox.left = 0; // Start from left edge
textBox.top = positionFromTop;
textBox.width = slideWidth;
textBox.height = CONFIDENTIAL_CONFIG.TEXT_BOX.HEIGHT;
textBox.name = CONFIDENTIAL_CONFIG.SHAPE_NAME;
// Load textFrame and its properties in a single batch
textBox.load("textFrame");
await context.sync();
if (textBox.textFrame) {
// Load textRange and its properties
textBox.textFrame.load("textRange");
await context.sync();
// Set text and formatting in a single batch
textBox.textFrame.textRange.text = CONFIDENTIAL_CONFIG.TEXT;
textBox.textFrame.textRange.load("font,paragraphFormat");
await context.sync();
try {
// Apply font properties in a single batch
const font = textBox.textFrame.textRange.font;
font.name = CONFIDENTIAL_CONFIG.FONT.NAME;
font.size = CONFIDENTIAL_CONFIG.FONT.SIZE;
font.color = CONFIDENTIAL_CONFIG.FONT.COLOR;
textBox.textFrame.textRange.paragraphFormat.horizontalAlignment = "Center";
await context.sync();
result.affectedCount++;
} catch (fontError) {
console.error("Error setting font properties:", fontError);
// Continue with default font if custom font fails
}
}
result.processedSlides++;
} catch (slideError) {
console.error(`Error processing slide ${i+1}:`, slideError);
result.errorSlides++;
// Continue to the next slide
}
}
return result;
};
/**
* Removes confidential markings from all slides in the presentation
*/
const removeConfidentialMarking = async (): Promise<void> => {
setIsProcessing(true); setIsProcessing(true);
try { try {
await PowerPoint.run(async (context) => { await PowerPoint.run(async (context) => {
try { // Get all slides in the presentation
// Get all slides in the presentation const slides = context.presentation.slides;
const slides = context.presentation.slides; slides.load("items");
slides.load("items"); await context.sync();
await context.sync();
if (slides.items.length === 0) {
if (slides.items.length === 0) { setStatusMessage("No slides found in the presentation.");
setStatusMessage("No slides found in the presentation."); setStatusType("warning");
setStatusType("warning"); return;
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
} }
const result = await processRemoveMarkings(context, slides.items);
updateStatusFromResult(result, false);
}); });
} catch (error) { } catch (error) {
setStatusMessage(`Error: ${error.message}`); handleError(error, "Remove confidential");
setStatusType("error");
console.error("Remove confidential error:", error);
} finally { } finally {
setIsProcessing(false); setIsProcessing(false);
} }
}; };
/**
* Processes all slides to remove confidential markings
*
* @param context - The PowerPoint API context
* @param slides - Array of slides to process
* @returns Processing result with counts of processed and affected slides
*/
const processRemoveMarkings = async (
context: PowerPoint.RequestContext,
slides: PowerPoint.Slide[]
): Promise<ProcessingResult> => {
const result: ProcessingResult = {
processedSlides: 0,
errorSlides: 0,
affectedCount: 0
};
// Process each slide
for (let i = 0; i < slides.length; i++) {
try {
const slide = slides[i];
// Load all shapes on the slide
slide.shapes.load("items");
await context.sync();
// Collect shapes to delete
const shapesToDelete: PowerPoint.Shape[] = [];
// Find shapes with the confidential marking name
for (let j = 0; j < slide.shapes.items.length; j++) {
const shape = slide.shapes.items[j];
shape.load("name");
}
// Wait for all shape names to load
await context.sync();
// Now check names and collect shapes to delete
for (let j = 0; j < slide.shapes.items.length; j++) {
const shape = slide.shapes.items[j];
if (shape.name === CONFIDENTIAL_CONFIG.SHAPE_NAME) {
shapesToDelete.push(shape);
}
}
// Delete all matching shapes in one batch
shapesToDelete.forEach(shape => shape.delete());
result.affectedCount += shapesToDelete.length;
await context.sync();
result.processedSlides++;
} catch (slideError) {
console.error(`Error processing slide ${i+1}:`, slideError);
result.errorSlides++;
// Continue to the next slide
}
}
return result;
};
return ( return (
<div className={commonStyles.container}> <div className={commonStyles.container}>
<div className={styles.buttonGrid}> <div className={styles.buttonGrid}>
@@ -269,4 +348,4 @@ export const ConfidentialButtons: React.FC = () => {
); );
}; };
export default ConfidentialButtons; export default ConfidentialButtons;