From 5ef95ac39ac7497e649d1a1239348f726ba8c035 Mon Sep 17 00:00:00 2001 From: SrGooglo Date: Fri, 25 Oct 2024 09:39:15 +0000 Subject: [PATCH] merge from local --- packages/app/src/components/Poll/index.jsx | 217 ++++++++++++++---- packages/app/src/components/Poll/index.less | 106 ++++++--- .../app/src/components/PostCard/index.jsx | 8 + .../app/src/components/PostCreator/index.jsx | 12 + 4 files changed, 260 insertions(+), 83 deletions(-) diff --git a/packages/app/src/components/Poll/index.jsx b/packages/app/src/components/Poll/index.jsx index 0da84d84..df32e245 100644 --- a/packages/app/src/components/Poll/index.jsx +++ b/packages/app/src/components/Poll/index.jsx @@ -1,94 +1,215 @@ import React from "react" import * as antd from "antd" -import classNames from "classnames" +import classnames from "classnames" +import { Icons, createIconRender } from "@components/Icons" +import { motion } from "framer-motion" -import { createIconRender } from "@components/Icons" +import useWsEvents from "@hooks/useWsEvents" + +import PostModel from "@models/post" import "./index.less" const PollOption = (props) => { - const { option, editMode, onRemove } = props + async function onClick() { + if (typeof props.onClick === "function") { + await props.onClick(props.option.id) + } + } return
{ - editMode && } - { - !editMode && - {option.label} +
+ { + props.checked && createIconRender("FaCheck") + } + + { + props.showPercentage && + {Math.floor(props.percentage)}% + + } + + + {props.option.label} - } - - { - editMode && - } +
} const Poll = (props) => { - const { editMode, onClose } = props + const { editMode, onClose, formRef } = props const [options, setOptions] = React.useState(props.options ?? []) + const [hasVoted, setHasVoted] = React.useState(false) + const [totalVotes, setTotalVotes] = React.useState(0) - async function addOption() { - setOptions((prev) => { - return [ - ...prev, - { - label: null + useWsEvents({ + "post.poll.vote": (data) => { + const { post_id, option_id, user_id, previous_option_id } = data + + if (post_id !== props.post_id) { + return false + } + + console.debug(`U[${user_id}] vote to option [${option_id}]`) + + setOptions((prev) => { + prev = prev.map((option) => { + return option + }) + + if (user_id === app.userData._id) { + // remove all `voted` properties + prev = prev.map((option) => { + delete option.voted + + option.voted = option.id === option_id + + return option + }) } - ] + + if (previous_option_id) { + const previousOptionIndex = prev.findIndex((option) => option.id === previous_option_id) + + if (previousOptionIndex !== -1) { + prev[previousOptionIndex].count = prev[previousOptionIndex].count - 1 + } + } + + if (option_id) { + const newOptionIndex = prev.findIndex((option) => option.id === option_id) + + if (newOptionIndex !== -1) { + prev[newOptionIndex].count += 1 + } + } + + return prev + }) + } + }, { + socketName: "posts" + }) + + async function onVote(id) { + console.debug(`Voting poll option`, { + option_id: id, + post_id: props.post_id, + }) + + await PostModel.votePoll({ + post_id: props.post_id, + option_id: id, }) } - async function removeOption(index) { - setOptions((prev) => { - return [ - ...prev.slice(0, index), - ...prev.slice(index + 1) - ] - }) - } + React.useEffect(() => { + if (options) { + const totalVotes = options.reduce((sum, option) => { + return sum + option.count + }, 0) + + setTotalVotes(totalVotes) + + const hasVoted = options.some((option) => { + return option.voted + }) + + setHasVoted(hasVoted) + } + }, [options]) return
{ - options.map((option, index) => { + !editMode && options.map((option, index) => { + const percentage = totalVotes > 0 ? (option.count / totalVotes) * 100 : 0 + return { - removeOption(index) - }} + onClick={onVote} + checked={option.voted} + percentage={percentage} + showPercentage={hasVoted} /> }) } { - editMode &&
- + - Add Option - + {(fields, { add, remove }) => { + return <> + { + fields.map((field, index) => { + return
+ + + + { + fields.length > 1 && remove(field.name)} + icon={createIconRender("MdRemove")} + /> + } +
+ }) + } + + add()} + icon={createIconRender("PlusOutlined")} + > + Add Option + + + }} + + + } + + { + editMode &&
} + + { + this.state.data.poll_options && + }
{ @@ -105,6 +107,12 @@ export default class PostCreator extends React.Component { timestamp: DateTime.local().toISO(), } + if (this.pollRef.current) { + let { options } = this.pollRef.current.getFieldsValue() + + payload.poll_options = options.filter((option) => !!option.label) + } + let response = null if (this.props.reply_to) { @@ -496,6 +504,7 @@ export default class PostCreator extends React.Component { status: "done", } }), + postPoll: post.poll_options }) } // fetch the posting policy @@ -567,6 +576,7 @@ export default class PostCreator extends React.Component {
+ +