mirror of
https://github.com/h3poteto/whalebird-desktop
synced 2025-01-27 15:49:43 +01:00
Implement poll
This commit is contained in:
parent
a7e05c9d03
commit
5fd257dc7a
@ -3,6 +3,7 @@
|
|||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
.emojione {
|
.emojione {
|
||||||
|
display: inline-block;
|
||||||
width: 1.2rem;
|
width: 1.2rem;
|
||||||
height: 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 (
|
return (
|
||||||
<section className="h-full w-full">
|
<section className="h-full w-full">
|
||||||
<div className="w-full bg-blue-950 text-blue-100 p-2 flex justify-between">
|
<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
|
<Virtuoso
|
||||||
style={{ height: '100%' }}
|
style={{ height: '100%' }}
|
||||||
data={statuses}
|
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>
|
</div>
|
||||||
</section>
|
</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 Media from './Media'
|
||||||
import emojify from '@/utils/emojify'
|
import emojify from '@/utils/emojify'
|
||||||
import Card from './Card'
|
import Card from './Card'
|
||||||
|
import Poll from './Poll'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
status: Entity.Status
|
status: Entity.Status
|
||||||
client: MegalodonInterface
|
client: MegalodonInterface
|
||||||
|
onRefresh: (status: Entity.Status) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Status(props: Props) {
|
export default function Status(props: Props) {
|
||||||
const status = originalStatus(props.status)
|
const status = originalStatus(props.status)
|
||||||
|
|
||||||
|
const onRefresh = async () => {
|
||||||
|
const res = await props.client.getStatus(status.id)
|
||||||
|
props.onRefresh(res.data)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="border-b mr-2 py-1">
|
<div className="border-b mr-2 py-1">
|
||||||
{rebloggedHeader(props.status)}
|
{rebloggedHeader(props.status)}
|
||||||
@ -35,6 +42,7 @@ export default function Status(props: Props) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Body status={status} />
|
<Body status={status} />
|
||||||
|
{status.poll && <Poll poll={status.poll} onRefresh={onRefresh} client={props.client} />}
|
||||||
{status.card && <Card card={status.card} />}
|
{status.card && <Card card={status.card} />}
|
||||||
<Media media={status.media_attachments} />
|
<Media media={status.media_attachments} />
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user