Files
powerpoint-toolbox/src/taskpane/components/ProgressBarButtons.tsx
T
schihei 1aeef10ebe Standardize documentation across all *.tsx files
- Add file-level JSDoc comments to components that were missing them
- Ensure consistent documentation format across all components
- Improve code maintainability and readability
2025-03-22 19:53:09 +01:00

276 lines
8.6 KiB
TypeScript

/**
* @file ProgressBarButtons.tsx
* @description Component that provides functionality to add and remove progress bars
* on PowerPoint slides. The progress bars visually indicate the current position within
* the presentation, with colored segments showing completed and upcoming slides.
*/
import * as React from "react";
import {
Button,
makeStyles
} from "@fluentui/react-components";
import {
GaugeRegular,
DismissRegular
} from "@fluentui/react-icons";
import { useStatusContext } from "./App";
import { useCommonStyles } from "./commonStyles";
// Constants for progress bar configuration
const PROGRESS_BAR_CONFIG = {
// Standard PowerPoint slide dimensions in points
slideWidth: 960,
slideHeight: 540,
// Progress bar appearance
barHeight: 1,
distanceFromBottom: 28,
overlap: 0.5, // Small overlap to ensure no gaps between segments
// Colors
completedColor: "#205170", // Blue for progressed segments (RGB 32, 81, 112)
pendingColor: "#F2F2F2" // Grey for segments not yet progressed (RGB 242, 242, 242)
};
// Layouts that should receive progress bars
const SUPPORTED_LAYOUTS = [
"Title",
"Content",
"Two Content",
"Blank"
];
const useStyles = makeStyles({
buttonGrid: {
display: "grid",
gridTemplateColumns: "repeat(2, 1fr)",
gap: "8px",
width: "100%"
},
progressButton: {
width: "100%",
minWidth: 0,
padding: "8px 4px",
"& span": {
justifyContent: "center"
}
}
});
export const ProgressBarButtons: React.FC = () => {
const styles = useStyles();
const commonStyles = useCommonStyles();
const {
statusMessage, setStatusMessage,
statusType, setStatusType,
isProcessing, setIsProcessing
} = useStatusContext();
/**
* Adds a progress bar to the bottom of each slide in the presentation
* The progress bar shows the current position within the presentation
* with colored segments indicating progress
*/
const addProgressBar = async () => {
setIsProcessing(true);
const startTime = performance.now();
try {
await PowerPoint.run(async (context) => {
// Get all slides at once
const presentation = context.presentation;
const slides = presentation.slides;
slides.load("items");
// Single sync to get all slides
await context.sync();
if (slides.items.length === 0) {
throw new Error("No slides in the presentation");
}
// Calculate dimensions based on slide count
const { slideWidth, slideHeight, barHeight, distanceFromBottom, overlap } = PROGRESS_BAR_CONFIG;
const slideCount = slides.items.length;
const segmentWidth = slideWidth / slideCount;
const startY = slideHeight - distanceFromBottom;
// Load all slide layouts at once to reduce API calls
for (let i = 0; i < slideCount; i++) {
slides.items[i].load("layout");
}
// Single sync to get all layouts
await context.sync();
// Process each slide without unnecessary syncs
for (let i = 0; i < slideCount; i++) {
const slide = slides.items[i];
const layoutName = slide.layout.name;
// Check if this is a layout we want to add the progress bar to
const isCompatibleLayout = SUPPORTED_LAYOUTS.some(layout =>
layoutName.includes(layout)
);
if (isCompatibleLayout) {
// Create all segments for this slide in a batch
for (let j = 0; j < slideCount; j++) {
// Calculate segment position and dimensions
const startX = j * segmentWidth;
const endX = (j + 1) * segmentWidth;
// Create a rectangle with minimal height to simulate a line
const segment = slide.shapes.addGeometricShape("Rectangle");
// Set the rectangle's position to look like a line
segment.left = startX - (j > 0 ? overlap : 0);
segment.top = startY;
segment.width = (endX - startX) + (j > 0 ? overlap : 0) + (j < slideCount - 1 ? overlap : 0);
segment.height = barHeight;
// Set fill color based on progress
const isCompleted = j <= i;
segment.fill.setSolidColor(isCompleted ?
PROGRESS_BAR_CONFIG.completedColor :
PROGRESS_BAR_CONFIG.pendingColor
);
// Make the current slide indicator slightly more prominent
if (j == i) {
segment.width += 1;
}
// Remove the border/outline entirely
segment.lineFormat.visible = false;
// Use shape name as identifier for later removal
segment.name = "progress_bar_" + j;
}
}
}
// Single final sync to commit all changes
await context.sync();
const endTime = performance.now();
console.log(`Progress bar creation took ${endTime - startTime}ms`);
setStatusMessage("Added progress bar to slides.");
setStatusType("success");
});
} catch (error) {
setStatusMessage(`Error: ${error.message}`);
setStatusType("error");
console.error("Add progress bar error:", error);
} finally {
setIsProcessing(false);
}
};
/**
* Removes all progress bar elements from all slides in the presentation
* Identifies progress bar elements by their name prefix "progress_bar_"
*/
const removeProgressBar = async () => {
setIsProcessing(true);
const startTime = performance.now();
try {
await PowerPoint.run(async (context) => {
// Get all slides at once
const presentation = context.presentation;
const slides = presentation.slides;
slides.load("items");
await context.sync();
let removedCount = 0;
const shapesToLoad = [];
// First, load all shapes from all slides in a batch
for (let i = 0; i < slides.items.length; i++) {
const slide = slides.items[i];
const shapes = slide.shapes;
shapes.load("items");
shapesToLoad.push({ slide, shapes });
}
// Single sync to get all shapes
await context.sync();
// Now load all shape names in a batch
const allShapesWithNames = [];
for (const { slide, shapes } of shapesToLoad) {
for (let j = 0; j < shapes.items.length; j++) {
const shape = shapes.items[j];
shape.load("name");
allShapesWithNames.push({ slide, shape });
}
}
// Single sync to get all shape names
await context.sync();
// Filter shapes that are progress bars and delete them
const progressBarShapes = allShapesWithNames.filter(
({ shape }) => shape.name && shape.name.startsWith("progress_bar_")
);
// Delete all progress bar shapes
for (const { shape } of progressBarShapes) {
shape.delete();
removedCount++;
}
// Single final sync to commit all deletions
await context.sync();
const endTime = performance.now();
console.log(`Progress bar removal took ${endTime - startTime}ms`);
if (removedCount > 0) {
setStatusMessage(`Removed progress bar from slides.`);
} else {
setStatusMessage("No progress bar found to remove.");
}
setStatusType("success");
});
} catch (error) {
setStatusMessage(`Error: ${error.message}`);
setStatusType("error");
console.error("Remove progress bar error:", error);
} finally {
setIsProcessing(false);
}
};
return (
<div className={commonStyles.container}>
<div className={styles.buttonGrid}>
<Button
appearance="primary"
className={styles.progressButton}
onClick={addProgressBar}
icon={<GaugeRegular />}
disabled={isProcessing}
title="Add Progress Bar"
>
Add
</Button>
<Button
appearance="primary"
className={styles.progressButton}
onClick={removeProgressBar}
icon={<DismissRegular />}
disabled={isProcessing}
title="Remove Progress Bar"
>
Remove
</Button>
</div>
</div>
);
};
export default ProgressBarButtons;