Skip to content

Commit

Permalink
handle unread messages count in page title
Browse files Browse the repository at this point in the history
  • Loading branch information
aurelienrichard committed May 16, 2024
1 parent 991c863 commit 828c192
Show file tree
Hide file tree
Showing 9 changed files with 102 additions and 54 deletions.
12 changes: 10 additions & 2 deletions apps/privelte/src/lib/components/Chat.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { afterUpdate } from 'svelte'
import { ProgressRadial } from '@skeletonlabs/skeleton'
import Message from './Message.svelte'
import { pendingMessages } from '$lib/stores'
import { pendingMessages, unreadMessagesCount } from '$lib/stores'
import { page } from '$app/stores'
import type { Payload, Presence } from '$lib/types/types'
import Clipboard from './Clipboard.svelte'
Expand All @@ -12,6 +12,7 @@
export let subscribed: 'loading' | 'ok' | 'error'
let bottom: HTMLDivElement
let scrollable: HTMLDivElement
$: getStatus = (id: string) => {
if (!$pendingMessages.get(id)) {
Expand All @@ -28,7 +29,14 @@
})
</script>

<div class="absolute left-0 top-0 h-full w-full space-y-4 overflow-y-auto overflow-x-clip px-4">
<div
bind:this={scrollable}
on:scrollend={() => {
if (scrollable.scrollHeight - scrollable.scrollTop - scrollable.clientHeight < 1)
unreadMessagesCount.reset()
}}
class="absolute left-0 top-0 h-full w-full space-y-4 overflow-y-auto overflow-x-clip px-4"
>
<h1 class="h3 text-surface-600-300-token mb-3 text-center leading-snug md:mb-6">
Share the link
<span
Expand Down
2 changes: 1 addition & 1 deletion apps/privelte/src/lib/components/Message.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
>
{#if isOwnMessage}
<header class="flex items-center justify-center">
<span class="text-lg font-semibold">{payload.username}</span>
<span class="font-semibold">{payload.username}</span>
{#if status === 'loading'}
<ProgressRadial width="ml-auto w-5 h-5 mr-[0.11rem]" />
{:else if status === 'error'}
Expand Down
27 changes: 26 additions & 1 deletion apps/privelte/src/lib/stores.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { writable } from 'svelte/store'
import { writable, derived } from 'svelte/store'

export const createPendingMessages = () => {
const { subscribe, update } = writable<Map<string, 'loading' | 'error'>>(new Map())
Expand All @@ -18,3 +18,28 @@ export const createPendingMessages = () => {
}

export const pendingMessages = createPendingMessages()

export const createUnreadMessagesCount = () => {
const { subscribe, update, set } = writable<number>(0)

return {
subscribe,
increment: () => update((value) => (value < 100 ? value + 1 : value)),
reset: () => set(0)
}
}

export const unreadMessagesCount = createUnreadMessagesCount()

export const unreadMessages = derived(unreadMessagesCount, ($unreadMessagesCount) => {
const strValue = String($unreadMessagesCount)

switch ($unreadMessagesCount) {
case 0:
return ''
case 100:
return '(99+)'
default:
return `(${strValue})`
}
})
5 changes: 4 additions & 1 deletion apps/privelte/src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
import { AppShell, AppBar, LightSwitch } from '@skeletonlabs/skeleton'
import logo from '$lib/images/github-mark.svg'
import { page } from '$app/stores'
import { unreadMessages } from '$lib/stores'
</script>

<svelte:head>
<title>{$page.data.title ?? 'Privelte'}</title>
<title>
{`${$unreadMessages} ${$page.data.title ? `${$page.data.title} - ` : ''}Privelte`}
</title>
<meta name="description" content="chat privately and anonymously." />
</svelte:head>

Expand Down
4 changes: 1 addition & 3 deletions apps/privelte/src/routes/new/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ export const load: PageServerLoad = () => {

currentId = id

return {
id
}
return { id, title: 'New Room' }
}

export const actions: Actions = {
Expand Down
2 changes: 1 addition & 1 deletion apps/privelte/src/routes/room/[id]/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const load: PageServerLoad = async ({ params, cookies }) => {
const { userId, username } = await verifyUser(session, params.id)

return {
title: `${params.id} - Privelte`,
title: `${params.id}`,
userId,
username
}
Expand Down
101 changes: 57 additions & 44 deletions apps/privelte/src/routes/room/[id]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { onMount } from 'svelte'
import { nanoid } from 'nanoid'
import { supabase } from '$lib/supabaseClient'
import { pendingMessages } from '$lib/stores'
import { pendingMessages, unreadMessagesCount } from '$lib/stores'
import type { Payload, Presence } from '$lib/types/types'
import type { PageData } from './$types'
import Chat from '$lib/components/Chat.svelte'
Expand All @@ -14,7 +14,45 @@
const channel = supabase.channel(data.roomId)
const sendMessage = async (message: string, id: string) => {
onMount(() => {
;(async () => {
await heartbeat()
})()
const intervalId = setInterval(async () => {
await heartbeat()
}, 12000)
channel
.on('broadcast', { event: 'presence' }, ({ payload }) => {
const { username, event } = payload as {
username: string
event: 'joined' | 'left'
}
handlePresence(username, event)
})
.on('broadcast', { event: 'message' }, ({ payload }: { payload: Payload }) => {
if (payload.userId !== data.userId) {
unreadMessagesCount.increment()
entries = [...entries, payload]
}
})
.subscribe((status) => {
if (status === 'SUBSCRIBED') {
subscribed = 'ok'
} else {
subscribed = 'error'
}
})
return async () => {
clearInterval(intervalId)
await supabase.removeChannel(channel)
}
})
async function sendMessage(message: string, id: string) {
pendingMessages.setStatus(id, 'loading')
const response = await fetch(data.roomId, {
Expand All @@ -32,13 +70,13 @@
}
}
const handleRetry = async (event: CustomEvent) => {
async function handleRetry(event: CustomEvent) {
const { id, message } = event.detail as Pick<Payload, 'id' | 'message'>
await sendMessage(message, id)
}
const handleSubmit = async (event: CustomEvent) => {
async function handleSubmit(event: CustomEvent) {
const { id, message } = event.detail as Pick<Payload, 'id' | 'message'>
const payload: Payload = {
Expand All @@ -54,48 +92,23 @@
await sendMessage(message, id)
}
onMount(() => {
const intervalId = setInterval(async () => {
await fetch(data.roomId, {
method: 'PATCH'
})
}, 12000)
channel
.on('broadcast', { event: 'presence' }, ({ payload }) => {
const { username, event } = payload as {
username: string
event: 'joined' | 'left'
}
const id = nanoid()
const presence: Presence = {
type: 'presence',
username,
event,
id
}
entries = [...entries, presence]
})
.on('broadcast', { event: 'message' }, ({ payload }: { payload: Payload }) => {
if (payload.userId !== data.userId) {
entries = [...entries, payload]
}
})
.subscribe((status) => {
if (status === 'SUBSCRIBED') {
subscribed = 'ok'
} else {
subscribed = 'error'
}
})
async function heartbeat() {
await fetch(data.roomId, {
method: 'PATCH'
})
}
return async () => {
clearInterval(intervalId)
await supabase.removeChannel(channel)
function handlePresence(username: string, event: 'joined' | 'left') {
const id = nanoid()
const presence: Presence = {
type: 'presence',
username,
event,
id
}
})
entries = [...entries, presence]
}
</script>

<div class="flex h-full flex-col">
Expand Down
2 changes: 1 addition & 1 deletion apps/privelte/src/routes/room/[id]/join/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const load: PageServerLoad = async ({ params, cookies }) => {
try {
await verifyUser(session, params.id)
} catch {
return { title: 'Join - Privelte' }
return { title: 'Join Room' }
}

return redirect(303, `/room/${params.id}`)
Expand Down
1 change: 1 addition & 0 deletions packages/eslint-config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ module.exports = {
'@typescript-eslint/no-explicit-any': ['error', { ignoreRestArgs: true }],
// getting data from 3rd party apis which naming conventions I don't control makes this rule insufferable
'@typescript-eslint/naming-convention': 'off',
'@typescript-eslint/no-floating-promises': ['error', { ignoreIIFE: true }],
'@typescript-eslint/no-misused-promises': [
'error',
{
Expand Down

0 comments on commit 828c192

Please sign in to comment.