Google Slidesの一覧ページをつくる (Vue.jsとGASでタイトル・サムネイルの表示)

はじめに

ポートフォリオサイトを作っていて、今までに作成したGoogle Slidesの一覧ページをつくりたくなりました。
いわゆる「スライド共有サービス」というものもいくつかありますが、それらはスライドを画像やPDFに変換してからアップロードする必要があります。そのため、アニメーションや動画の埋め込みなどの表現は失われてしまいます。また、公開後にスライドを修正する場合には、修正の度に画像化とアップロードが必要になります。
それに対してGoogle Slidesは、Google Docsなどと同じように「ウェブに公開」することができます。「ウェブに公開」することで、以下のような共有URLが発行されます。
docs.google.com
「ウェブに公開」機能を使ってスライドの共有をすれば、スライドの表現も失われず、修正もそのまま反映されます。
今回は、このGoogle Slidesの共有URLをサムネイル付きで一覧表示するWebページを作ってみました。


https://w-haibara.com/#/slides

f:id:w_haibara:20201027032114p:plain
スライド一覧ページ

要件定義

スライド一覧ページを作るにあたって、以下の要件を定義しました。

  1. スライドの表現(アニメーション・動画埋め込みなど)が失われない
  2. 公開後の修正が簡単
  3. 一覧ページへスライドを登録する手順が簡単
  4. スライドのサムネイルを表示できる
  5. 既存のポートフォリオサイト(Vue.js製)に埋め込める

スライドの表現が失われない・公開後の修正が簡単

これらはGoogle Slidesを使えば満たすことができます。

一覧ページへスライドを登録する手順が簡単

Google Drive上に公開用のフォルダを作り、一覧ページで公開したいスライドはそのフォルダに入れることにしました。これなら一覧ページへの登録・解除は簡単です。ここで、何らかの方法で「Google Drive上の特定のフォルダ内のスライドの情報を取得する」必要があります。これはGASで実装することにしました。やはり餅は餅屋です。

スライドのサムネイルを表示できる

GASのgetThumbnailメソッドを使います。これは、Google Drive上のファイルのサムネイルを取得するものです。
Class File  |  Apps Script  |  Google Developers
このメソッドの返り値はBlobというGAS独自のオブジェクトなので、これをbase64エンコードしてフロントに返すようにしました。GASのBlobについては以下の記事が参考になりました。
GAS の Blob とファイル変換まとめ - Qiita

既存のポートフォリオサイト(Vue.js製)に埋め込める

GAS側でスライドの情報をjsonで返すWebアプリとして実装し、フロントのVue.js側からaxiosを使ってHTTPリクエストを投げることにしました。Vue.js側でスライドの情報を受け取ったら、後は良い感じにやります(デザインは難しい)。

GAS側の実装

GASのコードは以下のようになります。

// HTTP GETリクエストで発火
function doGet(){
  // 公開用フォルダ内のファイル情報を取得
  let data = [];
  const files = DriveApp.getFolderById("<公開用フォルダのID>").getFiles();
  while (files.hasNext()) {
    const file = files.next();
    data.push({
      "name": file.getName(), // ファイル名
      "url": file.getUrl(), // 共有URL
      "created": file.getDateCreated(), // ファイル作成日時
      "thumbnail": blob2base64(file.getThumbnail()), // サムネイル画像(base64)
    });
  }
  
  // ファイルの作成日時が新しい順にソート
  data.sort(function(a, b){
    if( a.created > b.created ) return -1;
    if( a.created < b.created ) return 1;
    return 0;
  });
   
  // dataをjsonとして返す
  const json = JSON.stringify(data);
  const output = ContentService.createTextOutput(json);
  output.setMimeType(ContentService.MimeType.JSON);
  return output;
}

// blob から base64 image への変換
function blob2base64(blob) {
  const contentType = blob.getContentType();
  const base64Data = Utilities.base64Encode(blob.getBytes());
  // 単にbase64エンコーディングするだけでなく、ヘッダも必要
  return "data:" + contentType + ";base64," + base64Data;
}

Vue.js側の実装

Vue.jsのコードは以下のようにしました。
portfolio/Slides.vue at main · w-haibara/portfolio · GitHub

<template>
  <v-sheet class="blue-grey--text text--darken-4" height="100%" tile>
    <v-container>
      <h1 class="font-weight-medium">Slides</h1>
      <div v-if="!slidesLoaded">
        <v-progress-linear
          indeterminate
          color="blue-grey darken-5"
          bottom
        ></v-progress-linear>
      </div>
      <div v-else>
        <v-layout wrap>
          <v-flex v-for="slide in slides" :key="slide.url">
            <v-hover>
              <template v-slot="{ hover }">
                <v-card
                  :class="`elevation-${hover ? 4 : 0}`"
                  class="transition-swing my-1"
                  flat
                  tile
                  color="grey lighten-4"
                  width="24vw"
                  :href="slide.url"
                  target="_blank"
                  rel="noopener"
                >
                  <v-img :src="slide.thumbnail"></v-img>
                  <v-card-title>
                    <span>
                      {{ slide.name }}
                      <v-icon color="blue-grey darken-5" x-small right>
                        fas fa-external-link-alt
                      </v-icon>
                    </span>
                  </v-card-title>
                  <v-card-text>
                    {{ slide.created }}
                  </v-card-text>
                </v-card>
              </template>
            </v-hover>
          </v-flex>
        </v-layout>
      </div>
    </v-container>
  </v-sheet>
</template>

<script>
export default {
  data: () => ({
    slides: {
      id: {
        name: "",
        created: "",
        thumbnail: "",
        url: "",
      },
    },
    slidesLoaded: false,
  }),
  created() {
    this.axios
      .get(
        "<GASでウェブアプリケーションとして公開したURL>"
      )
      .then((response) => {
        this.slides = response.data;
        this.slidesLoaded = true;
      });
  },
};
</script>

おわりに

こういうときにGASは便利ですねー。