Dizzyw3

Untitled

Sep 5th, 2025
308
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // googleSlidesService.ts
  2. import { GoogleApiConfig, PickedFile, SlideContent } from './types';
  3.  
  4. export class GoogleSlidesService {
  5.   private isInitialized = false;
  6.   private accessToken: string | null = null;
  7.  
  8.   private readonly SCOPES = [
  9.     'https://www.googleapis.com/auth/drive.file',
  10.     'https://www.googleapis.com/auth/presentations'
  11.   ];
  12.  
  13.   constructor(private config: GoogleApiConfig) {}
  14.  
  15.   /**
  16.    * Initialize the Google APIs and authenticate
  17.    */
  18.   public async initialize(): Promise<void> {
  19.     if (this.isInitialized) return;
  20.  
  21.     // Load Google APIs
  22.     await this.loadGoogleAPIs();
  23.    
  24.     // Initialize gapi client
  25.     await new Promise<void>((resolve, reject) => {
  26.       window.gapi.load('client:auth2:picker', {
  27.         callback: resolve,
  28.         onerror: reject
  29.       });
  30.     });
  31.  
  32.     // Initialize client
  33.     await window.gapi.client.init({
  34.       apiKey: this.config.apiKey,
  35.       clientId: this.config.clientId,
  36.       discoveryDocs: ['https://slides.googleapis.com/$discovery/rest?version=v1'],
  37.       scope: this.SCOPES.join(' ')
  38.     });
  39.  
  40.     this.isInitialized = true;
  41.   }
  42.  
  43.   /**
  44.    * Load Google APIs script
  45.    */
  46.   private async loadGoogleAPIs(): Promise<void> {
  47.     return new Promise((resolve, reject) => {
  48.       if (window.gapi) {
  49.         resolve();
  50.         return;
  51.       }
  52.  
  53.       const script = document.createElement('script');
  54.       script.src = 'https://apis.google.com/js/api.js';
  55.       script.onload = () => {
  56.         window.gapiLoaded = resolve;
  57.         window.gapiLoadError = reject;
  58.       };
  59.       script.onerror = reject;
  60.       document.head.appendChild(script);
  61.     });
  62.   }
  63.  
  64.   /**
  65.    * Authenticate and get access token
  66.    */
  67.   public async authenticate(): Promise<string> {
  68.     const authInstance = window.gapi.auth2.getAuthInstance();
  69.    
  70.     if (!authInstance.isSignedIn.get()) {
  71.       await authInstance.signIn();
  72.     }
  73.    
  74.     const user = authInstance.currentUser.get();
  75.     const authResponse = user.getAuthResponse();
  76.     this.accessToken = authResponse.access_token;
  77.    
  78.     return this.accessToken;
  79.   }
  80.  
  81.   /**
  82.    * Open Google Picker to select a file
  83.    */
  84.   public async pickFile(): Promise<PickedFile> {
  85.     if (!this.accessToken) {
  86.       await this.authenticate();
  87.     }
  88.  
  89.     return new Promise((resolve, reject) => {
  90.       const picker = new window.google.picker.PickerBuilder()
  91.         .enableFeature(window.google.picker.Feature.NAV_HIDDEN)
  92.         .setAppId(this.config.appId)
  93.         .setOAuthToken(this.accessToken!)
  94.         .setDeveloperKey(this.config.apiKey)
  95.         .addView(new window.google.picker.DocsView(window.google.picker.ViewId.PRESENTATIONS))
  96.         .setCallback((data: any) => {
  97.           if (data.action === window.google.picker.Action.PICKED) {
  98.             const file = data.docs[0];
  99.             resolve({
  100.               id: file.id,
  101.               name: file.name,
  102.               mimeType: file.mimeType,
  103.               url: file.url
  104.             });
  105.           } else if (data.action === window.google.picker.Action.CANCEL) {
  106.             reject(new Error('File picking cancelled'));
  107.           }
  108.         })
  109.         .build();
  110.  
  111.       picker.setVisible(true);
  112.     });
  113.   }
  114.  
  115.   /**
  116.    * Read all content from a Google Slides presentation
  117.    */
  118.   public async readPresentationContent(presentationId: string): Promise<SlideContent> {
  119.     try {
  120.       const response = await window.gapi.client.slides.presentations.get({
  121.         presentationId: presentationId
  122.       });
  123.  
  124.       const presentation = response.result;
  125.      
  126.       return {
  127.         presentationId: presentation.presentationId!,
  128.         title: presentation.title!,
  129.         slides: presentation.slides || []
  130.       };
  131.     } catch (error) {
  132.       console.error('Error reading presentation:', error);
  133.       throw new Error(`Failed to read presentation: ${error}`);
  134.     }
  135.   }
  136.  
  137.   /**
  138.    * Create a new presentation with the same content
  139.    */
  140.   public async createPresentationWithContent(
  141.     sourceContent: SlideContent,
  142.     newTitle: string
  143.   ): Promise<string> {
  144.     try {
  145.       // Step 1: Create a blank presentation
  146.       const createResponse = await window.gapi.client.slides.presentations.create({
  147.         title: newTitle
  148.       });
  149.  
  150.       const newPresentationId = createResponse.result.presentationId!;
  151.      
  152.       // Step 2: Remove the default slide (if exists)
  153.       const newPresentation = await window.gapi.client.slides.presentations.get({
  154.         presentationId: newPresentationId
  155.       });
  156.  
  157.       const requests: any[] = [];
  158.      
  159.       // Delete default slide if it exists
  160.       if (newPresentation.result.slides && newPresentation.result.slides.length > 0) {
  161.         requests.push({
  162.           deleteObject: {
  163.             objectId: newPresentation.result.slides[0].objectId
  164.           }
  165.         });
  166.       }
  167.  
  168.       // Step 3: Copy all slides from source
  169.       for (let i = 0; i < sourceContent.slides.length; i++) {
  170.         const sourceSlide = sourceContent.slides[i];
  171.         const newSlideId = `slide_${i}_${Date.now()}`;
  172.        
  173.         // Create slide with same layout
  174.         requests.push({
  175.           createSlide: {
  176.             objectId: newSlideId,
  177.             insertionIndex: i,
  178.             slideLayoutReference: sourceSlide.slideProperties?.layoutReference || {
  179.               predefinedLayout: 'BLANK'
  180.             }
  181.           }
  182.         });
  183.  
  184.         // Copy all page elements from source slide
  185.         if (sourceSlide.pageElements) {
  186.           sourceSlide.pageElements.forEach((element: any, elementIndex: number) => {
  187.             const newElementId = `${newSlideId}_element_${elementIndex}`;
  188.            
  189.             // Create element based on type
  190.             if (element.shape) {
  191.               requests.push({
  192.                 createShape: {
  193.                   objectId: newElementId,
  194.                   shapeType: element.shape.shapeType,
  195.                   elementProperties: {
  196.                     pageObjectId: newSlideId,
  197.                     size: element.size,
  198.                     transform: element.transform
  199.                   }
  200.                 }
  201.               });
  202.  
  203.               // Add text if shape contains text
  204.               if (element.shape.text && element.shape.text.textElements) {
  205.                 const textContent = element.shape.text.textElements
  206.                   .filter((te: any) => te.textRun)
  207.                   .map((te: any) => te.textRun.content)
  208.                   .join('');
  209.                
  210.                 if (textContent.trim()) {
  211.                   requests.push({
  212.                     insertText: {
  213.                       objectId: newElementId,
  214.                       text: textContent,
  215.                       insertionIndex: 0
  216.                     }
  217.                   });
  218.                 }
  219.               }
  220.             } else if (element.image) {
  221.               requests.push({
  222.                 createImage: {
  223.                   objectId: newElementId,
  224.                   url: element.image.contentUrl,
  225.                   elementProperties: {
  226.                     pageObjectId: newSlideId,
  227.                     size: element.size,
  228.                     transform: element.transform
  229.                   }
  230.                 }
  231.               });
  232.             }
  233.           });
  234.         }
  235.       }
  236.  
  237.       // Execute all requests in batches (max 500 requests per batch)
  238.       const batchSize = 500;
  239.       for (let i = 0; i < requests.length; i += batchSize) {
  240.         const batch = requests.slice(i, i + batchSize);
  241.         await window.gapi.client.slides.presentations.batchUpdate({
  242.           presentationId: newPresentationId,
  243.           requests: batch
  244.         });
  245.       }
  246.  
  247.       return newPresentationId;
  248.     } catch (error) {
  249.       console.error('Error creating presentation:', error);
  250.       throw new Error(`Failed to create presentation: ${error}`);
  251.     }
  252.   }
  253.  
  254.   /**
  255.    * Complete workflow: pick file, read content, create copy
  256.    */
  257.   public async clonePresentation(newTitle?: string): Promise<string> {
  258.     try {
  259.       // Step 1: Initialize if needed
  260.       await this.initialize();
  261.      
  262.       // Step 2: Pick a file
  263.       console.log('Opening file picker...');
  264.       const pickedFile = await this.pickFile();
  265.       console.log('File picked:', pickedFile.name);
  266.  
  267.       // Step 3: Read the content
  268.       console.log('Reading presentation content...');
  269.       const content = await this.readPresentationContent(pickedFile.id);
  270.       console.log(`Read ${content.slides.length} slides from "${content.title}"`);
  271.  
  272.       // Step 4: Create new presentation
  273.       const finalTitle = newTitle || `Copy of ${content.title}`;
  274.       console.log(`Creating new presentation: "${finalTitle}"`);
  275.       const newPresentationId = await this.createPresentationWithContent(content, finalTitle);
  276.      
  277.       console.log(`Successfully created presentation with ID: ${newPresentationId}`);
  278.       return newPresentationId;
  279.     } catch (error) {
  280.       console.error('Error in clone workflow:', error);
  281.       throw error;
  282.     }
  283.   }
  284. }
  285.  
Advertisement
Add Comment
Please, Sign In to add comment