From 50966867b236fcb4fe49e8672f3c84137621b9b9 Mon Sep 17 00:00:00 2001 From: Heiko Joerg Schick Date: Sat, 15 Mar 2025 00:02:35 +0100 Subject: [PATCH] Improve TypeScript types and error handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- src/taskpane/components/ActionButton.tsx | 5 ++- src/taskpane/components/RoundImage.tsx | 21 +++++++---- src/taskpane/index.tsx | 18 +++++++-- src/taskpane/types/office-types.ts | 48 ++++++++++++++++++++++++ 4 files changed, 79 insertions(+), 13 deletions(-) create mode 100644 src/taskpane/types/office-types.ts diff --git a/src/taskpane/components/ActionButton.tsx b/src/taskpane/components/ActionButton.tsx index 1f1b98ab..df24e494 100644 --- a/src/taskpane/components/ActionButton.tsx +++ b/src/taskpane/components/ActionButton.tsx @@ -2,6 +2,7 @@ import * as React from "react"; import { Button, ButtonProps } from "@fluentui/react-components"; import { useStatusContext } from "./App"; import { useCommonStyles } from "./commonStyles"; +import { getErrorMessage } from "../types/office-types"; export interface ActionButtonProps { icon: ButtonProps["icon"]; @@ -37,8 +38,8 @@ export const ActionButton: React.FC = ({ setIsProcessing(true); try { await onClick(); - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); + } catch (error: unknown) { + const errorMessage = getErrorMessage(error); setStatusMessage(`Error: ${errorMessage}`); setStatusType("error"); console.error(`Error in ${title} action:`, error); diff --git a/src/taskpane/components/RoundImage.tsx b/src/taskpane/components/RoundImage.tsx index 0ec57d99..74b62323 100644 --- a/src/taskpane/components/RoundImage.tsx +++ b/src/taskpane/components/RoundImage.tsx @@ -7,6 +7,7 @@ import { } from "@fluentui/react-icons"; import { useStatusContext } from "./App"; import { useCommonStyles } from "./commonStyles"; +import { getErrorMessage, isPictureShape, getFirstSelectedSlide, selectShapesById } from "../types/office-types"; export const RoundImage: React.FC = () => { const styles = useCommonStyles(); @@ -38,8 +39,8 @@ export const RoundImage: React.FC = () => { shape.load(["type"]); await context.sync(); - // Ensure the shape is a picture - if (shape.type !== PowerPoint.ShapeType.image) { + // Ensure the shape is a picture using our type-safe utility + if (!isPictureShape(shape)) { setStatusMessage("Please select an image."); setStatusType("warning"); return; @@ -53,8 +54,10 @@ export const RoundImage: React.FC = () => { const width = shape.width; const height = shape.height; - const slide = context.presentation.getSelectedSlides().getItemAt(0); - const maskShape = slide.shapes.addGeometricShape("Ellipse" as any); + // Get the current slide using our type-safe utility + 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"]); 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."); 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 shape.width = width; shape.height = height; await context.sync(); }); - } catch (error) { - setStatusMessage(`Error: ${error.message}`); + } catch (error: unknown) { + const errorMessage = getErrorMessage(error); + setStatusMessage(`Error: ${errorMessage}`); setStatusType("error"); - console.error("Main error:", error); + console.error("Round image error:", error); } finally { setIsProcessing(false); } diff --git a/src/taskpane/index.tsx b/src/taskpane/index.tsx index c2108299..191e2e1b 100644 --- a/src/taskpane/index.tsx +++ b/src/taskpane/index.tsx @@ -19,9 +19,21 @@ Office.onReady(() => { ); }); -if ((module as any).hot) { - (module as any).hot.accept("./components/App", () => { +// Define proper module hot interface for webpack hot module replacement +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; - root?.render(NextApp); + root?.render( + + + + ); }); } diff --git a/src/taskpane/types/office-types.ts b/src/taskpane/types/office-types.ts new file mode 100644 index 00000000..4e117ee2 --- /dev/null +++ b/src/taskpane/types/office-types.ts @@ -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); +} \ No newline at end of file