Optimize performance by batching context.sync() calls

- GridGuidelineManager: Load all shape names upfront and apply operations in batches
- AlignmentButtons: Replaced for loops with forEach and reduced sync calls
- MatchProperties: Reorganized code to batch load operations and property assignments

This optimization significantly reduces round-trips between JavaScript and the Office application,
improving performance and responsiveness of the add-in.

🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-03-14 23:56:54 +01:00
parent d09dec4706
commit 07b0232726
3 changed files with 91 additions and 74 deletions
+30 -24
View File
@@ -56,10 +56,11 @@ export const AlignmentButtons: React.FC = () => {
return; return;
} }
// Loop through all select shapes and align them // Set alignment for all shapes at once
for (let i = 0; i < shapes.items.length; i++) { shapes.items.forEach(shape => {
shapes.items[i].left = 81.1; shape.left = 81.1;
} });
// Single sync after all updates
await context.sync(); await context.sync();
setStatusMessage("Objects aligned to left."); setStatusMessage("Objects aligned to left.");
@@ -90,10 +91,11 @@ export const AlignmentButtons: React.FC = () => {
return; return;
} }
// Loop through all select shapes and align them // Set alignment for all shapes at once
for (let i = 0; i < shapes.items.length; i++) { shapes.items.forEach(shape => {
shapes.items[i].left = 480 - (shapes.items[i].width / 2); shape.left = 480 - (shape.width / 2);
} });
// Single sync after all updates
await context.sync(); await context.sync();
setStatusMessage("Objects aligned to center."); setStatusMessage("Objects aligned to center.");
@@ -124,10 +126,11 @@ export const AlignmentButtons: React.FC = () => {
return; return;
} }
// Loop through all select shapes and align them // Set alignment for all shapes at once
for (let i = 0; i < shapes.items.length; i++) { shapes.items.forEach(shape => {
shapes.items[i].left = 879.75 - shapes.items[i].width; shape.left = 879.75 - shape.width;
} });
// Single sync after all updates
await context.sync(); await context.sync();
setStatusMessage("Objects aligned to right."); setStatusMessage("Objects aligned to right.");
@@ -158,10 +161,11 @@ export const AlignmentButtons: React.FC = () => {
return; return;
} }
// Loop through all select shapes and align them // Set alignment for all shapes at once
for (let i = 0; i < shapes.items.length; i++) { shapes.items.forEach(shape => {
shapes.items[i].top = 136.75; shape.top = 136.75;
} });
// Single sync after all updates
await context.sync(); await context.sync();
setStatusMessage("Objects aligned to top."); setStatusMessage("Objects aligned to top.");
@@ -192,10 +196,11 @@ export const AlignmentButtons: React.FC = () => {
return; return;
} }
// Loop through all select shapes and align them // Set alignment for all shapes at once
for (let i = 0; i < shapes.items.length; i++) { shapes.items.forEach(shape => {
shapes.items[i].top = 321 - (shapes.items[i].height / 2); shape.top = 321 - (shape.height / 2);
} });
// Single sync after all updates
await context.sync(); await context.sync();
setStatusMessage("Objects aligned to middle."); setStatusMessage("Objects aligned to middle.");
@@ -226,10 +231,11 @@ export const AlignmentButtons: React.FC = () => {
return; return;
} }
// Loop through all select shapes and align them // Set alignment for all shapes at once
for (let i = 0; i < shapes.items.length; i++) { shapes.items.forEach(shape => {
shapes.items[i].top = 505.25 - shapes.items[i].height; shape.top = 505.25 - shape.height;
} });
// Single sync after all updates
await context.sync(); await context.sync();
setStatusMessage("Objects aligned to bottom."); setStatusMessage("Objects aligned to bottom.");
@@ -241,13 +241,17 @@ export const GridGuidelineManager: React.FC = () => {
shapes.load("items"); shapes.load("items");
await context.sync(); await context.sync();
// Find shapes that are part of our grid // Load all shape names at once
const allShapes = shapes.items;
for (let i = 0; i < allShapes.length; i++) {
allShapes[i].load("name");
}
await context.sync();
// Find shapes that are part of our grid and mark them for deletion
let removedCount = 0; let removedCount = 0;
for (let i = 0; i < shapes.items.length; i++) { for (let i = 0; i < allShapes.length; i++) {
const shape = shapes.items[i]; const shape = allShapes[i];
shape.load("name");
await context.sync();
// Check if this shape is part of our grid // Check if this shape is part of our grid
if (shape.name && shape.name.startsWith(GRID_PREFIX)) { if (shape.name && shape.name.startsWith(GRID_PREFIX)) {
shape.delete(); shape.delete();
@@ -255,6 +259,7 @@ export const GridGuidelineManager: React.FC = () => {
} }
} }
// Single sync after all deletions
await context.sync(); await context.sync();
if (showStatus) { if (showStatus) {
+50 -44
View File
@@ -74,10 +74,27 @@ export const MatchProperties: React.FC = () => {
} }
} }
// First, load all text frames in a batch
if (hasText) {
// Pre-load textFrame for all target shapes
for (let i = 1; i < shapes.items.length; i++) {
shapes.items[i].load("textFrame");
}
await context.sync();
// For shapes that have textFrames, load their textRanges
for (let i = 1; i < shapes.items.length; i++) {
if (shapes.items[i].textFrame) {
shapes.items[i].textFrame.load("textRange");
}
}
await context.sync();
}
// Now apply properties to all shapes
let successCount = 0; let successCount = 0;
let textStyleCount = 0; let textStyleCount = 0;
// Loop through remaining shapes and apply properties
for (let i = 1; i < shapes.items.length; i++) { for (let i = 1; i < shapes.items.length; i++) {
try { try {
const targetShape = shapes.items[i]; const targetShape = shapes.items[i];
@@ -100,7 +117,6 @@ export const MatchProperties: React.FC = () => {
targetShape.lineFormat.color = firstShape.lineFormat.color; targetShape.lineFormat.color = firstShape.lineFormat.color;
} }
await context.sync();
propertiesApplied = true; propertiesApplied = true;
} catch (err) { } catch (err) {
console.error(`Error applying line format to shape ${i}:`, err); console.error(`Error applying line format to shape ${i}:`, err);
@@ -117,56 +133,43 @@ export const MatchProperties: React.FC = () => {
targetShape.fill.foregroundColor = firstShape.fill.foregroundColor; targetShape.fill.foregroundColor = firstShape.fill.foregroundColor;
} }
await context.sync();
propertiesApplied = true; propertiesApplied = true;
} catch (err) { } catch (err) {
console.error(`Error applying fill format to shape ${i}:`, err); console.error(`Error applying fill format to shape ${i}:`, err);
} }
// Apply text properties if the source has text // Apply text properties if the source has text
if (hasText) { if (hasText && targetShape.textFrame && targetShape.textFrame.textRange) {
try { try {
// First check if target shape has text const sourceFont = firstShape.textFrame.textRange.font;
targetShape.load("textFrame"); const targetFont = targetShape.textFrame.textRange.font;
await context.sync();
if (targetShape.textFrame) { // Apply font properties in batch
targetShape.textFrame.load("textRange"); if (sourceFont.name !== undefined) {
await context.sync(); targetFont.name = sourceFont.name;
if (targetShape.textFrame.textRange) {
const sourceFont = firstShape.textFrame.textRange.font;
const targetFont = targetShape.textFrame.textRange.font;
// Apply font properties
if (sourceFont.name !== undefined) {
targetFont.name = sourceFont.name;
}
if (sourceFont.size !== undefined) {
targetFont.size = sourceFont.size;
}
if (sourceFont.bold !== undefined) {
targetFont.bold = sourceFont.bold;
}
if (sourceFont.italic !== undefined) {
targetFont.italic = sourceFont.italic;
}
if (sourceFont.underline !== undefined) {
targetFont.underline = sourceFont.underline;
}
if (sourceFont.color !== undefined) {
targetFont.color = sourceFont.color;
}
await context.sync();
textStyleCount++;
}
} }
if (sourceFont.size !== undefined) {
targetFont.size = sourceFont.size;
}
if (sourceFont.bold !== undefined) {
targetFont.bold = sourceFont.bold;
}
if (sourceFont.italic !== undefined) {
targetFont.italic = sourceFont.italic;
}
if (sourceFont.underline !== undefined) {
targetFont.underline = sourceFont.underline;
}
if (sourceFont.color !== undefined) {
targetFont.color = sourceFont.color;
}
textStyleCount++;
} catch (err) { } catch (err) {
console.error(`Error applying text format to shape ${i}:`, err); console.error(`Error applying text format to shape ${i}:`, err);
} }
@@ -180,6 +183,9 @@ export const MatchProperties: React.FC = () => {
console.error(`Error updating shape ${i}:`, err); console.error(`Error updating shape ${i}:`, err);
} }
} }
// Single sync after all property changes
await context.sync();
// Final status message based on what was applied // Final status message based on what was applied
if (successCount > 0) { if (successCount > 0) {