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

This commit is contained in:
2025-03-22 19:33:46 +01:00
parent 54328baaec
commit 9bb1bf2cdb
+212 -112
View File
@@ -1,3 +1,10 @@
/**
* @file DraftButtons.tsx
* @description Component that provides functionality to add and remove draft watermarks
* on PowerPoint master slides. The watermarks appear as large, light-colored text
* across the slide background.
*/
import * as React from "react"; import * as React from "react";
import { import {
Button, Button,
@@ -10,6 +17,42 @@ import {
import { useStatusContext } from "./App"; import { useStatusContext } from "./App";
import { useCommonStyles } from "./commonStyles"; import { useCommonStyles } from "./commonStyles";
/**
* Configuration constants for draft watermarks
*/
const DRAFT_CONFIG = {
// Shape name used to identify draft watermarks
SHAPE_NAME: "DraftWatermark",
// Font configuration
FONT: {
NAME: "Inter",
SIZE: 54,
COLOR: "#FFE9E8" // Light pink RGB(255, 233, 232)
},
// Text box configuration
TEXT_BOX: {
LEFT: -330,
TOP: 32,
WIDTH: 2400,
HEIGHT: 540
},
// Watermark text (repeated pattern)
TEXT: [
"DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT",
"DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT",
"DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT",
"DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT",
"DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT",
"DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT",
"DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT",
"DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT",
"DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT"
].join("\n")
};
/**
* Component-specific styles
*/
const useStyles = makeStyles({ const useStyles = makeStyles({
buttonGrid: { buttonGrid: {
display: "grid", display: "grid",
@@ -27,16 +70,78 @@ const useStyles = makeStyles({
} }
}); });
/**
* Interface for master slide processing results
*/
interface ProcessingResult {
processedMasters: number;
errorMasters: number;
affectedCount: number; // Either added or removed watermarks
}
/**
* DraftButtons component provides UI and functionality to add or remove
* draft watermarks on PowerPoint master slides.
*
* @returns React component
*/
export const DraftButtons: React.FC = () => { export const DraftButtons: 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 addDraftWatermark = 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 { processedMasters, errorMasters, affectedCount } = result;
if (affectedCount > 0) {
const action = isAddOperation ? "Added" : "Removed";
const pluralWatermark = affectedCount > 1 ? 's' : '';
const pluralMasters = processedMasters > 1 ? 's' : '';
setStatusMessage(`${action} draft watermark${pluralWatermark} to ${processedMasters} master slide${pluralMasters}.`);
setStatusType("success");
} else if (errorMasters > 0) {
const pluralMasters = errorMasters > 1 ? 's' : '';
setStatusMessage(`Failed to ${isAddOperation ? "add" : "remove"} watermarks. Errors on ${errorMasters} master slide${pluralMasters}.`);
setStatusType("error");
} else {
if (isAddOperation) {
setStatusMessage("No master slides found to add draft watermark.");
} else {
setStatusMessage("No draft watermark found to remove.");
}
setStatusType(isAddOperation ? "warning" : "info");
}
};
/**
* Adds draft watermarks to all master slides in the presentation
*/
const addDraftWatermark = async (): Promise<void> => {
setIsProcessing(true); setIsProcessing(true);
try { try {
await PowerPoint.run(async (context) => { await PowerPoint.run(async (context) => {
@@ -51,26 +156,47 @@ export const DraftButtons: React.FC = () => {
return; return;
} }
// Process counter const result = await processAddWatermarks(context, masters.items);
let processedMasters = 0; updateStatusFromResult(result, true);
let errorMasters = 0; });
} catch (error) {
handleError(error, "Add draft watermark");
} finally {
setIsProcessing(false);
}
};
/**
* Processes all master slides to add draft watermarks
*
* @param context - The PowerPoint API context
* @param masters - Array of master slides to process
* @returns Processing result with counts of processed and error slides
*/
const processAddWatermarks = async (
context: PowerPoint.RequestContext,
masters: PowerPoint.SlideMaster[]
): Promise<ProcessingResult> => {
const result: ProcessingResult = {
processedMasters: 0,
errorMasters: 0,
affectedCount: 0
};
// Process each slide master (usually there's just one) // Process each slide master (usually there's just one)
for (let i = 0; i < masters.items.length; i++) { for (let i = 0; i < masters.length; i++) {
try { try {
const master = masters.items[i]; const master = masters[i];
// Create the textbox on the master slide // Create the textbox on the master slide
const textBox = master.shapes.addTextBox(""); const textBox = master.shapes.addTextBox("");
// textBox.left = 0; // Center it horizontally // Set position and size in a single batch
textBox.left = -330 textBox.left = DRAFT_CONFIG.TEXT_BOX.LEFT;
textBox.top = 32; textBox.top = DRAFT_CONFIG.TEXT_BOX.TOP;
// textBox.width = 960; textBox.width = DRAFT_CONFIG.TEXT_BOX.WIDTH;
textBox.width = 2400; textBox.height = DRAFT_CONFIG.TEXT_BOX.HEIGHT;
textBox.height = 540; textBox.name = DRAFT_CONFIG.SHAPE_NAME;
await context.sync();
// Load textFrame to set text properties // Load textFrame to set text properties
textBox.load("textFrame"); textBox.load("textFrame");
@@ -81,84 +207,48 @@ export const DraftButtons: React.FC = () => {
textBox.textFrame.load("textRange"); textBox.textFrame.load("textRange");
await context.sync(); await context.sync();
// Need to load font and paragraphFormat // Set text and load font properties in a single batch
textBox.textFrame.textRange.text = DRAFT_CONFIG.TEXT;
textBox.textFrame.textRange.load("font,paragraphFormat"); textBox.textFrame.textRange.load("font,paragraphFormat");
await context.sync(); await context.sync();
// Set font properties
try { try {
// Ensure the font is loaded properly before setting properties // Apply all font properties in a single batch
textBox.textFrame.textRange.font.load(); const font = textBox.textFrame.textRange.font;
font.name = DRAFT_CONFIG.FONT.NAME;
font.size = DRAFT_CONFIG.FONT.SIZE;
font.bold = true;
font.color = DRAFT_CONFIG.FONT.COLOR;
// Set alignment
textBox.textFrame.verticalAlignment = "MiddleCentered";
await context.sync(); await context.sync();
result.affectedCount++;
// Set font properties exactly as in the VBA code
textBox.textFrame.textRange.font.name = "Inter";
// textBox.textFrame.textRange.font.size = 256;
textBox.textFrame.textRange.font.size = 54;
textBox.textFrame.textRange.font.bold = true;
textBox.textFrame.verticalAlignment = "MiddleCentered"
// Set the color to RGB(255, 233, 232)
// Different APIs may need different color formats
textBox.textFrame.textRange.font.color = "#FFE9E8";
// Set the text
textBox.textFrame.textRange.text =
"DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT\n" +
"DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT\n" +
"DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT\n" +
"DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT\n" +
"DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT\n" +
"DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT\n" +
"DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT\n" +
"DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT\n" +
"DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT\n";
} catch (fontError) { } catch (fontError) {
console.error("Error setting font properties:", fontError); console.error("Error setting font properties:", fontError);
// Even if we can't set the font properties exactly, continue with default font // Continue with default font if custom font fails
}
} }
// Add a name/tag to the shape for identification result.processedMasters++;
textBox.name = "DraftWatermark";
await context.sync();
processedMasters++;
}
} catch (masterError) { } catch (masterError) {
console.error(`Error processing master ${i+1}:`, masterError); console.error(`Error processing master ${i+1}:`, masterError);
errorMasters++; result.errorMasters++;
// Continue to the next master // Continue to the next master
continue;
} }
} }
// Report results return result;
if (processedMasters > 0) {
setStatusMessage(`Added draft watermark to ${processedMasters} master slide${processedMasters > 1 ? 's' : ''}.`);
setStatusType("success");
} else if (errorMasters > 0) {
setStatusMessage(`Failed to add markings. Errors on ${errorMasters} master slide${errorMasters > 1 ? 's' : ''}.`);
setStatusType("error");
} else {
setStatusMessage("No master slides found to add draft watermark.");
setStatusType("warning");
}
});
} catch (error) {
setStatusMessage(`Error: ${error.message}`);
setStatusType("error");
console.error("Add draft watermark error:", error);
} finally {
setIsProcessing(false);
}
}; };
const removeDraftWatermark = async () => { /**
* Removes draft watermarks from all master slides in the presentation
*/
const removeDraftWatermark = async (): Promise<void> => {
setIsProcessing(true); setIsProcessing(true);
try { try {
await PowerPoint.run(async (context) => { await PowerPoint.run(async (context) => {
try {
// Get the slide masters collection // Get the slide masters collection
const masters = context.presentation.slideMasters; const masters = context.presentation.slideMasters;
masters.load("items"); masters.load("items");
@@ -170,66 +260,76 @@ export const DraftButtons: React.FC = () => {
return; return;
} }
// Process counter const result = await processRemoveWatermarks(context, masters.items);
let processedMasters = 0; updateStatusFromResult(result, false);
let errorMasters = 0; });
let removedCount = 0; } catch (error) {
handleError(error, "Remove draft watermark");
} finally {
setIsProcessing(false);
}
};
/**
* Processes all master slides to remove draft watermarks
*
* @param context - The PowerPoint API context
* @param masters - Array of master slides to process
* @returns Processing result with counts of processed and affected slides
*/
const processRemoveWatermarks = async (
context: PowerPoint.RequestContext,
masters: PowerPoint.SlideMaster[]
): Promise<ProcessingResult> => {
const result: ProcessingResult = {
processedMasters: 0,
errorMasters: 0,
affectedCount: 0
};
// Process each master slide // Process each master slide
for (let i = 0; i < masters.items.length; i++) { for (let i = 0; i < masters.length; i++) {
try { try {
const master = masters.items[i]; const master = masters[i];
// Load all shapes on the master slide // Load all shapes on the master slide
master.shapes.load("items"); master.shapes.load("items");
await context.sync(); await context.sync();
// Find shapes with name "DraftWatermark" // Collect shapes to delete
const shapesToDelete: PowerPoint.Shape[] = [];
// Load all shape names in a single batch
for (let j = 0; j < master.shapes.items.length; j++) { for (let j = 0; j < master.shapes.items.length; j++) {
const shape = master.shapes.items[j]; const shape = master.shapes.items[j];
shape.load("name"); shape.load("name");
}
// Wait for all shape names to load
await context.sync(); await context.sync();
if (shape.name === "DraftWatermark") { // Now check names and collect shapes to delete
// Delete the draft watermark shape for (let j = 0; j < master.shapes.items.length; j++) {
shape.delete(); const shape = master.shapes.items[j];
removedCount++; if (shape.name === DRAFT_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(); await context.sync();
processedMasters++; result.processedMasters++;
} catch (masterError) { } catch (masterError) {
console.error(`Error processing master slide ${i+1}:`, masterError); console.error(`Error processing master slide ${i+1}:`, masterError);
errorMasters++; result.errorMasters++;
// Continue to the next master slide // Continue to the next master slide
continue;
} }
} }
// Report results return result;
if (removedCount > 0) {
setStatusMessage(`Removed ${removedCount} draft watermark${removedCount > 1 ? 's' : ''} from ${processedMasters} master slide${processedMasters > 1 ? 's' : ''}.`);
setStatusType("success");
} else if (errorMasters > 0) {
setStatusMessage(`Failed to remove draft watermarks. Errors on ${errorMasters} master slide${errorMasters > 1 ? 's' : ''}.`);
setStatusType("error");
} else {
setStatusMessage("No draft watermark 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 draft watermark error:", error);
} finally {
setIsProcessing(false);
}
}; };
return ( return (