Skip to content

Overview

  • File: src/components/app/chat/chatNotification.svelte

The Chat Notification component manages real-time chat notifications in the HVC Web application, displaying incoming messages with user avatars and providing quick actions for message interaction.

Type Definitions

Message Interface

ts
interface IMessage {
  fromId: number; // Sender's ID
  from: string; // Sender's name
  groupId: number; // Chat group ID
  groupName: string; // Chat group name
  filename?: string; // File name (for file messages)
  content: string; // Message content
  lines: string[]; // Message lines (for multiple messages)
  contentType: "text" | "file"; // Message type
  date: Date; // Message timestamp
  expires: Date; // Notification expiry
  id: number; // Message ID
  profileImageUrl?: string; // Sender's profile image
  initials: string; // Sender's initials
}

Component Configuration

State Management

svelte
let expirySeconds = 6;    // Notification display duration
let refreshRate = 2000;   // Status check interval
let newMessages: IMessage[] = [];  // Active notifications
let intervalHandle: number;        // Timer reference

Event System

Event Subscriptions

svelte
// Subscribe to new messages
subscribe("onNewMessage", onNewMessage);

// Cleanup subscription
onDestroy(() => {
  unsubscribe("onNewMessage", onNewMessage);
});

Message Handler

svelte
async function onNewMessage(data) {
  if (!data?.content || !hasPermission(Permissions.canChat)) {
    return;
  }

  data.expires = dayjs().add(expirySeconds, "s").toDate();
  data.initials = getInitials(data.from);

  // Skip if in current chat group
  if ($activeChatGroupId === data.groupId) {
    return;
  }

  // Handle different notification scenarios
  if ($showChat) {
    showInfo({title: "New Message", text: data.content});
    addNotice("chat", data);
    return;
  }

  // Update or add new message
  const existing = newMessages.find(x =>
    x.fromId === data.fromId && x.groupId === data.groupId
  );

  if (existing) {
    existing.lines = [...existing.lines, data.content];
    existing.expires = data.expires;
    newMessages = [...newMessages];
  } else {
    newMessages = [...newMessages, {...data, lines: [data.content]}];
  }

  dispatch("newMessage", data);
}

User Interactions

Message Actions

svelte
// Close notification
function closeMessage(msg: IMessage) {
  newMessages = newMessages.filter(x => x.id !== msg.id);
  dispatch("closeMessage", msg);
}

// Open chat group
function openMessage(msg: IMessage) {
  newMessages = newMessages.filter(x => x.id !== msg.id);
  activateChatGroup(msg.groupId);
  dispatch("openMessage", msg);
}

UI Implementation

Notification Container

svelte
<div aria-live="assertive" class="pointer-events-none fixed inset-0 flex items-end px-4 py-6 sm:items-start sm:p-6 z-[100]">
  <div class="flex w-full flex-col items-center space-y-4 sm:items-end"
       class:hidden={$showChat}>
    {#each newMessages as msg}
      <!-- Message notification card -->
    {/each}
  </div>
</div>

Message Card Template

svelte
<div class="pointer-events-auto flex w-full max-w-md rounded-lg divide-x bg-white shadow-lg">
  <!-- Message Content -->
  <div class="w-0 flex-1 p-4">
    <div class="flex items-start">
      <!-- User Avatar -->
      <div class="flex-shrink-0 pt-0.5">
        {#if msg.profileImageUrl}
          <div class="avatar mt-2 placeholder">
            <img src={msg.profileImageUrl} alt=""/>
          </div>
        {:else}
          <div class="avatar mt-2 placeholder">
            <span>{msg.initials}</span>
          </div>
        {/if}
      </div>

      <!-- Message Details -->
      <div class="ml-3 w-0 flex-1">
        <p class="text-sm font-medium">{msg.from}</p>
        <p class="mt-1 text-sm text-gray-500">
          {#if msg.contentType === "file"}
            <span>{msg.content}</span>
          {:else if msg?.lines?.length === 1}
            <span>{msg.content}</span>
          {:else}
            <ul>
              {#each msg.lines as line}
                <li>{line}</li>
              {/each}
            </ul>
          {/if}
        </p>
      </div>
    </div>
  </div>

  <!-- Action Buttons -->
  <div class="flex">
    <button on:click={_ => openMessage(msg)}>Open</button>
    <button on:click={_ => closeMessage(msg)}>Close</button>
  </div>
</div>

Lifecycle Management

Component Initialization

svelte
onMount(() => {
  intervalHandle = setInterval(() => {
    if (newMessages.find(x => x.expires < new Date())) {
      newMessages = newMessages.filter(x => x.expires > new Date());
    }
  }, refreshRate);
});

Cleanup

svelte
onDestroy(() => {
  clearInterval(intervalHandle);
  unsubscribe("onNewMessage", onNewMessage);
});

Usage Guide

Basic Implementation

svelte
<script>
  import ChatNotification from './components/app/chat/chatNotification.svelte';
</script>

<ChatNotification />

Event Handling

svelte
<ChatNotification
  on:newMessage={handleNewMessage}
  on:closeMessage={handleCloseMessage}
  on:openMessage={handleOpenMessage}
/>

Released under the MIT License.