vue.jsとFirebaseのRealtime DatabaseとAuthorization機能を利用したSlackクローンの構築を複数回にわけて行っています。4回目となる今回はSlack Cloneでメッセージの送受信を行うメインページの画面をtailwindを利用して作成していきます。

最終的には下記のメッセージ画面を作成します。静的な画面なのでこの文書を読み終えてもメッセージの送受信などを行うことはできません。次回の文章で送受信機能を実装する予定です。

Slack Cloneの画面完成
Slack Cloneの画面完成

メインページ画面の作成

ユーザの登録画面とサインインの画面の作成は完了しましたがメッセージの送受信を行うメイン画面の作成は完了していません。前回ではメイン画面用のHome.vueファイルにSlack Cloneとサインアウトのボタンを追加したところで完了しています。

サインイン後の画面
サインイン後の画面

コラムの作成

Home.vueファイルに2つのコラムを作成します。1つはチャネルやユーザの一覧を表示し、もう1つのコラムにはSlack Clone上でやりとりするメッセージとメッセージ入力フォームを表示します。本書では前者はサイドバー、後者はメインと呼んで説明を進めていきます。

サイドバー、メインともにわかりやすくするために最初は背景色をつけておきます。Flexboxを利用してサイドバーの幅は全体の1/5(w-1/5)とし残りの領域をメインの幅(flex-growを設定)としています。


<template>
  <div class="flex h-screen">
    <div class="w-1/5 bg-gray-800 text-white">
      <h1 class="font-semibold text-xl leading-tight">Slack Clone</h1>
    </div>
    <div class="flex-grow bg-gray-100">
      <p>ログイン中</p>
      <div>
        <button class="py-1 px-4 bg-gray-800 text-white rounded">サインアウト</button>
      </div>
    </div>
  </div>
</template>

ブラウザで確認すると下記のように表示されます。

前回の文書を元に認証機能でアクセスの制限を行っている場合はサインインを行っておく必要があります。サインインしていない場合は/signinへリダイレクトされます。
2コラムの初期画面
2コラムの初期画面

サイドバーの作成

サイドバーから作成していきます。サイドバーにはユーザ情報、チャンネル情報、ユーザ一覧を表示します。

Slack Cloneのロゴの文字にスペースを与えるためpaddingの設定を行います。


<div class="w-1/5 bg-gray-800 text-white pt-3 px-4">
  <h1 class="font-semibold text-xl leading-tight">Slack Clone</h1>
</div>
paddingを設定
paddingを設定

アイコンの追加

アイコンが必要な場合はtailwindドキュメントで紹介されているフリーのSVGを利用することができます。

フルーのSVGがダウンロードできるURLが記載されているのはhttps://tailwindcss.com/resources#iconsです。左のメニューからResourceを選択し、Iconsからアクセスすることができます。

tailwindのリソースのアイコン
tailwindのリソースのアイコン

Heroicons UIのリンク先はGitHubのページになっているので使いたいアイコンがあればダウンロードして使用することができます。

GitHubのページでアイコン一覧
GitHubのページでアイコン一覧

最初に必要なのはベルのアイコンなのでicon-notification.svgファイルを利用します。

icon-notification.svgファイルを開いてそのまま中身を貼り付けるとアイコンが画面に表示されます。


<h1 class="font-semibold text-xl leading-tight">Slack Clone</h1>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
  <path
    class="heroicon-ui"
    d="M15 19a3 3 0 0 1-6 0H4a1 1 0 0 1 0-2h1v-6a7 7 0 0 1 4.02-6.34 3 3 0 0 1 5.96 0A7 7 0 0 1 19 11v6h1a1 1 0 0 1 0 2h-5zm-4 0a1 1 0 0 0 2 0h-2zm0-12.9A5 5 0 0 0 7 11v6h10v-6a5 5 0 0 0-4-4.9V5a1 1 0 0 0-2 0v1.1z"
  />
</svg>

Slack Cloneの文字列の下にNotificationのアイコンが表示されています。

SVGのアイコンを追加
SVGのアイコンを追加

アイコンSVGのコンポーネント化

そのままsvgタグを貼り付けても表示できるのですがsvgファイルのテキストの中身が長いのでコンポーネント化します。またコンポーネント化すると再利用する際に効率よく行うことができます。

componentsディレクトリにiconsディレクトリを作成しNotification.vueファイルを作成します。Notification.svgファイルの中身をペーストしてclassを設定します。

propsを利用してアイコンのサイズを設定することもできますが本文書ではtailwindのclassを利用してコンポーネントファイルのなkでサイズと色を固定しています。

<template>
  <svg
    xmlns="http://www.w3.org/2000/svg"
    viewBox="0 0 24 24"
    class="h-6 w-6 fill-current text-white opacity-25"
  >
    <path
      class="heroicon-ui"
      d="M15 19a3 3 0 0 1-6 0H4a1 1 0 0 1 0-2h1v-6a7 7 0 0 1 4.02-6.34 3 3 0 0 1 5.96 0A7 7 0 0 1 19 11v6h1a1 1 0 0 1 0 2h-5zm-4 0a1 1 0 0 0 2 0h-2zm0-12.9A5 5 0 0 0 7 11v6h10v-6a5 5 0 0 0-4-4.9V5a1 1 0 0 0-2 0v1.1z"
    />
  </svg>
</template>

作成したコンポーネントをHome.vueファイルで利用するためにimportを行います。


import firebase from "firebase/app";
import "firebase/auth";

import Notification from "../components/icons/Notification";

export default {
  components: {
    Notification
  },
// 略

先程まで直接記述していたsvgタグをコンポーネントのNotificationに変更します。


<div class="w-1/5 bg-gray-800 text-white pt-3 px-4">
  <h1 class="font-semibold text-xl leading-tight">Slack Clone</h1>
  <Notification />
</div>

今後アイコンを設定する場合は同じ方法でコンポーネントを作成します。

アイコンの配置場所の調節

Flexboxを利用してSlack Cloneの右側にNotificationのアイコンを配置します。


<div class="w-1/5 bg-gray-800 text-white pt-3 px-4">
  <div class="flex justify-between items-center">
    <h1 class="font-semibold text-xl leading-tight">Slack Clone</h1>
    <Notification />
  </div>
</div>
アイコンの場所の調整
アイコンの場所の調整

ユーザ情報の取得

/(ルート)へアクセスできるのはサインインしているユーザだけなのでライフサイクルフックのmountedを利用してFirebaseからユーザ情報を取得します。

ユーザの認証時にFirebaseのonAuthStateChangedメソッドを利用してユーザ情報を取得しましたがここではfirebase.auth().currentUserを使用してユーザ情報を取得します。どちらでのユーザ情報を取得することができます。

vue.jsのデータプロパティにuserを追加し、mountedでfirebase.auth().currenetUserを実行し、追加したuserに取得したユーザ情報を設定します。


export default {
  components: {
    Notification
  },
  data() {
    return {
      user: ""
    };
  },
  methods: {
    signOut() {
      firebase.auth().signOut();
      this.$router.push("/signin");
    }
  },
  mounted(){
    this.user = firebase.auth().currentUser;
  }
};

ユーザ情報の表示

取得したユーザ情報を表示します。FirebaseにはEmailしか登録していないのでユーザ名ではなくEmailを使います。


<div class="w-1/5 bg-gray-800 text-white pt-3 px-4">
  <div class="flex justify-between items-center">
    <h1 class="font-semibold text-xl leading-tight">Slack Clone</h1>
    <Notification />
  </div>
  <div>{{ user.email }}</div>
</div>

Firebaseから取得したユーザ情報を利用してemailが表示されることが確認できます。

emailの表示
emailの表示

Emailの左側に○を表示させ文字のカラーをopacityで調整します。


<div class="flex items-center">
  <span class="bg-yellow-400 rounded-full w-3 h-3 mr-2"></span>
  <span class="opacity-50">{{ user.email }}</span>
</div>

ブラウザで確認すると以下のように表示されます。

ユーザ情報の表示
ユーザ情報の表示

チャンネルの追加

チャンネルの機能はまだ実装されていないためダミーでチャンネル情報の表示を行います。

チャンネルの表示には文字列とアイコンを使用します。アイコンはHeroicons UIのicons-plus-circle.svgを使います。アイコンはPlusCircle.vueという名前でcomponents¥iconsの下にコンポーネントファイルを作成します。

チャンネルとアイコンのHTMLタグをユーザ情報の下に追加します。


<div class="mt-5 flex justify-between items-center">
  <div class="font-bold opacity-50 text-lg">チャンネル</div>
  <PlusCircle />
</div>
チャンネルを追加
チャンネルを追加

ダミーでチャンネルを追加します。

vue.jsのデータプロパティにchannelsを追加し、3つのチャンネル名を登録します。


  data() {
    return {
      user: "",
      channels: [
        {
          id: 1,
          channel_name: "営業"
        },
        {
          id: 2,
          channel_name: "マーケティング"
        },
        {
          id: 3,
          channel_name: "情シス"
        }
      ]
    };
  },

追加したchannelsはチャンネルの下でv-forを使って展開します。


<div
  class="opacity-50 mt-1"
  v-for="channel in channels"
  :key="channel.id"
># {{ channel.channel_name }}</div>

チャンネルの下にデータプロパティchannelsで登録した3件のチャンネル名が表示されます。

チャンネルをv-forで展開
チャンネルをv-forで展開

ダイレクトメッセージの設定

チャンネルと同じ方法でダイレクトメッセージの表示設定を行います。

先にダミーでデータプロパティにusersを追加します。


  data() {
    return {
      user: "",
      users: [
        {
          user_id: 11,
          email: "john@example.com"
        },
        {
          user_id: 12,
          email: "kevin@test.com"
        },
        {
          user_id: 13,
          email: "susan@test.com"
        }
      ],
      channels: [

チャンネルの下にユーザの情報を追加します。


<div class="mt-5 flex justify-between items-center">
  <div class="font-bold opacity-50 text-lg">ダイレクトメッセージ</div>
  <PlusCircle />
</div>
<div class="mt-2 flex items-center" v-for="user in users" :key="user.user_id">
  <span class="bg-yellow-400 rounded-full w-3 h-3 mr-2"></span>
  <span class="opacity-50">{{ user.email }}</span>
</div>

ブラウザで確認するとチャンネルの下にダイレクトメッセージとユーザの一覧が表示されることが確認できます。

ユーザ一覧を表示
ユーザ一覧を表示

ここまででサイドバーの作成は終了です。

メインの作成

メッセージの内容を表示させるメインの作成を行っていきます。

ヘッダーの設定

メインの上部のヘッダーの左上には最終的にはメッセージの送信相手の情報(チェンネル名やユーザ名)を表示させますがメッセージ送信機能はまだ実行されていないのでログインしたユーザのemailを表示させ、右側にはログアウトボタンを表示させます。


<div class="flex-grow bg-gray-100">
  <header class="border-b">
    <div class="flex justify-between m-3">
      <div class="font-bold text-lg">{{ user.email}}</div>
      <div>
        <button class="py-1 px-4 bg-gray-800 text-white rounded" @click="singOut">サインアウト</button>
      </div>
    </div>
  </header>
</div>

左側にemail、右側にログアウトボタンが表示されます。

ヘッダーの作成
ヘッダーの作成

アイコンの追加

ヘッダーにいくつかアイコンを追加しますがこれまでと同様にアイコンはHeroicons UIを利用してコンポーネントを作成します。

icon-search.svg, icon-star.svg, icon-cog.svg, icon-call.svg, icon-information.svg, atsymbol.svgを使います。

対応するコンポーネントはcomponets¥iconsの下にSearch.vue, Star.vue, Cog.svg, Call.vue, Information.vue, AtSymbol.vueという名前で保存します。

Search.vueのように今回追加したアイコンにはtailwindのclass text-gray-700でカラーを設定しています。


<template>
  <svg
    xmlns="http://www.w3.org/2000/svg"
    viewBox="0 0 24 24"
    class="h-6 w-6 fill-current text-gray-700"
  >
    <path
      class="heroicon-ui"
      d="M16.32 14.9l5.39 5.4a1 1 0 0 1-1.42 1.4l-5.38-5.38a8 8 0 1 1 1.41-1.41zM10 16a6 6 0 1 0 0-12 6 6 0 0 0 0 12z"
    />
  </svg>
</template>

Home.vueファイルで追加したアイコンのコンポーネントをimportします。


import Notification from "../components/icons/Notification";
import PlusCircle from "../components/icons/PlusCircle";
import Search from "../components/icons/Search";
import Star from "../components/icons/Star";
import Cog from "../components/icons/Cog";
import Call from "../components/icons/Call";
import Information from "../components/icons/Information";
import AtSymbol from "../components/icons/AtSymbol";

export default {
  components: {
    Notification,
    PlusCircle,
    Search,
    Star,
    Cog,
    Call,
    Information,
    AtSymbol
  },
  data() {

追加したアイコンコンポーネントをヘッダーに追加します。


<div class="flex justify-between m-3">
  <div class="font-bold text-lg">{{ user.email}}</div>
  <div class="flex">
    <Call />
    <Information />
    <Cog />
    <Search />
    <AtSymbol />
    <Star />
    <button class="py-1 px-4 bg-gray-800 text-white rounded" @click="singOut">サインアウト</button>
  </div>
</div>
アイコンをヘッダーに追加
アイコンをヘッダーに追加

ダミーの検索枠とアイコンの調整を行います。


<div class="flex justify-between m-3">
  <div class="font-bold text-lg">{{ user.email}}</div>
  <div class="flex items-center">
    <Call class="mx-2" />
    <Information />
    <Cog class="mx-2" />
    <div class="border w-64 rounded p-1 ml-2 mr-2 flex">
      <Search />
      <span class="ml-2 text-gray-700">検索</span>
    </div>
    <AtSymbol class="mx-2" />
    <Star class="mx-2" />
    <button class="py-1 px-4 bg-gray-800 text-white rounded" @click="singOut">サインアウト</button>
  </div>
</div>
アイコンの調整
アイコンの調整

emailの下にも☆のアイコンを追加しておきます。


<div>
  <div class="font-bold text-lg">{{ user.email}}</div>
  <Star />
</div>
Starコンポーネント追加
Starコンポーネント追加

メッセージ表示、入力部分の作成

メッセージを表示させる部分とメッセージを入力する部分を作成します。

Flexboxのflex-colとflex-grow利用して入力する部分がブラウザの一番下になるように設定します。

flex-growはメッセージを表示するdivに設定し、メッセージを入力する部分以外の残りの領域をflex-growを設定したdivが占めるように設定を行っています。またメッセージが増えた場合にスクロールバーで移動できるようにoverflow:yを設定しています。


<main class="h-full overflow-y-scroll"<
  <div class="h-full flex flex-col ml-6"<
    <div class="flex-grow overflow-y-scroll"<
      <p<メッセージ一覧</p<
    </div<
    <div class="border border-gray-900 rounded mb-4"<
      <textarea class="w-full pt-4 pl-8 outline-none" placeholder="XXXXへのメッセージ"<</textarea<
      <div class="bg-gray-100 p-2"<
        <button class="bg-green-900 text-sm text-white font-bold py-1 px-2 rounded"<送信</button<
      </div<
    </div<
  </div<
</main<
メッセージ表示と入力部分の設定
メッセージ表示と入力部分の設定

アバター画像の設定

メッセージの表示を設定する前にユーザのアバター画像の設定を行います。通常だとユーザ毎に各自が画像を用意して設定を行いますが本アプリケーションではhttps://pravatar.cc/の画像を利用します。

pravator.ccサイト
pravator.ccサイト

https://i.pravator.cc/50でアクセスすると50サイズの画像がランダムで表示されます。画像を固定したい場合は、https://i.pravator.cc/50?u=john@example.comと設定すると毎回同じ画像を取得することができます。

componetsディレクトリの中にAvator.vueファイルを作成します。propsを利用してコンポーネント外部から情報を受け取ります。受け取った情報を使ってpravator.ccから画像を取得しています。


<template>
  <div>
    <img :src="avator_url" class="rounded" />
  </div>
</template>

<script>
export default {
  name: "Avator",
  props: ["user"],
  computed: {
    avator_url() {
      return "https://i.pravatar.cc/50?u=" + this.user;
    }
  }
};
</script>

メッセージを表示する際に作成したAvatorコンポーネントを利用します。Home.vueファイルへのAvatorコンポーネントのimportを行っておきます。


import Information from "../components/icons/Information";
import AtSymbol from "../components/icons/AtSymbol";
import Avator from "../components/Avator";

export default {
  components: {
    Notification,
    PlusCircle,
    Search,
    Star,
    Cog,
    Call,
    Information,
    AtSymbol,
    Avator
  },

メッセージの表示

ダミーメッセージを使ってメッセージ表示部分の作成を行います。Avatorコンポーネントのpropsにはemailを渡しています。emailはユーザ毎に一意で固定されているのでemailが同じであればいつも同じ画像が取得されることになります。


<div class="flex-grow overflow-y-scroll">
  <div class="mt-2 mb-4 flex">
    <Avator :user="user.email" />
    <div class="ml-2">
      <div class="font-bold">{{ user.email }}</div>
      <div>初めてのメッセージ</div>
    </div>
  </div>
</div>

ブラウザで確認すると以下のような画面が表示されます。

Slack Cloneの画面完成
Slack Cloneの画面完成

ここまではvue.jsとFirebaseを使ってサインインしたユーザの情報のみを利用していましたが、次からはユーザ同士でのメッセージの送受信を実装していきます。