slides.ts•7.29 kB
import { GoogleSlidesAuth } from './auth.js';
export interface SlideInfo {
slideId: string;
title?: string;
}
export interface RectangleOptions {
slideId: string;
x?: number;
y?: number;
width?: number;
height?: number;
}
export class GoogleSlidesService {
private auth: GoogleSlidesAuth;
constructor(auth: GoogleSlidesAuth) {
this.auth = auth;
}
/**
* Create a new slide in the presentation
*/
async createSlide(presentationId: string, insertionIndex?: number): Promise<SlideInfo> {
await this.auth.refreshTokenIfNeeded();
const slides = this.auth.getSlidesClient();
const requests = [{
createSlide: {
objectId: `slide_${Date.now()}`,
insertionIndex: insertionIndex || 0,
slideLayoutReference: {
predefinedLayout: 'BLANK'
}
}
}];
try {
const response = await slides.presentations.batchUpdate({
presentationId,
requestBody: {
requests
}
});
const createdSlide = response.data.replies[0].createSlide;
return {
slideId: createdSlide.objectId,
title: 'New Slide'
};
} catch (error) {
console.error('Error creating slide:', error);
throw new Error(`Failed to create slide: ${error}`);
}
}
/**
* Add a rectangle to a slide with 40% of slide dimensions
*/
async addRectangle(presentationId: string, options: RectangleOptions): Promise<string> {
await this.auth.refreshTokenIfNeeded();
const slides = this.auth.getSlidesClient();
// Get presentation details to calculate slide dimensions
const presentation = await slides.presentations.get({
presentationId
});
// Verify the slide exists
const slideExists = presentation.data.slides?.find((slide: any) => slide.objectId === options.slideId);
if (!slideExists) {
console.error(`Available slides:`, presentation.data.slides?.map((s: any) => s.objectId));
throw new Error(`Slide with ID ${options.slideId} not found in presentation`);
}
console.error(`Confirmed slide ${options.slideId} exists in presentation`);
const pageSize = presentation.data.pageSize;
const slideWidth = pageSize.width.magnitude;
const slideHeight = pageSize.height.magnitude;
const slideUnit = pageSize.width.unit; // Usually 'EMU'
console.error(`Page size unit: ${slideUnit}`);
// Convert from EMU to PT if needed (1 EMU = 1/914400 inches, 1 PT = 1/72 inches)
// So 1 PT = 12700 EMU
let slideWidthPt, slideHeightPt;
if (slideUnit === 'EMU') {
slideWidthPt = slideWidth / 12700;
slideHeightPt = slideHeight / 12700;
} else {
slideWidthPt = slideWidth;
slideHeightPt = slideHeight;
}
// Calculate rectangle dimensions (40% of slide size for better visibility)
const rectWidth = slideWidthPt * 0.4;
const rectHeight = slideHeightPt * 0.4;
// Position rectangle in center if no position specified
const x = options.x || (slideWidthPt - rectWidth) / 2;
const y = options.y || (slideHeightPt - rectHeight) / 2;
const rectangleId = `rectangle_${Date.now()}`;
console.error(`Creating RED rectangle with dimensions: ${rectWidth} x ${rectHeight} PT at position (${x}, ${y}) PT`);
console.error(`Original slide dimensions: ${slideWidth} x ${slideHeight} ${slideUnit}`);
console.error(`Converted slide dimensions: ${slideWidthPt} x ${slideHeightPt} PT`);
console.error(`Target slide ID: ${options.slideId}`);
console.error(`Rectangle ID: ${rectangleId}`);
const requests = [{
createShape: {
objectId: rectangleId,
shapeType: 'RECTANGLE',
elementProperties: {
pageObjectId: options.slideId,
size: {
width: {
magnitude: options.width || rectWidth,
unit: 'PT'
},
height: {
magnitude: options.height || rectHeight,
unit: 'PT'
}
},
transform: {
scaleX: 1,
scaleY: 1,
translateX: x,
translateY: y,
unit: 'PT'
}
}
}
}];
try {
console.error('Sending rectangle creation request:', JSON.stringify(requests, null, 2));
// First create the shape
await slides.presentations.batchUpdate({
presentationId,
requestBody: {
requests
}
});
// Then update its styling with bright red color
const styleRequests = [{
updateShapeProperties: {
objectId: rectangleId,
shapeProperties: {
shapeBackgroundFill: {
solidFill: {
color: {
rgbColor: {
red: 1.0,
green: 0.0,
blue: 0.0
}
}
}
},
outline: {
outlineFill: {
solidFill: {
color: {
rgbColor: {
red: 0.0,
green: 0.0,
blue: 0.0
}
}
}
},
weight: {
magnitude: 4,
unit: 'PT'
}
}
},
fields: 'shapeBackgroundFill,outline'
}
}];
console.error('Sending shape styling request:', JSON.stringify(styleRequests, null, 2));
const styleResponse = await slides.presentations.batchUpdate({
presentationId,
requestBody: {
requests: styleRequests
}
});
console.error('Shape styling response:', JSON.stringify(styleResponse.data, null, 2));
return rectangleId;
} catch (error) {
console.error('Error adding rectangle:', error);
console.error('Full error details:', JSON.stringify(error, null, 2));
throw new Error(`Failed to add rectangle: ${error}`);
}
}
/**
* Get presentation information
*/
async getPresentationInfo(presentationId: string) {
await this.auth.refreshTokenIfNeeded();
const slides = this.auth.getSlidesClient();
try {
const response = await slides.presentations.get({
presentationId
});
return {
title: response.data.title,
slideCount: response.data.slides?.length || 0,
pageSize: response.data.pageSize
};
} catch (error) {
console.error('Error getting presentation info:', error);
throw new Error(`Failed to get presentation info: ${error}`);
}
}
/**
* List all slides in a presentation
*/
async listSlides(presentationId: string): Promise<SlideInfo[]> {
await this.auth.refreshTokenIfNeeded();
const slides = this.auth.getSlidesClient();
try {
const response = await slides.presentations.get({
presentationId
});
return response.data.slides?.map((slide: any, index: number) => ({
slideId: slide.objectId,
title: `Slide ${index + 1}`
})) || [];
} catch (error) {
console.error('Error listing slides:', error);
throw new Error(`Failed to list slides: ${error}`);
}
}
}