1aeef10ebe
- Add file-level JSDoc comments to components that were missing them - Ensure consistent documentation format across all components - Improve code maintainability and readability
276 lines
8.6 KiB
TypeScript
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;
|