184 lines
4.7 KiB
TypeScript
184 lines
4.7 KiB
TypeScript
import {
|
|
batch,
|
|
createSelector,
|
|
createSignal,
|
|
Index,
|
|
Show,
|
|
untrack,
|
|
type Component,
|
|
} from "solid-js";
|
|
import "./TootPoll.css";
|
|
import type { mastodon } from "masto";
|
|
import { resolveCustomEmoji } from "../../masto/toot";
|
|
import {
|
|
Button,
|
|
Checkbox,
|
|
Divider,
|
|
List,
|
|
ListItemButton,
|
|
ListItemText,
|
|
Radio,
|
|
} from "@suid/material";
|
|
import { formatDistance, isBefore } from "date-fns";
|
|
import { useTimeSource } from "~platform/timesrc";
|
|
import { useDateFnLocale } from "~platform/i18n";
|
|
import TootPollDialog from "./TootPollDialog";
|
|
import { ANIM_CURVE_STD } from "~material/theme";
|
|
import { useTootEnv } from "../RegularToot";
|
|
|
|
type TootPollProps = {
|
|
value: mastodon.v1.Poll;
|
|
status: mastodon.v1.Status;
|
|
};
|
|
|
|
const TootPoll: Component<TootPollProps> = (props) => {
|
|
let list!: HTMLUListElement;
|
|
const { vote } = useTootEnv();
|
|
|
|
const now = useTimeSource();
|
|
const dateFnLocale = useDateFnLocale();
|
|
const [mustShowResult, setMustShowResult] = createSignal<boolean>();
|
|
const [showVoteDialog, setShowVoteDialog] = createSignal(false);
|
|
|
|
const [initialVote, setInitialVote] = createSignal(0);
|
|
|
|
const poll = () => props.value;
|
|
|
|
const isShowResult = () => {
|
|
const n = mustShowResult();
|
|
if (typeof n !== "undefined") {
|
|
return n;
|
|
}
|
|
|
|
return poll().expired || poll().voted;
|
|
};
|
|
|
|
const isOwnVote = createSelector(
|
|
() => poll().ownVotes,
|
|
(idx: number, votes) => votes?.includes(idx) || false,
|
|
);
|
|
|
|
const openVote = (i: number, event: Event) => {
|
|
event.stopPropagation();
|
|
|
|
if (poll().expired || poll().voted) {
|
|
return;
|
|
}
|
|
|
|
batch(() => {
|
|
setInitialVote(i);
|
|
setShowVoteDialog(true);
|
|
});
|
|
};
|
|
|
|
const animateAndSetMustShow = (event: Event) => {
|
|
event.stopPropagation();
|
|
list.animate(
|
|
{
|
|
opacity: [0.5, 0, 0.5],
|
|
},
|
|
{
|
|
duration: 220,
|
|
easing: ANIM_CURVE_STD,
|
|
},
|
|
);
|
|
setMustShowResult((x) => {
|
|
if (typeof x === "undefined") {
|
|
return !untrack(isShowResult);
|
|
} else {
|
|
return undefined;
|
|
}
|
|
});
|
|
};
|
|
|
|
return (
|
|
<section class="TootPoll">
|
|
<div class="hints">
|
|
<span>{poll().votesCount} votes in total</span>
|
|
<Show when={poll().expired}>
|
|
<span>Poll is ended</span>
|
|
</Show>
|
|
</div>
|
|
<List ref={list!} disablePadding class="option-list">
|
|
<Index each={poll().options}>
|
|
{(option, index) => {
|
|
return (
|
|
<>
|
|
<Show when={index === 0}>
|
|
<Divider />
|
|
</Show>
|
|
<ListItemButton
|
|
onClick={[openVote, index]}
|
|
class="poll-item"
|
|
aria-disabled={isShowResult()}
|
|
>
|
|
<ListItemText>
|
|
<span
|
|
innerHTML={resolveCustomEmoji(
|
|
option().title,
|
|
option().emojis,
|
|
)}
|
|
></span>
|
|
</ListItemText>
|
|
|
|
<Show when={isShowResult()}>
|
|
<span>
|
|
<Show when={typeof option().votesCount !== "undefined"}>
|
|
{option().votesCount} votes
|
|
</Show>
|
|
</span>
|
|
</Show>
|
|
|
|
<Show
|
|
when={poll().multiple}
|
|
fallback={
|
|
<Radio
|
|
checked={isOwnVote(index)}
|
|
disabled={isShowResult()}
|
|
/>
|
|
}
|
|
>
|
|
<Checkbox
|
|
checked={isOwnVote(index)}
|
|
disabled={isShowResult()}
|
|
/>
|
|
</Show>
|
|
</ListItemButton>
|
|
<Divider />
|
|
</>
|
|
);
|
|
}}
|
|
</Index>
|
|
</List>
|
|
<div class="trailers">
|
|
<Button onClick={animateAndSetMustShow}>
|
|
{isShowResult() ? "Hide result" : "Reveal result"}
|
|
</Button>
|
|
<Show when={poll().expiresAt}>
|
|
<span>
|
|
<span style={{ "margin-inline-end": "0.5ch" }}>
|
|
{isBefore(now(), poll().expiresAt!) ? "Expire in" : "Expired"}
|
|
</span>
|
|
|
|
<time dateTime={poll().expiresAt!}>
|
|
{formatDistance(now(), poll().expiresAt!, {
|
|
locale: dateFnLocale(),
|
|
})}
|
|
</time>
|
|
</span>
|
|
</Show>
|
|
</div>
|
|
|
|
<TootPollDialog
|
|
open={showVoteDialog()}
|
|
options={poll().options}
|
|
multiple={poll().multiple}
|
|
onVote={[vote, props.status]}
|
|
onClose={() => setShowVoteDialog(false)}
|
|
initialVotes={[initialVote()]}
|
|
/>
|
|
</section>
|
|
);
|
|
};
|
|
|
|
export default TootPoll;
|