Initial commit of ProgressBar file

This commit is contained in:
2025-03-10 18:06:27 +01:00
parent 29b35a0bfd
commit 213b503c65
@@ -0,0 +1,227 @@
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";
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();
const addProgressBar = async () => {
setIsProcessing(true);
try {
await PowerPoint.run(async (context) => {
// Get all slides
const presentation = context.presentation;
const slides = presentation.slides;
slides.load("items");
// Need to load the first slide to get access to dimensions
await context.sync();
if (slides.items.length === 0) {
throw new Error("No slides in the presentation");
}
// Get dimensions from first slide
const firstSlide = slides.items[0];
// 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 segmentWidth = slideWidth / slideCount;
// Process each slide
for (let i = 0; i < slideCount; i++) {
const slide = slides.items[i];
// Load slide properties to check layout
slide.load("layout");
await context.sync();
const layoutName = slide.layout.name;
// 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,
// so we're checking for common layouts
if (layoutName.includes("Title") ||
layoutName.includes("Content") ||
layoutName.includes("Two Content") ||
layoutName.includes("Blank")) {
// Add progress bar segments to this slide
for (let j = 0; j < slideCount; j++) {
// Create line for this segment
const startX = j * segmentWidth;
const endX = (j + 1) * segmentWidth - segmentGap;
// Create a rectangle with minimal height to simulate a line
// since proper line creation is challenging with the Office JS API
const line = slide.shapes.addGeometricShape("Rectangle");
// Set the rectangle's position to look like a line
line.left = startX;
line.top = startY;
line.width = endX - startX;
line.height = progressBarHeight; // Very small height
// Set fill color based on progress instead of line color
// We're using a rectangle, so fill is more appropriate than line
if (j <= i) {
// Blue for progressed segments (RGB 32, 81, 112)
line.fill.setSolidColor("#205170");
} else {
// Grey for segments not yet progressed (RGB 242, 242, 242)
line.fill.setSolidColor("#F2F2F2");
}
// Remove the border/outline entirely
line.lineFormat.visible = false;
// Since tags API might not be fully supported, use shape name as identifier
line.name = "progress_bar_" + j;
}
}
}
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);
}
};
const removeProgressBar = async () => {
setIsProcessing(true);
try {
await PowerPoint.run(async (context) => {
// Get all slides
const presentation = context.presentation;
const slides = presentation.slides;
slides.load("items");
await context.sync();
let removedCount = 0;
// Process each slide
for (let i = 0; i < slides.items.length; i++) {
const slide = slides.items[i];
// Load all shapes in this slide
const shapes = slide.shapes;
shapes.load("items");
await context.sync();
// Find shapes with our tag
const progressBarShapes = [];
for (let j = 0; j < shapes.items.length; j++) {
const shape = shapes.items[j];
// Check if this shape name indicates it's a progress bar
shape.load("name");
await context.sync();
// 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++;
}
}
await context.sync();
if (removedCount > 0) {
setStatusMessage(`Removed ${removedCount} progress bar elements from slides.`);
} else {
setStatusMessage("No progress bar elements 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;