Google Slides をストレージとして使用した簡易フォトギャラリー

# 概要

この投稿では Google Slides をストレージとして使用した簡易フォトギャラリーを紹介させていただきます。フォトギャラリーと言えば、[Google Photos](https://www.google.com/photos/about/)があります。これとは別で一時的に、 Google にログインしていない不特定ユーザが画像を追加できる簡易フォトギャラリーを用意する必要があり、試行錯誤の結果、ストレージとして Google Slides を使用し、フォトギャラリーと画像ファイルのアップロードのために Web Apps を使用することで、一つの Google Slides ファイルのみで実現可能な簡易フォトギャラリーを実現できました。最初に想定していた以上に便利でしたので、他のユーザの方々へもお役に立つことができればと公開させていただきました。

GitHub のリポジトリは[こちら](https://github.com/tanaikech/SimplePhotoGalleryUsingGoogleAppsScript)です。

# デモ

![fig1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133383/538abde8-b14c-e15e-3275-946f466895f0.gif)

デモで使用している画像は[http://k3-studio.deviantart.com/](http://k3-studio.deviantart.com/)から使用させていただきました。

# 原理

この簡易フォトギャラリーの原理は次の通りです。

- ブラウザを使ってデプロイした Web Apps へアクセスすると、表示された画像は[`lightgallery.js`](https://github.com/sachinchoolur/lightgallery.js)によりフォトギャラリーとして閲覧できます。
- また、Web Apps 上から画像ファイルをアップロードすることができます。
- アップロードされた画像は Google Slides の各スライドへ画像として保存されます。
- Google Slides 上の画像を読み込む際は、[`getContentUrl`](https://developers.google.com/apps-script/reference/slides/image#getcontenturl)を使って画像の URL を取得します。
  - この時、取得される画像の URL はテンポラルですが、フォトギャラリーを開く際に新たに URL が作成されますので、閲覧時は URL はアクティブな状態になります。
- この場合、Google Slides を削除すると、アップロードした画像データも全て消えてしまいますので注意が必要です。

# 使い方

## 1. Google Slides を作成

Google Drive へログインし、新規で Google Slides を作成してください。

## 2. スクリプトの準備

Google Slides と Web Apps のインターフェースとなるスクリプトを Google Apps Script と HTML で用意します。作成した Google Slides 上でスクリプトエディタを開いてください。

### Google Apps Script

下記スクリプトを開いたスクリプトエディタの`コード.gs`へコピーペーストしてください。

```javascript
const doGet = (_) => HtmlService.createHtmlOutputFromFile("index");

function getImages() {
  return SlidesApp.getActivePresentation()
    .getSlides()
    .map((e) =>
      e.getImages().length > 0 ? e.getImages()[0].getContentUrl() : ""
    )
    .filter(String);
}

function appendImages(images) {
  const s = SlidesApp.getActivePresentation();
  images.forEach((e) => {
    const image = s
      .appendSlide()
      .insertImage(
        Utilities.newBlob(
          Utilities.base64Decode(e.data),
          e.mimeType,
          e.fileName
        )
      );
    image.setTitle(e.fileName);
    image.setDescription(new Date().toISOString());
  });
  s.saveAndClose();
  return getImages();
}
```

### HTML&Javascript

スクリプトエディタ内で HTML ファイルを`index.html`のファイル名で作成し、そこへ下記スクリプトをコピーペーストしてください。コピーペースト後、保存ボタンを押してスクリプトを保存してください。

```html
<link type="text/css" rel="stylesheet" href="https://cdn.jsdelivr.net/npm/lightgallery.js@1.2.0/src/css/lightgallery.css" />
<script src="https://cdn.jsdelivr.net/npm/lightgallery.js@1.2.0/dist/js/lightgallery.min.js"></script>
<div>
<label id="label" for="file" style="color: white; background-color: deepskyblue; border-radius: 3px;">
Upload images
<input type="file" id="file" multiple="true" style="display:none;" accept="image/png,image/jpeg">
</div>
<div id="status"></div>
<div id="lightgallery"></div>

<script>
function showImages(urls) {
  if (urls.length == 0) return;
  const lightgallery = document.getElementById("lightgallery");
  lightgallery.innerHTML = "";
  urls.forEach(url => {
    const a = document.createElement("a");
    a.setAttribute("href", url);
    const img = document.createElement("img");
    img.setAttribute("src", url);
    img.setAttribute("width", 200);
    a.appendChild(img);
    lightgallery.appendChild(a);
  });
  lightGallery(document.getElementById('lightgallery'), {download: false});
}

const f = document.getElementById('file');
f.addEventListener("change", () => {
  if (f.files.length > 0) {
    const status = document.getElementById("status");
    status.innerHTML = "Now uploading...";
    Promise.all([...f.files].map((file, i) => {
      const fr = new FileReader();
      return new Promise(r => {
        fr.onload = e => {
          const data = e.target.result.split(",");
          r({fileName: file.name, mimeType: data[0].match(/:(\w.+);/)[1], data: data[1]});
        }
        fr.readAsDataURL(file);
      });
    }))
    .then(images => {
      google.script.run
      .withFailureHandler(err => {
        console.log(err);
        status.innerHTML = "";
      })
      .withSuccessHandler(() => {
        main();
        status.innerHTML = "";
      }).appendImages(images);
    });
  }
});

function main() {
  google.script.run.withFailureHandler(err => console.log(err)).withSuccessHandler(showImages).getImages();
}

main();
</script>
```

## 3. Web Apps のデプロイ

新しいスクリプトエディタを使用していると想定します。次のフローを実行してください。公式ドキュメントは[こちら](https://developers.google.com/apps-script/guides/web#deploy_a_script_as_a_web_app)です。

1. スクリプトエディタの右上にある"デプロイ"から"新しいデプロイ"を選択肢て開く。
2. "種類の選択"から"ウェブアプリ"を選択。
3. "説明"に簡単な説明を記入し、"次のユーザとして実行"、"アクセスできるユーザ"をそれぞれ、"自分", "全員"として選択し、右下の"デプロイ"ボタンを押します。
4. "アクセスを承認"ボタンのある画面が表示されますので、それをクリックします。
5. "Google にログイン"の画面がポップアップされますので、自分のアカウントを選択して、"詳細"をクリックし、"...に移動"をクリックします。すると、スコープの承認画面が表示されますので許可します。
6. 表示されたウェブアプリの URL(`https://script.google.com/macros/s/###/exec`)をコピーします。

**注意点:** もしも Web Apps のスクリプトを変更した場合は、Web Apps を上記の手順で再デプロイしてください。これにより、最新のスクリプトが Web Apps へ反映されます。

## 4. 動作テスト

上記で得たウェブアプリの URL(`https://script.google.com/macros/s/###/exec`)へブラウザを使ってアクセスします。すると、上記のデモで見られる"Upload images"のボタンのみが表示されますので、ボタンを押してサンプルの画像をアップロードします。すると、ブラウザ上にアップロードした画像が表示され、デモ動画のようにフォトギャラリーとして閲覧することができます。

# 参考

- [Web Apps](https://developers.google.com/apps-script/guides/web)
- [Taking advantage of Web Apps with Google Apps Script](https://github.com/tanaikech/taking-advantage-of-Web-Apps-with-google-apps-script)