テキストファイルをスプレッドシートにアップロードする

## Agenda
* はじめに
* やりたいこと
* どう実現するか
* 実装
* できた
* さいごに

## はじめに
O84です。
Excel(スプレッドシート)方眼紙にソースコードを貼り付ける機会、稀によくありますよね？
この記事ではアドカレ1日目で扱った内容の逆で、ファイルのアップロード処理を実装します。

## やりたいこと
1. ダイアログからファイルを受け取る
2. ファイルを整形してスプレッドシートに反映する

## どう実現するか
クライアント側でのファイル受付は適当にやる[^1] [^2]
```google.script.run```を使ってファイルをサーバに渡す[^3]
受け取ったファイルを任意の形式でスプレッドシートに反映する

## 実装

### クライアント側

```html:upload.html
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <script type="text/javascript">
      async function uploadFiles() {
        const uploadFilesElement = document.getElementById("uploadFiles");
        const readFiles = [];
        for (let file of uploadFilesElement.files) {
          readFiles.push(
            readFile(file).then(function(file) {
              google.script.run
                .withSuccessHandler()
                .withFailureHandler()
                .file2Sheet(file);
            })
          );
        }
        await Promise.all(readFiles);
      }

      function readFile(file) {
        return new Promise((resolve) => {
          const reader = new FileReader();
          reader.onload = () => {
            resolve({
              name: file.name,
              data: reader.result
            });
          }
          reader.readAsText(file);
        });
      }
    </script>
  </head>
  <body>
    <div>
      <input type="file" id="uploadFiles" multiple>
    </div>
    <div>
      <button onclick="uploadFiles()">アップロードする</button>
    </div>
  </body>
</html>
```

### サーバ側

```js:main.js
function onOpen() {
  const ui = SpreadsheetApp.getUi();
  const menu = ui.createMenu('変換ツール');
  menu.addItem('ファイルアップロード', 'upload');
  menu.addToUi();
}

function upload() {
  const html = HtmlService.createTemplateFromFile('upload')
    .evaluate()
    .setWidth(510)
    .setHeight(400);
  SpreadsheetApp.getUi()
    .showModalDialog(html, 'ファイルをDrag&Dropしてください');
}

function file2Sheet(file) {
  const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  
  // 同じ名前のシートが無かったら新しく作る
  let targetSheet = spreadsheet.getSheetByName(file.name);
  if (targetSheet == null) {
    targetSheet = spreadsheet.insertSheet();
    targetSheet.setName(file.name);
  } else {
    targetSheet.clearContents();
  }

  // なんやかんやしてシートに反映する
  file.data.split("\n").map((row, index) => {
    const cols = row.split("  ");
    targetSheet.getRange(index + 1, 1, 1, cols.length)
      .setValues([cols]);
  });
}
```

## できた

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3527495/b6e0e767-a2a8-6492-d549-8b99b42b5c66.png)

google.script.runを並列で実行したらどうなるんだろ～?と思って試したら普通に動いたのでそのままにしている

一度に大量のファイルを送りつけると"シート[0-9]+_conflict[0-9]+"って名前のシートが生えてくるが、勝手に解決してくれるのでヨシ！

約20KBのファイルを30件同時に投げても問題なく動作したことを確認済み

## さいごに

**一緒に働く仲間を募集中です！**

リンクラフト株式会社では、組織拡大に伴い積極的な採用活動を行っています。
少しでも興味がある方はぜひご連絡ください。

▽会社ホームページ
https://lincraft.co.jp/

▽SNS
[X (旧Twitter)](https://twitter.com/lincraft_inc)
[Instagram](https://www.instagram.com/lincraft.inc/)
[Facebook](https://www.facebook.com/lincraft.inc/)

▽ご応募はこちらより
https://lincraft.co.jp/recruit

※カジュアル面談も受付中です。ご希望の方はHPのお問い合わせフォームよりご連絡ください。

[^1]: [input type="file" (Mozilla)](https://developer.mozilla.org/ja/docs/Web/HTML/Element/input/file)

[^2]: [jsでファイルの情報を参照する (Mozilla)](https://developer.mozilla.org/ja/docs/Web/API/File_API/Using_files_from_web_applications)

[^3]: [google.script.run (Google Workspace)](https://developers.google.com/apps-script/guides/html/reference/run?hl=ja)