Skip to content

Commit

Permalink
✨ Add graphical support for polls
Browse files Browse the repository at this point in the history
Closes #43
  • Loading branch information
Pustur committed Sep 10, 2024
1 parent 7e33745 commit 778a557
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 10 deletions.
28 changes: 19 additions & 9 deletions src/components/Message/Message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import { Suspense } from 'react';
import Linkify from 'react-linkify';

import Attachment from '../Attachment/Attachment';
import Poll from '../Poll/Poll';
import * as S from './style';
import { IndexedMessage } from '../../types';
import { parsePollMessage } from '../../utils/poll-parser';

function Link(
decoratedHref: string,
Expand Down Expand Up @@ -32,6 +34,22 @@ function Message({
}: IMessage) {
const isSystem = !message.author;
const dateTime = message.date.toISOString().slice(0, 19).replace('T', ' ');
const pollData = parsePollMessage(message.message);
let messageComponent = (
<Linkify componentDecorator={Link}>
<S.Message>{message.message}</S.Message>
</Linkify>
);

if (message.attachment) {
messageComponent = (
<Suspense fallback={`Loading ${message.attachment.fileName}...`}>
<Attachment fileName={message.attachment.fileName} />
</Suspense>
);
} else if (pollData !== null) {
messageComponent = <Poll pollData={pollData} />;
}

return (
<S.Item
Expand All @@ -47,15 +65,7 @@ function Message({
{!isSystem && !sameAuthorAsPrevious && (
<S.Author color={color}>{message.author}</S.Author>
)}
{message.attachment ? (
<Suspense fallback={`Loading ${message.attachment.fileName}...`}>
<Attachment fileName={message.attachment.fileName} />
</Suspense>
) : (
<Linkify componentDecorator={Link}>
<S.Message>{message.message}</S.Message>
</Linkify>
)}
{messageComponent}
</S.Wrapper>
{!isSystem && (
<S.Date dateTime={dateTime}>
Expand Down
27 changes: 27 additions & 0 deletions src/components/Poll/Poll.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { PollStructure } from '../../types';
import * as S from './style';

interface IPoll {
pollData: PollStructure;
}

function Poll({ pollData }: IPoll) {
return (
<S.Poll>
<S.Title>{pollData.title}</S.Title>
{pollData.options.map(option => {
return (
<S.Option key={option.text}>
<div>{option.text}</div>
<S.Flex>
<S.Progress max={pollData.maxVotes} value={option.votes} />
<div>{option.votes}</div>
</S.Flex>
</S.Option>
);
})}
</S.Poll>
);
}

export default Poll;
62 changes: 62 additions & 0 deletions src/components/Poll/style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import styled from 'styled-components';
import {
activeUserDarkBackgroundColor,
viewerDarkBackgroundColor,
whatsappThemeColor,
} from '../../utils/colors';

const Poll = styled.div`
display: flex;
flex-direction: column;
gap: 0.6rem;
`;

const Title = styled.div`
font-weight: bolder;
`;

const Option = styled.div`
display: flex;
flex-direction: column;
gap: 0.2rem;
`;

const Flex = styled.div`
display: flex;
gap: 0.5rem;
align-items: center;
font-size: 0.8rem;
`;

const Progress = styled.progress`
display: block;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
width: 100%;
max-width: 500px;
height: 10px;
appearance: none;
border-radius: 99px;
padding: 1px;
&::-webkit-progress-bar {
background-color: white;
border-radius: 99px;
}
&::-webkit-progress-value {
background-color: ${whatsappThemeColor};
border-radius: 99px;
}
@media (prefers-color-scheme: dark) {
&::-webkit-progress-bar {
background-color: color-mix(
in srgb,
${activeUserDarkBackgroundColor},
${viewerDarkBackgroundColor}
);
}
}
`;

export { Poll, Title, Option, Flex, Progress };
21 changes: 20 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,23 @@ interface DateBounds {
end: Date;
}

export type { FilterMode, ExtractedFile, IndexedMessage, ILimits, DateBounds };
interface PollOption {
text: string;
votes: number;
}

interface PollStructure {
title: string;
options: PollOption[];
maxVotes: number;
}

export type {
FilterMode,
ExtractedFile,
IndexedMessage,
ILimits,
DateBounds,
PollOption,
PollStructure,
};
31 changes: 31 additions & 0 deletions src/utils/poll-parser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { PollOption, PollStructure } from '../types';

function parsePollMessage(message: string): PollStructure | null {
const lines = message
.trim()
.split('\n')
.map(line => line.trim());

if (!lines[0].includes('POLL:') || !lines[2]?.includes('OPTION:'))
return null;

const optionRegex = /OPTION: (?<text>.+) \((?<votes>\d+).*\)/;

const options = lines.slice(2).reduce<PollOption[]>((acc, line) => {
const match = optionRegex.exec(line);

if (!match?.groups) return acc;

const { text, votes } = match.groups;

return acc.concat({ text, votes: Number(votes) });
}, []);

return {
title: lines[1],
options,
maxVotes: Math.max(...options.map(o => o.votes)),
};
}

export { parsePollMessage };

0 comments on commit 778a557

Please sign in to comment.