improve component layout

This commit is contained in:
SrGooglo 2023-03-07 02:10:26 +00:00
parent 38e88b48a7
commit 63ec7daa7a
9 changed files with 288 additions and 239 deletions

View File

@ -1,5 +1,6 @@
import React from "react"
import * as antd from "antd"
import classnames from "classnames"
import moment from "moment"
import { Icons } from "components/Icons"
@ -48,11 +49,10 @@ const CommentCard = (props) => {
}
export default (props) => {
const [visible, setVisible] = React.useState(props.visible ?? true)
const [comments, setComments] = React.useState(null)
const fetchData = async () => {
setComments(null)
// fetch comments
const commentsResult = await PostModel.getPostComments({
post_id: props.post_id
@ -123,9 +123,19 @@ export default (props) => {
}
React.useEffect(() => {
fetchData()
listenEvents()
setVisible(props.visible)
}, [props.visible])
React.useEffect(() => {
if (visible) {
fetchData()
listenEvents()
} else {
unlistenEvents()
}
}, [visible])
React.useEffect(() => {
return () => {
unlistenEvents()
}
@ -149,17 +159,23 @@ export default (props) => {
})
}
if (!comments) {
return <antd.Skeleton active />
}
return <div className="comments">
return <div
id="comments"
className={classnames(
"comments",
{
"hidden": !visible
}
)}
>
<div className="header">
<h3>
<Icons.MessageSquare /> Comments
</h3>
</div>
{renderComments()}
<div className="commentCreatorWrapper">
<CommentCreator
onSubmit={handleCommentSubmit}

View File

@ -16,6 +16,18 @@
color: var(--text-color);
}
&.hidden {
height: 0px;
padding: 0px;
}
overflow: hidden;
width: 100%;
height: 100%;
transition: all 150ms ease-in-out;
.comment {
position: relative;
display: flex;

View File

@ -11,7 +11,7 @@ export default (props) => {
<Button
type="ghost"
shape="circle"
onClick={props.onClickComments}
onClick={props.onClick}
icon={<Icons.MessageCircle />}
/>
{

View File

@ -8,16 +8,36 @@ import CommentsButton from "./commentsButton"
import "./index.less"
const SelfActionsItems = [
{
key: "onClickEdit",
label: <>
<Icons.Edit />
<span>Edit</span>
</>,
},
{
key: "onClickDelete",
label: <>
<Icons.Trash />
<span>Delete</span>
</>,
},
{
type: "divider",
},
]
const MoreActionsItems = [
{
key: "repost",
key: "onClickRepost",
label: <>
<Icons.Repeat />
<span>Repost</span>
</>,
},
{
key: "share",
key: "onClickShare",
label: <>
<Icons.Share />
<span>Share</span>
@ -27,7 +47,7 @@ const MoreActionsItems = [
type: "divider",
},
{
key: "report",
key: "onClickReport",
label: <>
<Icons.AlertTriangle />
<span>Report</span>
@ -36,33 +56,68 @@ const MoreActionsItems = [
]
export default (props) => {
const [isSelf, setIsSelf] = React.useState(false)
const {
onClickLike,
onClickSave,
onClickComments,
} = props.actions ?? {}
const genItems = () => {
let items = MoreActionsItems
if (isSelf) {
items = [...SelfActionsItems, ...items]
}
return items
}
const handleDropdownClickItem = (e) => {
if (typeof props.actions[e.key] === "function") {
props.actions[e.key]()
}
}
return <div className="post_actions_wrapper">
<div className="actions">
<div className="action" id="likes">
<LikeButton
defaultLiked={props.defaultLiked}
onClick={props.onClickLike}
count={props.likesCount}
onClick={onClickLike}
/>
</div>
<div className="action" id="save">
<SaveButton
defaultActive={props.defaultSaved}
onClick={props.onClickSave}
onClick={onClickSave}
/>
</div>
<div className="action" id="comments">
<CommentsButton
onClickComments={props.onClickComments}
count={props.commentsCount}
onClick={onClickComments}
/>
</div>
<div className="action" id="more">
<Dropdown
menu={{
items: MoreActionsItems
items: genItems(),
onClick: handleDropdownClickItem,
}}
trigger={["click"]}
onOpenChange={(open) => {
if (open && props.user_id) {
const isSelf = app.cores.permissions.checkUserIdIsSelf(props.user_id)
setIsSelf(isSelf)
}
}}
overlayStyle={{
minWidth: "200px",
}}
>
<div className="icon">
<Icons.MoreHorizontal />

View File

@ -124,7 +124,7 @@ const Attachment = React.memo((props) => {
}
})
export default (props) => {
export default React.memo((props) => {
const carouselRef = React.useRef(null)
const [attachmentIndex, setAttachmentIndex] = React.useState(0)
@ -174,9 +174,11 @@ export default (props) => {
return null
}
return <Attachment index={index} attachment={attachment} />
return <React.Fragment key={index}>
<Attachment index={index} attachment={attachment} />
</React.Fragment>
})
}
</Carousel>
</div>
}
})

View File

@ -1,75 +0,0 @@
import React from "react"
import * as antd from "antd"
import Plyr from "plyr-react"
import classnames from "classnames"
import { processString } from "utils"
import "./index.less"
export default (props) => {
let { message } = props.data
const [nsfwAccepted, setNsfwAccepted] = React.useState(false)
// parse message
const regexs = [
{
regex: /https?:\/\/(?:www\.)?youtube\.com\/watch\?v=([a-zA-Z0-9_-]{11})(&[a-zA-Z0-9_-]+=[a-zA-Z0-9_-]+)*/g,
fn: (key, result) => {
return <Plyr source={{
type: "video",
sources: [{
src: result[1],
provider: "youtube",
}],
}} />
}
},
{
regex: /(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})/gi,
fn: (key, result) => {
return <a key={key} href={result[1]} target="_blank" rel="noopener noreferrer">{result[1]}</a>
}
},
{
regex: /(@[a-zA-Z0-9_]+)/gi,
fn: (key, result) => {
return <a key={key} onClick={() => window.app.setLocation(`/@${result[1].substr(1)}`)}>{result[1]}</a>
}
},
]
message = processString(regexs)(message)
return <div
className={
classnames(
"post_content",
{
["nsfw"]: props.nsfw && !nsfwAccepted
}
)
}
>
{props.nsfw && !nsfwAccepted &&
<div className="nsfw_alert">
<h2>
This post may contain sensitive content.
</h2>
<antd.Button onClick={() => setNsfwAccepted(true)}>
Show anyways
</antd.Button>
</div>
}
<div className="message">
{message}
</div>
{
props.children
}
</div>
}

View File

@ -1,81 +0,0 @@
.post_content {
position: relative;
display: inline-flex;
flex-direction: column;
align-items: flex-start;
padding: 0 10px 10px 10px;
border-radius: 8px;
color: rgba(var(--background-color-contrast));
overflow: hidden;
transition: all 0.2s ease-in-out;
z-index: 190;
h1,
h2,
h3,
h4,
h5,
h6,
p,
span {
color: var(--text-color);
}
&.nsfw {
.message {
filter: blur(10px);
}
.post_attachments {
filter: blur(25px);
}
}
.nsfw_alert {
position: absolute;
padding: 10px 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
// -webkit-backdrop-filter: blur(25px);
// backdrop-filter: blur(25px);
//background-color: rgba(0, 0, 0, 0.5);
border-radius: 8px;
z-index: 200;
h2 {
color: #fff;
}
}
.message {
width: 100%;
font-size: 0.9rem;
font-weight: 400;
color: var(--background-color-contrast);
word-break: break-all;
user-select: text;
}
>div {
margin-bottom: 10px;
}
}

View File

@ -1,22 +1,48 @@
import React from "react"
import * as antd from "antd"
import classnames from "classnames"
import Plyr from "plyr-react"
import { CommentsCard } from "components"
import { Icons } from "components/Icons"
import { processString } from "utils"
import PostHeader from "./components/header"
import PostContent from "./components/content"
import PostActions from "./components/actions"
import PostAttachments from "./components/attachments"
import "./index.less"
const messageRegexs = [
{
regex: /https?:\/\/(?:www\.)?youtube\.com\/watch\?v=([a-zA-Z0-9_-]{11})(&[a-zA-Z0-9_-]+=[a-zA-Z0-9_-]+)*/g,
fn: (key, result) => {
return <Plyr source={{
type: "video",
sources: [{
src: result[1],
provider: "youtube",
}],
}} />
}
},
{
regex: /(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})/gi,
fn: (key, result) => {
return <a key={key} href={result[1]} target="_blank" rel="noopener noreferrer">{result[1]}</a>
}
},
{
regex: /(@[a-zA-Z0-9_]+)/gi,
fn: (key, result) => {
return <a key={key} onClick={() => window.app.setLocation(`/@${result[1].substr(1)}`)}>{result[1]}</a>
}
},
]
export default class PostCard extends React.PureComponent {
state = {
loading: true,
data: this.props.data ?? {},
countLikes: this.props.data.countLikes ?? 0,
countComments: this.props.data.countComments ?? 0,
@ -24,6 +50,9 @@ export default class PostCard extends React.PureComponent {
hasSaved: this.props.data.isSaved ?? false,
open: this.props.defaultOpened ?? false,
isNsfw: this.props.data.flags?.includes("nsfw") ?? false,
nsfwAccepted: false,
}
onClickDelete = async () => {
@ -32,7 +61,7 @@ export default class PostCard extends React.PureComponent {
return
}
return await this.props.events.onClickDelete(this.state.data)
return await this.props.events.onClickDelete(this.props.data)
}
onClickLike = async () => {
@ -41,7 +70,7 @@ export default class PostCard extends React.PureComponent {
return
}
return await this.props.events.onClickLike(this.state.data)
return await this.props.events.onClickLike(this.props.data)
}
onClickSave = async () => {
@ -50,7 +79,7 @@ export default class PostCard extends React.PureComponent {
return
}
return await this.props.events.onClickSave(this.state.data)
return await this.props.events.onClickSave(this.props.data)
}
onClickEdit = async () => {
@ -59,7 +88,7 @@ export default class PostCard extends React.PureComponent {
return
}
return await this.props.events.onClickEdit(this.state.data)
return await this.props.events.onClickEdit(this.props.data)
}
onDoubleClick = async () => {
@ -76,17 +105,19 @@ export default class PostCard extends React.PureComponent {
}
if (typeof this.props.events?.onToogleOpen === "function") {
this.props.events?.onToogleOpen(to, this.state.data)
this.props.events?.onToogleOpen(to, this.props.data)
}
this.setState({
open: to,
})
//app.controls.openPostViewer(this.state.data)
//app.controls.openPostViewer(this.props.data)
}
onLikesUpdate = (data) => {
console.log("onLikesUpdate", data)
if (data.to) {
this.setState({
countLikes: this.state.countLikes + 1,
@ -99,24 +130,13 @@ export default class PostCard extends React.PureComponent {
}
componentDidMount = async () => {
app.eventBus.on(`post.${this.state.data._id}.delete`, this.onClickDelete)
app.eventBus.on(`post.${this.state.data._id}.update`, this.onClickEdit)
// first listen to post changes
app.cores.api.listenEvent(`post.${this.state.data._id}.likes.update`, this.onLikesUpdate)
this.setState({
isSelf: app.cores.permissions.checkUserIdIsSelf(this.state.data.user_id),
loading: false,
})
app.cores.api.listenEvent(`post.${this.props.data._id}.likes.update`, this.onLikesUpdate)
}
componentWillUnmount = () => {
app.eventBus.off(`post.${this.state.data._id}.delete`, this.onClickDelete)
app.eventBus.off(`post.${this.state.data._id}.update`, this.onClickEdit)
// remove the listener
app.cores.api.unlistenEvent(`post.${this.state.data._id}.likes.update`, this.onLikesUpdate)
app.cores.api.unlistenEvent(`post.${this.props.data._id}.likes.update`, this.onLikesUpdate)
}
componentDidCatch = (error, info) => {
@ -133,62 +153,81 @@ export default class PostCard extends React.PureComponent {
</div>
}
render = () => {
if (this.state.loading) {
return <div
key={this.state.data.key ?? this.state.data._id}
id={this.state.data._id}
className="postCard"
>
<antd.Skeleton active avatar />
</div>
}
render() {
return <div
key={this.props.index ?? this.state.data._id}
id={this.state.data._id}
key={this.props.index}
id={this.props.data._id}
className={classnames(
"postCard",
{
["open"]: this.state.open,
}
)}
style={this.props.style}
context-menu={"postCard-context"}
user-id={this.state.data.user_id}
self-post={this.state.isSelf.toString()}
user-id={this.props.data.user_id}
>
<div className="wrapper">
<PostHeader
postData={this.state.data}
isLiked={this.state.hasLiked}
postData={this.props.data}
onDoubleClick={this.onDoubleClick}
/>
<PostContent
data={this.state.data}
nsfw={this.state.data.flags && this.state.data.flags.includes("nsfw")}
onDoubleClick={this.onDoubleClick}
<div
id="post_content"
className={classnames(
"post_content",
{
["nsfw"]: this.state.isNsfw && !this.state.nsfwAccepted,
}
)}
>
{this.state.data.attachments && this.state.data.attachments.length > 0 && <PostAttachments
attachments={this.state.data.attachments}
/>}
</PostContent>
{
this.state.isNsfw && !this.state.nsfwAccepted &&
<div className="nsfw_alert">
<h2>
This post may contain sensitive content.
</h2>
<antd.Button onClick={() => this.setState({ nsfwAccepted: true })}>
Show anyways
</antd.Button>
</div>
}
<div className="message">
{
processString(messageRegexs)(this.props.data.message ?? "")
}
</div>
{
this.props.data.attachments && this.props.data.attachments.length > 0 && <PostAttachments
attachments={this.props.data.attachments}
/>
}
</div>
</div>
<PostActions
user_id={this.props.data.user_id}
likesCount={this.state.countLikes}
commentsCount={this.state.countComments}
defaultLiked={this.state.hasLiked}
defaultSaved={this.state.hasSaved}
onClickLike={this.onClickLike}
onClickSave={this.onClickSave}
onClickComments={this.onClickComments}
actions={{
delete: this.onClickDelete,
onClickLike: this.onClickLike,
onClickEdit: this.onClickEdit,
onClickDelete: this.onClickDelete,
onClickSave: this.onClickSave,
onClickComments: this.onClickComments,
}}
/>
{
this.state.open && <CommentsCard post_id={this.state.data._id} />
}
<CommentsCard
post_id={this.props.data._id}
visible={this.state.open}
/>
</div>
}
}

View File

@ -1,19 +1,22 @@
.postCard {
display: inline-flex;
display: flex;
flex-direction: column;
width: 100%;
width: 35vw;
min-width: 300px;
max-width: 600px;
background-color: var(--background-color-accent);
transition: all 0.2s ease-in-out;
transition: all 150ms ease-in-out;
padding: 17px;
&.open {
height: 100%;
}
border-bottom: 2px solid var(--border-color);
padding-bottom: 10px;
overflow: hidden;
.wrapper {
display: inline-flex;
@ -29,9 +32,87 @@
}
}
border-bottom: 2px solid var(--border-color);
.post_content {
position: relative;
display: inline-flex;
flex-direction: column;
align-items: flex-start;
padding-bottom: 10px;
padding: 0 10px 10px 10px;
border-radius: 8px;
color: rgba(var(--background-color-contrast));
overflow: hidden;
transition: all 0.2s ease-in-out;
z-index: 190;
h1,
h2,
h3,
h4,
h5,
h6,
p,
span {
color: var(--text-color);
}
&.nsfw {
.message {
filter: blur(10px);
}
.post_attachments {
filter: blur(25px);
}
}
.nsfw_alert {
position: absolute;
padding: 10px 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
// -webkit-backdrop-filter: blur(25px);
// backdrop-filter: blur(25px);
//background-color: rgba(0, 0, 0, 0.5);
border-radius: 8px;
z-index: 200;
h2 {
color: #fff;
}
}
.message {
width: 100%;
font-size: 0.9rem;
font-weight: 400;
color: var(--background-color-contrast);
word-break: break-all;
user-select: text;
}
>div {
margin-bottom: 10px;
}
}
&:first-child {
border-top-left-radius: 8px;