mirror of
https://github.com/h3poteto/whalebird-desktop
synced 2025-01-12 17:04:07 +01:00
Implement poll
This commit is contained in:
parent
a7e05c9d03
commit
5fd257dc7a
renderer
@ -3,6 +3,7 @@
|
||||
@tailwind utilities;
|
||||
|
||||
.emojione {
|
||||
display: inline-block;
|
||||
width: 1.2rem;
|
||||
height: 1.2rem;
|
||||
}
|
||||
|
@ -45,6 +45,23 @@ export default function Timeline(props: Props) {
|
||||
}
|
||||
}
|
||||
|
||||
const updateStatus = (current: Array<Entity.Status>, status: Entity.Status) => {
|
||||
const renew = current.map(s => {
|
||||
if (s.id === status.id) {
|
||||
return status
|
||||
} else if (s.reblog && s.reblog.id === status.id) {
|
||||
return Object.assign({}, s, { reblog: status })
|
||||
} else if (status.reblog && s.id === status.reblog.id) {
|
||||
return status.reblog
|
||||
} else if (status.reblog && s.reblog && s.reblog.id === status.reblog.id) {
|
||||
return Object.assign({}, s, { reblog: status.reblog })
|
||||
} else {
|
||||
return s
|
||||
}
|
||||
})
|
||||
return renew
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="h-full w-full">
|
||||
<div className="w-full bg-blue-950 text-blue-100 p-2 flex justify-between">
|
||||
@ -59,7 +76,14 @@ export default function Timeline(props: Props) {
|
||||
<Virtuoso
|
||||
style={{ height: '100%' }}
|
||||
data={statuses}
|
||||
itemContent={(_, status) => <Status client={props.client} status={status} key={status.id} />}
|
||||
itemContent={(_, status) => (
|
||||
<Status
|
||||
client={props.client}
|
||||
status={status}
|
||||
key={status.id}
|
||||
onRefresh={status => setStatuses(current => updateStatus(current, status))}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
124
renderer/components/timelines/status/Poll.tsx
Normal file
124
renderer/components/timelines/status/Poll.tsx
Normal file
@ -0,0 +1,124 @@
|
||||
import dayjs from 'dayjs'
|
||||
import { Progress, Button, Radio, Label, Checkbox } from 'flowbite-react'
|
||||
import { Entity, MegalodonInterface } from 'megalodon'
|
||||
|
||||
type Props = {
|
||||
poll: Entity.Poll
|
||||
client: MegalodonInterface
|
||||
onRefresh: () => void
|
||||
}
|
||||
export default function Poll(props: Props) {
|
||||
if (props.poll.voted || props.poll.expired) {
|
||||
return <PollResult {...props} />
|
||||
} else if (props.poll.multiple) {
|
||||
return <MultiplePoll {...props} />
|
||||
} else {
|
||||
return <SimplePoll {...props} />
|
||||
}
|
||||
}
|
||||
|
||||
function SimplePoll(props: Props) {
|
||||
const vote = async () => {
|
||||
const elements = document.getElementsByName(props.poll.id)
|
||||
let checked: number | null = null
|
||||
elements.forEach((element, index) => {
|
||||
if ((element as HTMLInputElement).checked) {
|
||||
checked = index
|
||||
}
|
||||
})
|
||||
if (checked !== null) {
|
||||
await props.client.votePoll(props.poll.id, [checked])
|
||||
props.onRefresh()
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div className="my-2">
|
||||
{props.poll.options.map((option, index) => (
|
||||
<div key={index} className="flex items-center gap-2 my-2 pl-1">
|
||||
<Radio id={option.title} name={props.poll.id} value={option.title} />
|
||||
<Label htmlFor={option.title}>{option.title}</Label>
|
||||
</div>
|
||||
))}
|
||||
<div className="flex gap-2 items-center mt-2">
|
||||
<Button outline={true} size="xs" onClick={vote}>
|
||||
Vote
|
||||
</Button>
|
||||
<div>{props.poll.votes_count} people</div>
|
||||
<div>
|
||||
<time dateTime={props.poll.expires_at}>{dayjs(props.poll.expires_at).format('YYYY-MM-DD HH:mm:ss')}</time>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function MultiplePoll(props: Props) {
|
||||
const vote = async () => {
|
||||
let checked: Array<number> = []
|
||||
props.poll.options.forEach((value, index) => {
|
||||
const element = document.getElementById(value.title) as HTMLInputElement
|
||||
if (element.checked) {
|
||||
checked = [...checked, index]
|
||||
}
|
||||
})
|
||||
if (checked.length > 0) {
|
||||
await props.client.votePoll(props.poll.id, checked)
|
||||
props.onRefresh()
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="my-2">
|
||||
{props.poll.options.map((option, index) => (
|
||||
<div key={index} className="flex items-center gap-2 my-2 pl-1">
|
||||
<Checkbox id={option.title} />
|
||||
<Label htmlFor={option.title}>{option.title}</Label>
|
||||
</div>
|
||||
))}
|
||||
<div className="flex gap-2 items-center mt-2">
|
||||
<Button outline={true} size="xs" onClick={vote}>
|
||||
Vote
|
||||
</Button>
|
||||
<div>{props.poll.votes_count} people</div>
|
||||
<div>
|
||||
<time dateTime={props.poll.expires_at}>{dayjs(props.poll.expires_at).format('YYYY-MM-DD HH:mm:ss')}</time>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function PollResult(props: Props) {
|
||||
return (
|
||||
<div className="my-2">
|
||||
{props.poll.options.map((option, index) => (
|
||||
<div key={index}>
|
||||
<span className="pr-2">{percent(option.votes_count ?? 0, props.poll.votes_count)}%</span>
|
||||
<span>{option.title}</span>
|
||||
<Progress progress={percent(option.votes_count ?? 0, props.poll.votes_count)} />
|
||||
</div>
|
||||
))}
|
||||
<div className="flex gap-2 items-center mt-2">
|
||||
<Button outline={true} size="xs" onClick={props.onRefresh}>
|
||||
Refresh
|
||||
</Button>
|
||||
<div>{props.poll.votes_count} people</div>
|
||||
{props.poll.expired ? (
|
||||
<div>Closed</div>
|
||||
) : (
|
||||
<div>
|
||||
<time dateTime={props.poll.expires_at}>{dayjs(props.poll.expires_at).format('YYYY-MM-DD HH:mm:ss')}</time>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const percent = (votes: number, all: number) => {
|
||||
if (all > 0) {
|
||||
return Math.round((votes * 100) / all)
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
@ -5,15 +5,22 @@ import Body from './Body'
|
||||
import Media from './Media'
|
||||
import emojify from '@/utils/emojify'
|
||||
import Card from './Card'
|
||||
import Poll from './Poll'
|
||||
|
||||
type Props = {
|
||||
status: Entity.Status
|
||||
client: MegalodonInterface
|
||||
onRefresh: (status: Entity.Status) => void
|
||||
}
|
||||
|
||||
export default function Status(props: Props) {
|
||||
const status = originalStatus(props.status)
|
||||
|
||||
const onRefresh = async () => {
|
||||
const res = await props.client.getStatus(status.id)
|
||||
props.onRefresh(res.data)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="border-b mr-2 py-1">
|
||||
{rebloggedHeader(props.status)}
|
||||
@ -35,6 +42,7 @@ export default function Status(props: Props) {
|
||||
</div>
|
||||
</div>
|
||||
<Body status={status} />
|
||||
{status.poll && <Poll poll={status.poll} onRefresh={onRefresh} client={props.client} />}
|
||||
{status.card && <Card card={status.card} />}
|
||||
<Media media={status.media_attachments} />
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user