Optimize ProgressBarButtons with performance improvements and documentation
- Reduce API calls by batching operations - Add performance monitoring - Create configuration constants - Improve code documentation - Enhance variable naming for readability
This commit is contained in:
@@ -10,6 +10,28 @@ import {
|
|||||||
import { useStatusContext } from "./App";
|
import { useStatusContext } from "./App";
|
||||||
import { useCommonStyles } from "./commonStyles";
|
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({
|
const useStyles = makeStyles({
|
||||||
buttonGrid: {
|
buttonGrid: {
|
||||||
display: "grid",
|
display: "grid",
|
||||||
@@ -36,108 +58,96 @@ export const ProgressBarButtons: React.FC = () => {
|
|||||||
isProcessing, setIsProcessing
|
isProcessing, setIsProcessing
|
||||||
} = useStatusContext();
|
} = 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 () => {
|
const addProgressBar = async () => {
|
||||||
setIsProcessing(true);
|
setIsProcessing(true);
|
||||||
|
const startTime = performance.now();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await PowerPoint.run(async (context) => {
|
await PowerPoint.run(async (context) => {
|
||||||
// Get all slides
|
// Get all slides at once
|
||||||
const presentation = context.presentation;
|
const presentation = context.presentation;
|
||||||
const slides = presentation.slides;
|
const slides = presentation.slides;
|
||||||
slides.load("items");
|
slides.load("items");
|
||||||
|
|
||||||
// Need to load the first slide to get access to dimensions
|
// Single sync to get all slides
|
||||||
await context.sync();
|
await context.sync();
|
||||||
|
|
||||||
if (slides.items.length === 0) {
|
if (slides.items.length === 0) {
|
||||||
throw new Error("No slides in the presentation");
|
throw new Error("No slides in the presentation");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get dimensions from first slide
|
// Calculate dimensions based on slide count
|
||||||
const firstSlide = slides.items[0];
|
const { slideWidth, slideHeight, barHeight, distanceFromBottom, overlap } = PROGRESS_BAR_CONFIG;
|
||||||
|
|
||||||
// We'll create our own width and height based on standard values
|
|
||||||
// since direct access to slide dimensions is tricky in Office.js
|
|
||||||
const slideWidth = 960; // Standard PowerPoint slide width in points
|
|
||||||
const slideHeight = 540; // Standard PowerPoint slide height in points
|
|
||||||
|
|
||||||
// Define progress bar properties
|
|
||||||
const progressBarHeight = 1; // Height in points
|
|
||||||
const segmentGap = 0; // Gap between segments
|
|
||||||
const startY = slideHeight - 28; // 28 points from bottom
|
|
||||||
|
|
||||||
// Load slides first to get count
|
|
||||||
await context.sync();
|
|
||||||
|
|
||||||
const slideCount = slides.items.length;
|
const slideCount = slides.items.length;
|
||||||
const segmentWidth = slideWidth / slideCount;
|
const segmentWidth = slideWidth / slideCount;
|
||||||
|
const startY = slideHeight - distanceFromBottom;
|
||||||
|
|
||||||
// Process each slide
|
// 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++) {
|
for (let i = 0; i < slideCount; i++) {
|
||||||
const slide = slides.items[i];
|
const slide = slides.items[i];
|
||||||
|
|
||||||
// Load slide properties to check layout
|
|
||||||
slide.load("layout");
|
|
||||||
await context.sync();
|
|
||||||
|
|
||||||
const layoutName = slide.layout.name;
|
const layoutName = slide.layout.name;
|
||||||
|
|
||||||
// Check if this is a layout we want to add the progress bar to
|
// Check if this is a layout we want to add the progress bar to
|
||||||
// Note: Office.js might not have exact same layout names as VBA,
|
const isCompatibleLayout = SUPPORTED_LAYOUTS.some(layout =>
|
||||||
// so we're checking for common layouts
|
layoutName.includes(layout)
|
||||||
if (layoutName.includes("Title") ||
|
);
|
||||||
layoutName.includes("Content") ||
|
|
||||||
layoutName.includes("Two Content") ||
|
if (isCompatibleLayout) {
|
||||||
layoutName.includes("Blank")) {
|
// Create all segments for this slide in a batch
|
||||||
|
|
||||||
// Add progress bar segments to this slide
|
|
||||||
for (let j = 0; j < slideCount; j++) {
|
for (let j = 0; j < slideCount; j++) {
|
||||||
// Create line for this segment with a small overlap to prevent gaps
|
// Calculate segment position and dimensions
|
||||||
const overlap = 0.5; // Small overlap to ensure no gaps between segments
|
|
||||||
const startX = j * segmentWidth;
|
const startX = j * segmentWidth;
|
||||||
const endX = (j + 1) * segmentWidth;
|
const endX = (j + 1) * segmentWidth;
|
||||||
|
|
||||||
// Create a rectangle with minimal height to simulate a line
|
// Create a rectangle with minimal height to simulate a line
|
||||||
// since proper line creation is challenging with the Office JS API
|
const segment = slide.shapes.addGeometricShape("Rectangle");
|
||||||
const line = slide.shapes.addGeometricShape("Rectangle");
|
|
||||||
|
|
||||||
// Set the rectangle's position to look like a line
|
// Set the rectangle's position to look like a line
|
||||||
// Adding a tiny bit of overlap to ensure no gaps
|
segment.left = startX - (j > 0 ? overlap : 0);
|
||||||
line.left = startX - (j > 0 ? overlap : 0); // Overlap with previous segment except for first
|
segment.top = startY;
|
||||||
line.top = startY;
|
segment.width = (endX - startX) + (j > 0 ? overlap : 0) + (j < slideCount - 1 ? overlap : 0);
|
||||||
line.width = (endX - startX) + (j > 0 ? overlap : 0) + (j < slideCount - 1 ? overlap : 0);
|
segment.height = barHeight;
|
||||||
line.height = progressBarHeight; // Very small height
|
|
||||||
|
|
||||||
// Set fill color based on progress instead of line color
|
// Set fill color based on progress
|
||||||
// We're using a rectangle, so fill is more appropriate than line
|
const isCompleted = j <= i;
|
||||||
if (j <= i) {
|
segment.fill.setSolidColor(isCompleted ?
|
||||||
// Blue for progressed segments (RGB 32, 81, 112)
|
PROGRESS_BAR_CONFIG.completedColor :
|
||||||
line.fill.setSolidColor("#205170");
|
PROGRESS_BAR_CONFIG.pendingColor
|
||||||
|
);
|
||||||
// Make the current slide indicator (last blue segment) slightly more prominent
|
|
||||||
if (j == i) {
|
// Make the current slide indicator slightly more prominent
|
||||||
// Make it wider and taller for emphasis
|
if (j == i) {
|
||||||
line.width += 1; // Add extra width
|
segment.width += 1;
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Grey for segments not yet progressed (RGB 242, 242, 242)
|
|
||||||
line.fill.setSolidColor("#F2F2F2");
|
|
||||||
|
|
||||||
// Make the current slide indicator (last blue segment) slightly more prominent
|
|
||||||
if (j == i) {
|
|
||||||
// Make it wider and taller for emphasis
|
|
||||||
line.width += 1; // Add extra width
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the border/outline entirely
|
// Remove the border/outline entirely
|
||||||
line.lineFormat.visible = false;
|
segment.lineFormat.visible = false;
|
||||||
|
|
||||||
// Since tags API might not be fully supported, use shape name as identifier
|
// Use shape name as identifier for later removal
|
||||||
line.name = "progress_bar_" + j;
|
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.");
|
setStatusMessage("Added progress bar to slides.");
|
||||||
setStatusType("success");
|
setStatusType("success");
|
||||||
});
|
});
|
||||||
@@ -150,53 +160,68 @@ export const ProgressBarButtons: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all progress bar elements from all slides in the presentation
|
||||||
|
* Identifies progress bar elements by their name prefix "progress_bar_"
|
||||||
|
*/
|
||||||
const removeProgressBar = async () => {
|
const removeProgressBar = async () => {
|
||||||
setIsProcessing(true);
|
setIsProcessing(true);
|
||||||
|
const startTime = performance.now();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await PowerPoint.run(async (context) => {
|
await PowerPoint.run(async (context) => {
|
||||||
// Get all slides
|
// Get all slides at once
|
||||||
const presentation = context.presentation;
|
const presentation = context.presentation;
|
||||||
const slides = presentation.slides;
|
const slides = presentation.slides;
|
||||||
slides.load("items");
|
slides.load("items");
|
||||||
await context.sync();
|
await context.sync();
|
||||||
|
|
||||||
let removedCount = 0;
|
let removedCount = 0;
|
||||||
|
const shapesToLoad = [];
|
||||||
|
|
||||||
// Process each slide
|
// First, load all shapes from all slides in a batch
|
||||||
for (let i = 0; i < slides.items.length; i++) {
|
for (let i = 0; i < slides.items.length; i++) {
|
||||||
const slide = slides.items[i];
|
const slide = slides.items[i];
|
||||||
|
|
||||||
// Load all shapes in this slide
|
|
||||||
const shapes = slide.shapes;
|
const shapes = slide.shapes;
|
||||||
shapes.load("items");
|
shapes.load("items");
|
||||||
await context.sync();
|
shapesToLoad.push({ slide, shapes });
|
||||||
|
}
|
||||||
// Find shapes with our tag
|
|
||||||
const progressBarShapes = [];
|
// 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++) {
|
for (let j = 0; j < shapes.items.length; j++) {
|
||||||
const shape = shapes.items[j];
|
const shape = shapes.items[j];
|
||||||
// Check if this shape name indicates it's a progress bar
|
|
||||||
shape.load("name");
|
shape.load("name");
|
||||||
await context.sync();
|
allShapesWithNames.push({ slide, shape });
|
||||||
|
|
||||||
// Look for shapes with names starting with "progress_bar_"
|
|
||||||
if (shape.name && shape.name.startsWith("progress_bar_")) {
|
|
||||||
progressBarShapes.push(shape);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete all progress bar shapes
|
|
||||||
for (const shape of progressBarShapes) {
|
|
||||||
shape.delete();
|
|
||||||
removedCount++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Single sync to get all shape names
|
||||||
await context.sync();
|
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) {
|
if (removedCount > 0) {
|
||||||
// setStatusMessage(`Removed ${removedCount} progress bar elements from slides.`);
|
|
||||||
// Added progress bar to slides.
|
|
||||||
setStatusMessage(`Removed progress bar from slides.`);
|
setStatusMessage(`Removed progress bar from slides.`);
|
||||||
} else {
|
} else {
|
||||||
setStatusMessage("No progress bar found to remove.");
|
setStatusMessage("No progress bar found to remove.");
|
||||||
@@ -240,4 +265,4 @@ export const ProgressBarButtons: React.FC = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ProgressBarButtons;
|
export default ProgressBarButtons;
|
||||||
|
|||||||
Reference in New Issue
Block a user