0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Twitterクローンを作ります #45 チャット機能(7)

Posted at

チャット名と参加ユーザーを表示するのと、メッセージを投稿したら画面に反映させるようにします。

チャット名と参加ユーザーの表示

チャット情報はすでに取得しているのでストアに保持します。

client/src/store/index.js

...
    setChat(state, args) {
      state.chat.chat = args.chat;
    },
...
    async loadChat({ commit }, args) {
      return axios
        .get(`/api/chats/${args.chat}`)
        .then((resp) => {
          commit("setChat", { chat: resp.data });
        })
        .catch(defaultErrorHandler);
    },

チャット名はChatsView.vueでも使うので切り出します。

client/src/functions/chat.js
export function chatName(chat, userLoggedIn) {
  if (!chat) {
    return "";
  }
  if (chat.name) {
    return chat.name;
  }
  return chat.users
    .filter((u) => u._id !== userLoggedIn._id)
    .map((u) => u.display_name)
    .join(", ");
}

client/src/views/ChatView.vue
<template>
  <div class="uk-margin-right uk-flex uk-flex-column uk-height-1-1">
    <div>
      <h3 class="uk-margin-top">チャット</h3>
    </div>
    <div class="uk-text-bold">
      {{ chatName(chat, userLoggedIn) }}
    </div>
    <div class="uk-margin-small-top">
      <avatar-pic
        v-for="user in users"
        :key="user._id"
        :user="user"
        size="small"
        :disableLink="true"
        class="uk-margin-small-right"
      ></avatar-pic>
    </div>
    <hr class="top-hr" />
    <div class="uk-flex-1 uk-flex uk-flex-column content">
      <chat-message
        v-for="(message, idx) in messages"
        :key="message._id"
        :message="message"
        :isMine="isMine(message)"
        :isFirst="isFirst(message, idx)"
        :isLast="isLast(message, idx)"
      ></chat-message>
    </div>
    <hr class="bottom-hr" />
    <div class="uk-flex uk-flex-row uk-flex-middle uk-margin-bottom">
      <input
        type="text"
        class="uk-input uk-flex-1 uk-margin-right"
        v-model="text"
        @keypress.prevent.enter.exact="enableSubmit"
        @keyup.prevent.enter.exact="submit"
      />
      <span
        uk-icon="icon: comment; ratio: 1.5"
        @click="onSendClick"
        :class="{ clickable: isValid }"
        :disabled="!isValid"
      ></span>
    </div>
  </div>
</template>

<script>
import { computed, ref, watch } from "vue";
import { useRoute } from "vue-router";
import { useStore } from "vuex";

import AvatarPic from "@/components/AvatarPic.vue";
import ChatMessage from "@/components/ChatMessage.vue";
import { chatName } from "@/functions/chat.js";

export default {
  components: {
    ChatMessage,
    AvatarPic,
  },
  setup() {
    const store = useStore();
    const route = useRoute();

    const userLoggedIn = store.state.userLoggedIn;

    const text = ref("");

    const canSubmit = ref(false);

    const enableSubmit = () => {
      canSubmit.value = true;
    };

    const _post = () => {
      store
        .dispatch("sendMessage", {
          content: text.value,
          sender: store.state.userLoggedIn._id,
          chat: route.params["chatId"],
        })
        .then(() => (text.value = ""));
    };

    const submit = () => {
      if (!canSubmit.value || !text.value.trim()) {
        return;
      }
      _post();
      canSubmit.value = false;
    };

    const onSendClick = () => {
      if (!text.value.trim()) {
        return;
      }
      _post();
      canSubmit.value = false;
    };

    const isValid = computed(() => {
      return Boolean(text.value);
    });

    watch(
      () => route.params,
      (newParams) => {
        store.dispatch("loadChat", { chat: newParams["chatId"] });
        store.dispatch("loadMessages", { chat: newParams["chatId"] });
      },
      {
        immediate: true,
      }
    );

    const messages = computed(() => store.state.chat.messages);

    const isMine = (message) => {
      return message.sender._id === store.state.userLoggedIn._id;
    };

    const isFirst = (message, idx) => {
      if (idx === 0) {
        return true;
      }
      const prev = messages.value[idx - 1];
      return prev.sender._id != message.sender._id;
    };

    const isLast = (message, idx) => {
      if (idx === messages.value.length - 1) {
        return true;
      }
      const next = messages.value[idx + 1];
      return next.sender._id != message.sender._id;
    };

    const chat = computed(() => store.state.chat.chat);
    const users = computed(() => {
      return store.state.chat.chat.users.filter(
        (u) => u._id != store.state.userLoggedIn._id
      );
    });

    return {
      userLoggedIn,
      messages,
      text,
      enableSubmit,
      submit,
      onSendClick,
      isValid,
      isMine,
      isFirst,
      isLast,
      chatName,
      chat,
      users,
    };
  },
};
</script>

<style scoped>
.content {
  overflow-y: auto;
  margin: 0;
  padding: 5px;
}

.top-hr {
  margin-bottom: 0;
}

.bottom-hr {
  margin-top: 0;
}

.clickable {
  cursor: pointer;
}
</style>

スクリーンショット 2022-04-03 22.49.07.png

タイトルと参加者を表示できました。これらを編集できなければならないんですがそれは後で対応しましょう。

投稿したメッセージを表示する

client/src/store/index.js
...
    addMessage(state, args) {
      state.chat.messages.push(args.message);
    },
...
    async sendMessage({ commit }, args) {
      return axios
        .post(`/api/chats/${args.chat}/messages`, {
          content: args.content,
          sender: args.sender,
        })
        .then((resp) => {
          commit("addMessage", { message: resp.data });
          return Promise.resolve(resp.data);
        })
        .catch(defaultErrorHandler);
    },
...

次回は他の参加者が投稿したら表示されるようにしていきます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?