Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // googleSlidesService.ts
- import { GoogleApiConfig, PickedFile, SlideContent } from './types';
- export class GoogleSlidesService {
- private isInitialized = false;
- private accessToken: string | null = null;
- private readonly SCOPES = [
- 'https://www.googleapis.com/auth/drive.file',
- 'https://www.googleapis.com/auth/presentations'
- ];
- constructor(private config: GoogleApiConfig) {}
- /**
- * Initialize the Google APIs and authenticate
- */
- public async initialize(): Promise<void> {
- if (this.isInitialized) return;
- // Load Google APIs
- await this.loadGoogleAPIs();
- // Initialize gapi client
- await new Promise<void>((resolve, reject) => {
- window.gapi.load('client:auth2:picker', {
- callback: resolve,
- onerror: reject
- });
- });
- // Initialize client
- await window.gapi.client.init({
- apiKey: this.config.apiKey,
- clientId: this.config.clientId,
- discoveryDocs: ['https://slides.googleapis.com/$discovery/rest?version=v1'],
- scope: this.SCOPES.join(' ')
- });
- this.isInitialized = true;
- }
- /**
- * Load Google APIs script
- */
- private async loadGoogleAPIs(): Promise<void> {
- return new Promise((resolve, reject) => {
- if (window.gapi) {
- resolve();
- return;
- }
- const script = document.createElement('script');
- script.src = 'https://apis.google.com/js/api.js';
- script.onload = () => {
- window.gapiLoaded = resolve;
- window.gapiLoadError = reject;
- };
- script.onerror = reject;
- document.head.appendChild(script);
- });
- }
- /**
- * Authenticate and get access token
- */
- public async authenticate(): Promise<string> {
- const authInstance = window.gapi.auth2.getAuthInstance();
- if (!authInstance.isSignedIn.get()) {
- await authInstance.signIn();
- }
- const user = authInstance.currentUser.get();
- const authResponse = user.getAuthResponse();
- this.accessToken = authResponse.access_token;
- return this.accessToken;
- }
- /**
- * Open Google Picker to select a file
- */
- public async pickFile(): Promise<PickedFile> {
- if (!this.accessToken) {
- await this.authenticate();
- }
- return new Promise((resolve, reject) => {
- const picker = new window.google.picker.PickerBuilder()
- .enableFeature(window.google.picker.Feature.NAV_HIDDEN)
- .setAppId(this.config.appId)
- .setOAuthToken(this.accessToken!)
- .setDeveloperKey(this.config.apiKey)
- .addView(new window.google.picker.DocsView(window.google.picker.ViewId.PRESENTATIONS))
- .setCallback((data: any) => {
- if (data.action === window.google.picker.Action.PICKED) {
- const file = data.docs[0];
- resolve({
- id: file.id,
- name: file.name,
- mimeType: file.mimeType,
- url: file.url
- });
- } else if (data.action === window.google.picker.Action.CANCEL) {
- reject(new Error('File picking cancelled'));
- }
- })
- .build();
- picker.setVisible(true);
- });
- }
- /**
- * Read all content from a Google Slides presentation
- */
- public async readPresentationContent(presentationId: string): Promise<SlideContent> {
- try {
- const response = await window.gapi.client.slides.presentations.get({
- presentationId: presentationId
- });
- const presentation = response.result;
- return {
- presentationId: presentation.presentationId!,
- title: presentation.title!,
- slides: presentation.slides || []
- };
- } catch (error) {
- console.error('Error reading presentation:', error);
- throw new Error(`Failed to read presentation: ${error}`);
- }
- }
- /**
- * Create a new presentation with the same content
- */
- public async createPresentationWithContent(
- sourceContent: SlideContent,
- newTitle: string
- ): Promise<string> {
- try {
- // Step 1: Create a blank presentation
- const createResponse = await window.gapi.client.slides.presentations.create({
- title: newTitle
- });
- const newPresentationId = createResponse.result.presentationId!;
- // Step 2: Remove the default slide (if exists)
- const newPresentation = await window.gapi.client.slides.presentations.get({
- presentationId: newPresentationId
- });
- const requests: any[] = [];
- // Delete default slide if it exists
- if (newPresentation.result.slides && newPresentation.result.slides.length > 0) {
- requests.push({
- deleteObject: {
- objectId: newPresentation.result.slides[0].objectId
- }
- });
- }
- // Step 3: Copy all slides from source
- for (let i = 0; i < sourceContent.slides.length; i++) {
- const sourceSlide = sourceContent.slides[i];
- const newSlideId = `slide_${i}_${Date.now()}`;
- // Create slide with same layout
- requests.push({
- createSlide: {
- objectId: newSlideId,
- insertionIndex: i,
- slideLayoutReference: sourceSlide.slideProperties?.layoutReference || {
- predefinedLayout: 'BLANK'
- }
- }
- });
- // Copy all page elements from source slide
- if (sourceSlide.pageElements) {
- sourceSlide.pageElements.forEach((element: any, elementIndex: number) => {
- const newElementId = `${newSlideId}_element_${elementIndex}`;
- // Create element based on type
- if (element.shape) {
- requests.push({
- createShape: {
- objectId: newElementId,
- shapeType: element.shape.shapeType,
- elementProperties: {
- pageObjectId: newSlideId,
- size: element.size,
- transform: element.transform
- }
- }
- });
- // Add text if shape contains text
- if (element.shape.text && element.shape.text.textElements) {
- const textContent = element.shape.text.textElements
- .filter((te: any) => te.textRun)
- .map((te: any) => te.textRun.content)
- .join('');
- if (textContent.trim()) {
- requests.push({
- insertText: {
- objectId: newElementId,
- text: textContent,
- insertionIndex: 0
- }
- });
- }
- }
- } else if (element.image) {
- requests.push({
- createImage: {
- objectId: newElementId,
- url: element.image.contentUrl,
- elementProperties: {
- pageObjectId: newSlideId,
- size: element.size,
- transform: element.transform
- }
- }
- });
- }
- });
- }
- }
- // Execute all requests in batches (max 500 requests per batch)
- const batchSize = 500;
- for (let i = 0; i < requests.length; i += batchSize) {
- const batch = requests.slice(i, i + batchSize);
- await window.gapi.client.slides.presentations.batchUpdate({
- presentationId: newPresentationId,
- requests: batch
- });
- }
- return newPresentationId;
- } catch (error) {
- console.error('Error creating presentation:', error);
- throw new Error(`Failed to create presentation: ${error}`);
- }
- }
- /**
- * Complete workflow: pick file, read content, create copy
- */
- public async clonePresentation(newTitle?: string): Promise<string> {
- try {
- // Step 1: Initialize if needed
- await this.initialize();
- // Step 2: Pick a file
- console.log('Opening file picker...');
- const pickedFile = await this.pickFile();
- console.log('File picked:', pickedFile.name);
- // Step 3: Read the content
- console.log('Reading presentation content...');
- const content = await this.readPresentationContent(pickedFile.id);
- console.log(`Read ${content.slides.length} slides from "${content.title}"`);
- // Step 4: Create new presentation
- const finalTitle = newTitle || `Copy of ${content.title}`;
- console.log(`Creating new presentation: "${finalTitle}"`);
- const newPresentationId = await this.createPresentationWithContent(content, finalTitle);
- console.log(`Successfully created presentation with ID: ${newPresentationId}`);
- return newPresentationId;
- } catch (error) {
- console.error('Error in clone workflow:', error);
- throw error;
- }
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment