import { type Accessor, type Component, createEffect, createResource, Show } from "solid-js"; import type { RouteSectionProps } from "@solidjs/router"; import { type ChartData } from "chart.js"; import { LineChart, RadarChart, WordCloudChart } from "~/components/ui/charts"; import { dmOverviewQuery, dmPartnerRecipientQuery, dmSentMessagesPerPersonOverviewQuery, SELF_ID, threadMostUsedWordsQuery, threadSentMessagesOverviewQuery, } from "~/db"; import { getNameFromRecipient } from "~/lib/get-name-from-recipient"; import { Heading } from "~/components/ui/heading"; import { Grid } from "~/components/ui/grid"; import { Flex } from "~/components/ui/flex"; import { CalendarArrowUp, CalendarArrowDown, CalendarClock, MessagesSquare } from "lucide-solid"; import { getDistanceBetweenDatesInDays } from "~/lib/date"; type MonthIndex = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; const monthNames: Record = { 1: "January", 2: "February", 3: "March", 4: "April", 5: "May", 6: "June", 7: "July", 8: "August", 9: "September", 10: "October", 11: "November", 12: "December", }; const initialMonthMap = Object.fromEntries( Array(12) .fill(0) .map((_value, index) => [index + 1, 0]), ) as Record; export const DmId: Component = (props) => { const dmId = () => Number(props.params.dmid); const [dmOverview] = createResource(async () => { const dmOverview = await dmOverviewQuery(dmId()); if (dmOverview) { return { messageCount: dmOverview.message_count, firstMessageDate: new Date(dmOverview.first_message_date), lastMessageDate: new Date(dmOverview.last_message_date), }; } }); // the other person in the chat with name and id const [dmPartner] = createResource(async () => { const dmPartner = await dmPartnerRecipientQuery(dmId()); if (dmPartner) { return { id: dmPartner._id, name: getNameFromRecipient( dmPartner.nickname_joined_name, dmPartner.system_joined_name, dmPartner.profile_joined_name, ), }; } }); const [dmMessagesPerPerson] = createResource(() => dmSentMessagesPerPersonOverviewQuery(dmId())); const [dmMessagesOverview] = createResource(async () => { const dmMessageOverview = await threadSentMessagesOverviewQuery(dmId()); if (dmMessageOverview) { return dmMessageOverview.map((row) => { return { messageDate: new Date(row.message_datetime), recipientId: row.from_recipient_id, }; }); } }); const dmMessagesPerMonth = () => { const currentDmMessagesOverview = dmMessagesOverview(); if (currentDmMessagesOverview) { return currentDmMessagesOverview.reduce>( (prev, curr) => { const month = curr.messageDate.getMonth() as MonthIndex; prev[month as MonthIndex] += 1; return prev; }, { ...initialMonthMap }, ); } }; // maps all the message counts to dates const dmMessagesPerDateOverview = () => { return dmMessagesPerPerson()?.reduce< { rawDate: string; date: Date; totalMessages: number; [recipientId: number]: number; }[] >((prev, curr) => { const existingDate = prev.find(({ rawDate }) => rawDate === curr.message_date); if (existingDate) { existingDate[curr.from_recipient_id] = curr.message_count; existingDate.totalMessages += curr.message_count; } else { prev.push({ rawDate: curr.message_date, date: new Date(curr.message_date), totalMessages: curr.message_count, [curr.from_recipient_id]: curr.message_count, }); } return prev; }, []); }; const [mostUsedWordCounts] = createResource(async () => threadMostUsedWordsQuery(dmId(), 300)); const recipients = () => { const currentDmPartner = dmPartner(); if (currentDmPartner) { return [ { recipientId: currentDmPartner.id, name: currentDmPartner.name }, { recipientId: SELF_ID, name: "You", }, ]; } return [ { recipientId: SELF_ID, name: "You", }, ]; }; const maxWordSize = 100; const mostUsedWordChartData: Accessor | undefined> = () => { const currentMostUsedWordCounts = mostUsedWordCounts(); if (currentMostUsedWordCounts) { // ordered descending in db query const highestWordCount = currentMostUsedWordCounts[0].count; const calcWordSizeInPixels = (count: number) => { return 10 + Math.round((maxWordSize / highestWordCount) * count); }; return { labels: currentMostUsedWordCounts.map(({ word }) => word), datasets: [ { label: "Used", data: currentMostUsedWordCounts.map(({ count }) => calcWordSizeInPixels(count)), }, ], }; } }; const dateChartData: Accessor | undefined> = () => { const currentDmMessages = dmMessagesPerDateOverview(); const currentRecipients = recipients(); if (currentDmMessages) { return { labels: currentDmMessages.map((row) => row.date.toDateString()), datasets: [ { label: "Total number of messages", data: currentDmMessages.map((row) => row.totalMessages), borderWidth: 2, }, ...currentDmMessages.reduce<{ id: number; label: string; data: number[] }[]>( (prev, curr) => { for (const recipient of currentRecipients) { prev.find(({ id }) => id === recipient.recipientId)?.data.push(curr[recipient.recipientId] ?? 0); } return prev; }, currentRecipients.map((recipient) => { return { id: recipient.recipientId, label: `Number of messages from ${recipient.name.toString()}`, data: [], borderWidth: 2, }; }), ), ], }; } }; const monthChartData: Accessor | undefined> = () => { const currentMessagesPerMonth = dmMessagesPerMonth(); if (currentMessagesPerMonth) { return { labels: Object.values(monthNames), datasets: [ { label: "Number of messages", data: Object.values(currentMessagesPerMonth), }, ], }; } }; return (
DM with {dmPartner()?.name} Chat timeline {(currentDateChartData) => ( )} Your first message is from {(currentDmOverview) => ( {currentDmOverview().firstMessageDate.toDateString()} )} Your last message is from {(currentDmOverview) => ( {currentDmOverview().lastMessageDate.toDateString()} )} You have been chatting for {(currentDmOverview) => ( {getDistanceBetweenDatesInDays( currentDmOverview().firstMessageDate, currentDmOverview().lastMessageDate, )} )} days You have written {(currentDmOverview) => ( {currentDmOverview().messageCount.toString()} )} messages Messages per
Month {(currentMonthChartData) => ( )}
Word cloud {(currentMostUsedWordChartData) => ( // without a container this will scale in height infinitely somehow
)}
); }; export default DmId;