merge from local

This commit is contained in:
SrGooglo 2024-10-25 09:39:15 +00:00
parent a986804cd1
commit 5ef95ac39a
4 changed files with 260 additions and 83 deletions

View File

@ -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 <div
className={classNames(
className={classnames(
"poll-option",
{
["editable"]: !!editMode
["checked"]: props.checked,
}
)}
style={{
"--percentage": `${props.percentage}%`
}}
onClick={onClick}
>
{
editMode && <antd.Input
placeholder="Option"
defaultValue={option.label}
props.checked && <motion.div
className="percentage-indicator"
animate={{ width: `${props.percentage}%` }}
initial={{ width: 0 }}
transition={{ ease: "easeOut" }}
/>
}
<div className="poll-option-content">
{
!editMode && <span>
{option.label}
props.checked && createIconRender("FaCheck")
}
{
props.showPercentage && <span>
{Math.floor(props.percentage)}%
</span>
}
{
editMode && <antd.Button
onClick={onRemove}
icon={createIconRender("CloseOutlined")}
size="small"
type="text"
/>
}
<span>
{props.option.label}
</span>
</div>
</div>
}
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
})
}
async function removeOption(index) {
setOptions((prev) => {
return [
...prev.slice(0, index),
...prev.slice(index + 1)
]
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,
})
}
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 <div className="poll">
{
options.map((option, index) => {
!editMode && options.map((option, index) => {
const percentage = totalVotes > 0 ? (option.count / totalVotes) * 100 : 0
return <PollOption
key={index}
option={option}
editMode={editMode}
onRemove={() => {
removeOption(index)
}}
onClick={onVote}
checked={option.voted}
percentage={percentage}
showPercentage={hasVoted}
/>
})
}
{
editMode && <div className="poll-edit-actions">
editMode && <antd.Form
name="post-poll"
className="post-poll-edit"
ref={formRef}
initialValues={{
options: options
}}
>
<antd.Form.List
name="options"
>
{(fields, { add, remove }) => {
return <>
{
fields.map((field, index) => {
return <div
key={field.key}
className="post-poll-edit-option"
>
<antd.Form.Item
{...field}
name={[field.name, "label"]}
>
<antd.Input
placeholder="Type a option"
/>
</antd.Form.Item>
{
fields.length > 1 && <antd.Button
onClick={() => remove(field.name)}
icon={createIconRender("MdRemove")}
/>
}
</div>
})
}
<antd.Button
onClick={addOption}
onClick={() => add()}
icon={createIconRender("PlusOutlined")}
>
Add Option
</antd.Button>
</>
}}
</antd.Form.List>
</antd.Form>
}
{
editMode && <div className="poll-edit-actions">
<antd.Button
onClick={onClose}
icon={createIconRender("CloseOutlined")}

View File

@ -13,41 +13,6 @@
padding: 10px;
.poll-option {
display: flex;
flex-direction: row;
align-items: center;
gap: 10px;
padding: 5px;
width: 100%;
transition: all 150ms ease-in-out;
border-radius: 6px;
background-color: var(--background-color-accent);
cursor: pointer;
.ant-input {
background-color: transparent;
width: 100%;
border: 0;
color: var(--text-color);
&::placeholder {
color: var(--text-color);
opacity: 0.5;
}
}
}
.poll-edit-actions {
display: flex;
flex-direction: row;
@ -58,3 +23,74 @@
gap: 10px;
}
}
.poll-option {
z-index: 100;
position: relative;
width: 100%;
height: auto;
transition: all 150ms ease-in-out;
border-radius: 6px;
background-color: var(--background-color-accent);
cursor: pointer;
overflow: hidden;
// &.checked {
// background-color: rgba(var(--bg_color_4), 0.8);
// }
.percentage-indicator {
z-index: 95;
position: absolute;
background-color: var(--colorPrimary);
opacity: 0.5;
width: var(--percentage);
height: 100%;
border-radius: 0 6px 6px 0;
}
.poll-option-content {
z-index: 100;
position: relative;
display: flex;
flex-direction: row;
align-items: center;
gap: 10px;
padding: 5px;
}
}
.post-poll-edit {
display: flex;
flex-direction: column;
gap: 10px;
.post-poll-edit-option {
display: flex;
flex-direction: row;
gap: 10px;
.ant-form-item {
margin: 0;
padding: 0;
width: 100%;
}
}
}

View File

@ -3,6 +3,7 @@ import classnames from "classnames"
import Plyr from "plyr-react"
import { motion } from "framer-motion"
import Poll from "@components/Poll"
import { Icons } from "@components/Icons"
import { processString } from "@utils"
@ -212,6 +213,13 @@ export default class PostCard extends React.PureComponent {
flags={this.state.data.flags}
/>
}
{
this.state.data.poll_options && <Poll
post_id={this.state.data._id}
options={this.state.data.poll_options}
/>
}
</div>
<PostActions

View File

@ -35,6 +35,8 @@ export default class PostCreator extends React.Component {
postingPolicy: DEFAULT_POST_POLICY,
}
pollRef = React.createRef()
creatorRef = React.createRef()
cleanPostData = () => {
@ -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 {
<div className="avatar">
<img src={app.userData?.avatar} />
</div>
<antd.Input.TextArea
placeholder="What are you thinking?"
value={postMessage}
@ -578,6 +588,7 @@ export default class PostCreator extends React.Component {
draggable={false}
allowClear
/>
<div>
<antd.Button
type="primary"
@ -609,6 +620,7 @@ export default class PostCreator extends React.Component {
{
this.state.postPoll && <Poll
formRef={this.pollRef}
options={this.state.postPoll}
onClose={this.handleDeletePoll}
editMode