GAS ファイルのアップロード

# はじめに

GASでPDFなどのファイルのアップロードに対応する
以下のようなアプリを作成します。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3343142/c45e625f-704e-d432-1b54-f3ca48bc0cdd.png)

ファイルを選択して、処理開始を押すとアップロードが開始されます。
問題なければ以下の様に表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3343142/55c069ee-3e64-70b9-c2a9-cc630d25a86d.png)

# コード

```JavaScript:Code.gs
function processFiles(fileObjects) {
  var results = [];
  // ファイル格納先のフォルダのIDを指定
  var folderId = "ここにフォルダのIDを入れます";
  var folder = DriveApp.getFolderById(folderId);

  for (var i = 0; i < fileObjects.length; i++) {
    var file = fileObjects[i];
    var blob = Utilities.newBlob(Utilities.base64Decode(file.data), file.mimeType, file.name);

    var uploadedFile = folder.createFile(blob);
    var fileId = uploadedFile.getId();

    // 処理完了後の表示に使う（要修正）
    results.push({ name: file.name, text: file.name, url: file.name });
  }
  return results;
}
```

<details><summary>index.html</summary>

~~~HTML:index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <base target="_top">
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Web App</title>
  <script src="https://cdn.tailwindcss.com"></script>
  <script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
</head>
<body class="bg-gray-100 dark:bg-gray-900 text-gray-900 dark:text-gray-100">
  <div id="app" class="container mx-auto px-4 py-8">
    <h1 class="text-3xl font-bold mb-8 text-center">Advanced OCR Web App</h1>
    
    <div class="mb-8">
      <label for="fileUpload" class="block text-lg font-medium mb-2">ファイルをアップロード (PDF, PNG, JPG, JPEG)</label>
      <input type="file" id="fileUpload" ref="fileUpload" @change="handleFileUpload" multiple accept=".pdf,.png,.jpg,.jpeg" class="block w-full text-sm text-gray-500
        file:mr-4 file:py-2 file:px-4
        file:rounded-full file:border-0
        file:text-sm file:font-semibold
        file:bg-blue-50 file:text-blue-700
        hover:file:bg-blue-100
      "/>
    </div>
    
    <button @click="processFiles" :disabled="!files.length || processing" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded disabled:opacity-50 disabled:cursor-not-allowed">
      処理開始
    </button>
    
    <div v-if="processing" class="mt-4">
      <div class="w-full bg-gray-200 rounded-full h-2.5 dark:bg-gray-700">
        <div class="bg-blue-600 h-2.5 rounded-full" :style="{ width: `${progress}%` }"></div>
      </div>
      <p class="text-center mt-2">処理中... {{ progress }}%</p>
    </div>
    
    <div v-if="results.length" class="mt-8">
      <h2 class="text-2xl font-bold mb-4">処理結果</h2>
      <div v-for="(result, index) in results" :key="index" class="mb-4 p-4 bg-white dark:bg-gray-800 rounded shadow">
        <h3 class="text-xl font-semibold mb-2">{{ result.name }}</h3>
        <pre class="whitespace-pre-wrap">{{ result.text }}</pre>
      </div>
    </div>
    
    <div v-if="error" class="mt-4 p-4 bg-red-100 border border-red-400 text-red-700 rounded">
      {{ error }}
    </div>
  </div>

  <script>
    const { createApp, ref, computed } = Vue;
    
    createApp({
      setup() {
        const files = ref([]);
        const results = ref([]);
        const processing = ref(false);
        const progress = ref(0);
        const error = ref('');
        
        const handleFileUpload = (event) => {
          files.value = Array.from(event.target.files);
        };
        
        const processFiles = async () => {
          if (!files.value.length) return;
          
          processing.value = true;
          progress.value = 0;
          results.value = [];
          error.value = '';
          
          const fileObjects = await Promise.all(files.value.map(fileToObject));
          
          try {
            const processedResults = await google.script.run
              .withSuccessHandler((res) => {
                results.value = res;
                processing.value = false;
                progress.value = 100;
              })
              .withFailureHandler((err) => {
                error.value = `エラーが発生しました: ${err.message || err}`;
                processing.value = false;
              })
              .processFiles(fileObjects);
          } catch (err) {
            error.value = `エラーが発生しました: ${err.message || err}`;
            processing.value = false;
          }
        };
        
        const fileToObject = (file) => {
          return new Promise((resolve, reject) => {
            const reader = new FileReader();
            reader.onload = (e) => {
              resolve({
                name: file.name,
                mimeType: file.type,
                data: e.target.result.split(',')[1]
              });
              progress.value += (100 / files.value.length) / 2;
            };
            reader.onerror = (e) => reject(e);
            reader.readAsDataURL(file);
          });
        };
        
        return {
          files,
          results,
          processing,
          progress,
          error,
          handleFileUpload,
          processFiles
        };
      }
    }).mount('#app');
  </script>
</body>
</html>
~~~

</details>