Improve TypeScript types and error handling
- Created proper TypeScript types for Office API operations - Replaced 'any' casts with appropriate types - Added type-safe utility functions in office-types.ts - Improved error handling with proper unknown type - Added helper functions for PowerPoint shape operations - Created proper TypeScript interface for webpack HMR These changes improve code quality, maintainability, and help catch potential bugs at compile time rather than runtime. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ import * as React from "react";
|
|||||||
import { Button, ButtonProps } from "@fluentui/react-components";
|
import { Button, ButtonProps } from "@fluentui/react-components";
|
||||||
import { useStatusContext } from "./App";
|
import { useStatusContext } from "./App";
|
||||||
import { useCommonStyles } from "./commonStyles";
|
import { useCommonStyles } from "./commonStyles";
|
||||||
|
import { getErrorMessage } from "../types/office-types";
|
||||||
|
|
||||||
export interface ActionButtonProps {
|
export interface ActionButtonProps {
|
||||||
icon: ButtonProps["icon"];
|
icon: ButtonProps["icon"];
|
||||||
@@ -37,8 +38,8 @@ export const ActionButton: React.FC<ActionButtonProps> = ({
|
|||||||
setIsProcessing(true);
|
setIsProcessing(true);
|
||||||
try {
|
try {
|
||||||
await onClick();
|
await onClick();
|
||||||
} catch (error) {
|
} catch (error: unknown) {
|
||||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
const errorMessage = getErrorMessage(error);
|
||||||
setStatusMessage(`Error: ${errorMessage}`);
|
setStatusMessage(`Error: ${errorMessage}`);
|
||||||
setStatusType("error");
|
setStatusType("error");
|
||||||
console.error(`Error in ${title} action:`, error);
|
console.error(`Error in ${title} action:`, error);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
} from "@fluentui/react-icons";
|
} from "@fluentui/react-icons";
|
||||||
import { useStatusContext } from "./App";
|
import { useStatusContext } from "./App";
|
||||||
import { useCommonStyles } from "./commonStyles";
|
import { useCommonStyles } from "./commonStyles";
|
||||||
|
import { getErrorMessage, isPictureShape, getFirstSelectedSlide, selectShapesById } from "../types/office-types";
|
||||||
|
|
||||||
export const RoundImage: React.FC = () => {
|
export const RoundImage: React.FC = () => {
|
||||||
const styles = useCommonStyles();
|
const styles = useCommonStyles();
|
||||||
@@ -38,8 +39,8 @@ export const RoundImage: React.FC = () => {
|
|||||||
shape.load(["type"]);
|
shape.load(["type"]);
|
||||||
await context.sync();
|
await context.sync();
|
||||||
|
|
||||||
// Ensure the shape is a picture
|
// Ensure the shape is a picture using our type-safe utility
|
||||||
if (shape.type !== PowerPoint.ShapeType.image) {
|
if (!isPictureShape(shape)) {
|
||||||
setStatusMessage("Please select an image.");
|
setStatusMessage("Please select an image.");
|
||||||
setStatusType("warning");
|
setStatusType("warning");
|
||||||
return;
|
return;
|
||||||
@@ -53,8 +54,10 @@ export const RoundImage: React.FC = () => {
|
|||||||
const width = shape.width;
|
const width = shape.width;
|
||||||
const height = shape.height;
|
const height = shape.height;
|
||||||
|
|
||||||
const slide = context.presentation.getSelectedSlides().getItemAt(0);
|
// Get the current slide using our type-safe utility
|
||||||
const maskShape = slide.shapes.addGeometricShape("Ellipse" as any);
|
const slide = getFirstSelectedSlide(context);
|
||||||
|
// Create elliptical mask with proper type
|
||||||
|
const maskShape = slide.shapes.addGeometricShape(PowerPoint.GeometricShapeType.ellipse);
|
||||||
|
|
||||||
maskShape.load(["width", "height", "left", "top", "id"]);
|
maskShape.load(["width", "height", "left", "top", "id"]);
|
||||||
await context.sync();
|
await context.sync();
|
||||||
@@ -73,17 +76,19 @@ export const RoundImage: React.FC = () => {
|
|||||||
setStatusMessage("Created mask shape. Please select both the image and the oval, then use the 'Shape Forma > Merge Shapes > Intersect' command in PowerPoint.");
|
setStatusMessage("Created mask shape. Please select both the image and the oval, then use the 'Shape Forma > Merge Shapes > Intersect' command in PowerPoint.");
|
||||||
setStatusType("warning");
|
setStatusType("warning");
|
||||||
|
|
||||||
slide.setSelectedShapes([shape.id, maskShape.id]);
|
// Use our type-safe utility for selecting shapes
|
||||||
|
selectShapesById(slide, [shape.id, maskShape.id]);
|
||||||
// Ensure we maintain the same size
|
// Ensure we maintain the same size
|
||||||
shape.width = width;
|
shape.width = width;
|
||||||
shape.height = height;
|
shape.height = height;
|
||||||
|
|
||||||
await context.sync();
|
await context.sync();
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error: unknown) {
|
||||||
setStatusMessage(`Error: ${error.message}`);
|
const errorMessage = getErrorMessage(error);
|
||||||
|
setStatusMessage(`Error: ${errorMessage}`);
|
||||||
setStatusType("error");
|
setStatusType("error");
|
||||||
console.error("Main error:", error);
|
console.error("Round image error:", error);
|
||||||
} finally {
|
} finally {
|
||||||
setIsProcessing(false);
|
setIsProcessing(false);
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-3
@@ -19,9 +19,21 @@ Office.onReady(() => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
if ((module as any).hot) {
|
// Define proper module hot interface for webpack hot module replacement
|
||||||
(module as any).hot.accept("./components/App", () => {
|
interface HotModule extends NodeModule {
|
||||||
|
hot?: {
|
||||||
|
accept(path: string, callback: () => void): void;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the proper type for module with HMR
|
||||||
|
if ((module as HotModule).hot) {
|
||||||
|
(module as HotModule).hot?.accept("./components/App", () => {
|
||||||
const NextApp = require("./components/App").default;
|
const NextApp = require("./components/App").default;
|
||||||
root?.render(NextApp);
|
root?.render(
|
||||||
|
<FluentProvider theme={webLightTheme}>
|
||||||
|
<NextApp title={title} />
|
||||||
|
</FluentProvider>
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/**
|
||||||
|
* Type definitions for the Office JS API
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extended Error interface for Office/PowerPoint API errors
|
||||||
|
*/
|
||||||
|
export interface OfficeApiError extends Error {
|
||||||
|
code?: string;
|
||||||
|
debugInfo?: {
|
||||||
|
code?: string;
|
||||||
|
message?: string;
|
||||||
|
errorLocation?: string;
|
||||||
|
statement?: string;
|
||||||
|
innerError?: any;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to safely extract error message from any error object
|
||||||
|
*/
|
||||||
|
export function getErrorMessage(error: unknown): string {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
return error.message;
|
||||||
|
}
|
||||||
|
return String(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type-safe wrapper for PowerPoint API operations on shapes
|
||||||
|
*/
|
||||||
|
export function isPictureShape(shape: PowerPoint.Shape): boolean {
|
||||||
|
return shape.type === PowerPoint.ShapeType.image;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type-safe wrapper for working with PowerPoint slides
|
||||||
|
*/
|
||||||
|
export function getFirstSelectedSlide(context: PowerPoint.RequestContext): PowerPoint.Slide {
|
||||||
|
return context.presentation.getSelectedSlides().getItemAt(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type-safe utility for selecting shapes
|
||||||
|
*/
|
||||||
|
export function selectShapesById(slide: PowerPoint.Slide, shapeIds: string[]): void {
|
||||||
|
slide.setSelectedShapes(shapeIds);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user