mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-10 02:54:15 +00:00
early implement media viewer & self menu
This commit is contained in:
parent
c08fc6cf8e
commit
7c49505551
@ -1,17 +1,23 @@
|
|||||||
import React from "react"
|
import React from "react"
|
||||||
import * as antd from "antd"
|
import * as antd from "antd"
|
||||||
|
import { Swiper } from "antd-mobile"
|
||||||
import { Icons } from "components/Icons"
|
import { Icons } from "components/Icons"
|
||||||
import { LikeButton } from "components"
|
import { LikeButton } from "components"
|
||||||
import moment from "moment"
|
import moment from "moment"
|
||||||
import classnames from "classnames"
|
import classnames from "classnames"
|
||||||
|
import loadable from "@loadable/component"
|
||||||
import { User } from "models"
|
|
||||||
|
|
||||||
import CSSMotion from "rc-animate/lib/CSSMotion"
|
import CSSMotion from "rc-animate/lib/CSSMotion"
|
||||||
import useLayoutEffect from "rc-util/lib/hooks/useLayoutEffect"
|
import useLayoutEffect from "rc-util/lib/hooks/useLayoutEffect"
|
||||||
|
|
||||||
import "./index.less"
|
import "./index.less"
|
||||||
|
|
||||||
|
const ContentFailed = () => {
|
||||||
|
return <div className="contentFailed">
|
||||||
|
<Icons.MdCloudOff />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
const getCurrentHeight = (node) => ({ height: node.offsetHeight })
|
const getCurrentHeight = (node) => ({ height: node.offsetHeight })
|
||||||
|
|
||||||
const getMaxHeight = (node) => {
|
const getMaxHeight = (node) => {
|
||||||
@ -78,13 +84,104 @@ function PostHeader(props) {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
function PostContent({ message }) {
|
const PostContent = React.memo((props) => {
|
||||||
|
let { message, additions } = props.data
|
||||||
|
|
||||||
|
// first filter if is an string
|
||||||
|
additions = additions.filter(file => typeof file === "string")
|
||||||
|
|
||||||
|
// then filter if is an uri
|
||||||
|
additions = additions.filter(file => /^(http|https):\/\//.test(file))
|
||||||
|
|
||||||
|
additions = additions.map((uri, index) => {
|
||||||
|
const MediaRender = loadable(async () => {
|
||||||
|
// create a basic http request for fetching the file media type
|
||||||
|
const request = new Request(uri, {
|
||||||
|
method: "HEAD",
|
||||||
|
})
|
||||||
|
|
||||||
|
// fetch the file media type
|
||||||
|
const mediaType = await fetch(request)
|
||||||
|
.then(response => response.headers.get("content-type"))
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error)
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!mediaType) {
|
||||||
|
return () => <ContentFailed />
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (mediaType.split("/")[0]) {
|
||||||
|
case "image": {
|
||||||
|
return () => <img src={uri} />
|
||||||
|
}
|
||||||
|
case "video": {
|
||||||
|
return () => <video controls>
|
||||||
|
<source src={uri} type={mediaType} />
|
||||||
|
</video>
|
||||||
|
}
|
||||||
|
case "audio": {
|
||||||
|
return () => <audio controls>
|
||||||
|
<source src={uri} type={mediaType} />
|
||||||
|
</audio>
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
return () => <h4>
|
||||||
|
Unsupported media type [{mediaType}]
|
||||||
|
</h4>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return <Swiper.Item
|
||||||
|
key={index}
|
||||||
|
>
|
||||||
|
<div className="addition">
|
||||||
|
<React.Suspense fallback={<div>Loading</div>} >
|
||||||
|
<MediaRender />
|
||||||
|
</React.Suspense>
|
||||||
|
</div>
|
||||||
|
</Swiper.Item>
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
return <div className="content">
|
return <div className="content">
|
||||||
{message}
|
{message}
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
function PostActions(props) {
|
{additions &&
|
||||||
|
<div className="additions">
|
||||||
|
<Swiper
|
||||||
|
direction="vertical"
|
||||||
|
indicatorProps={{
|
||||||
|
style: {
|
||||||
|
'--dot-color': 'rgba(0, 0, 0, 0.4)',
|
||||||
|
'--active-dot-color': '#ffc0cb',
|
||||||
|
'--dot-size': '50px',
|
||||||
|
'--active-dot-size': '30px',
|
||||||
|
'--dot-border-radius': '50%',
|
||||||
|
'--active-dot-border-radius': '15px',
|
||||||
|
'--dot-spacing': '8px',
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{additions}
|
||||||
|
</Swiper>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
})
|
||||||
|
|
||||||
|
const PostActions = (props) => {
|
||||||
|
const handleSelfMenuAction = async (event) => {
|
||||||
|
const fn = props.actions[event.key]
|
||||||
|
|
||||||
|
if (typeof fn === "function") {
|
||||||
|
await fn()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return <div className="actions">
|
return <div className="actions">
|
||||||
<div className="action" id="likes">
|
<div className="action" id="likes">
|
||||||
<div className="icon">
|
<div className="icon">
|
||||||
@ -101,10 +198,25 @@ function PostActions(props) {
|
|||||||
<Icons.Bookmark />
|
<Icons.Bookmark />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{props.isSelf && <div className="action" id="selfMenu" onClick={props.onClickSelfMenu}>
|
{props.self && <div className="action" id="selfMenu" onClick={props.onClickSelfMenu}>
|
||||||
<div className="icon">
|
<antd.Dropdown
|
||||||
<Icons.MoreVertical />
|
overlay={<antd.Menu
|
||||||
</div>
|
onClick={handleSelfMenuAction}
|
||||||
|
>
|
||||||
|
<antd.Menu.Item icon={<Icons.Edit />} key="edit">
|
||||||
|
Edit
|
||||||
|
</antd.Menu.Item>
|
||||||
|
<antd.Menu.Divider />
|
||||||
|
<antd.Menu.Item icon={<Icons.Trash />} key="delete">
|
||||||
|
Delete
|
||||||
|
</antd.Menu.Item>
|
||||||
|
</antd.Menu>}
|
||||||
|
trigger={['click']}
|
||||||
|
>
|
||||||
|
<div className="icon">
|
||||||
|
<Icons.MoreVertical />
|
||||||
|
</div>
|
||||||
|
</antd.Dropdown>
|
||||||
</div>}
|
</div>}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@ -112,36 +224,59 @@ function PostActions(props) {
|
|||||||
export class PostCard extends React.Component {
|
export class PostCard extends React.Component {
|
||||||
state = {
|
state = {
|
||||||
loading: true,
|
loading: true,
|
||||||
selfId: null,
|
likes: this.props.data.likes,
|
||||||
data: this.props.data,
|
comments: this.props.data.comments,
|
||||||
}
|
}
|
||||||
|
|
||||||
api = window.app.request
|
api = window.app.request
|
||||||
|
|
||||||
componentDidMount = async () => {
|
componentDidMount = async () => {
|
||||||
const selfId = await User.selfUserId()
|
window.app.ws.listen(`post.like.${this.props.data._id}`, async (data) => {
|
||||||
|
await this.setState({ likes: data })
|
||||||
window.app.ws.listen(`like.post.${this.props.data._id}`, async (data) => {
|
|
||||||
await this.setState({ data })
|
|
||||||
})
|
})
|
||||||
window.app.ws.listen(`unlike.post.${this.props.data._id}`, async (data) => {
|
window.app.ws.listen(`post.unlike.${this.props.data._id}`, async (data) => {
|
||||||
await this.setState({ data })
|
await this.setState({ likes: data })
|
||||||
|
})
|
||||||
|
|
||||||
|
window.app.ws.listen(`post.comment.${this.props.data._id}`, async (data) => {
|
||||||
|
await this.setState({ comments: data })
|
||||||
|
})
|
||||||
|
window.app.ws.listen(`post.uncomment.${this.props.data._id}`, async (data) => {
|
||||||
|
await this.setState({ comments: data })
|
||||||
})
|
})
|
||||||
|
|
||||||
await this.setState({
|
await this.setState({
|
||||||
selfId,
|
|
||||||
loading: false
|
loading: false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onClickDelete = async () => {
|
||||||
|
const result = await this.api.delete.post({
|
||||||
|
post_id: this.props.data._id,
|
||||||
|
}).catch(error => {
|
||||||
|
console.error(error)
|
||||||
|
antd.message.error(error.message)
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
if (typeof this.props.close === "function") {
|
||||||
|
this.props.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onClickLike = async (to) => {
|
onClickLike = async (to) => {
|
||||||
let result = false
|
let result = false
|
||||||
|
|
||||||
if (to) {
|
if (to) {
|
||||||
const apiResult = await await this.api.put.like({ post_id: this.props.data._id })
|
const apiResult = await this.api.put.like({ post_id: this.props.data._id })
|
||||||
result = apiResult.success
|
result = apiResult.success
|
||||||
} else {
|
} else {
|
||||||
const apiResult = await await this.api.put.unlike({ post_id: this.props.data._id })
|
const apiResult = await this.api.put.unlike({ post_id: this.props.data._id })
|
||||||
result = apiResult.success
|
result = apiResult.success
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,15 +284,15 @@ export class PostCard extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onClickSave = async () => {
|
onClickSave = async () => {
|
||||||
// TODO: save post
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
onClickEdit = async () => {
|
||||||
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
hasLiked = () => {
|
hasLiked = () => {
|
||||||
return this.state.data.likes.some(user_id => user_id === this.state.selfId)
|
return this.state.likes.some((user_id) => user_id === this.props.selfId)
|
||||||
}
|
|
||||||
|
|
||||||
isSelf = () => {
|
|
||||||
return this.state.selfId === this.state.data.user._id
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@ -176,13 +311,11 @@ export class PostCard extends React.Component {
|
|||||||
<PostHeader
|
<PostHeader
|
||||||
postData={this.props.data}
|
postData={this.props.data}
|
||||||
isLiked={hasLiked}
|
isLiked={hasLiked}
|
||||||
onClickLike={() => this.onClickLike(false)}
|
likes={this.state.likes.length}
|
||||||
onClickSave={this.onClickSave}
|
comments={this.state.comments.length}
|
||||||
likes={this.state.data.likes.length}
|
|
||||||
comments={this.state.data.comments.length}
|
|
||||||
/>
|
/>
|
||||||
<PostContent
|
<PostContent
|
||||||
message={this.props.data.message}
|
data={this.props.data}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="actionsIndicatorWrapper">
|
<div className="actionsIndicatorWrapper">
|
||||||
@ -192,26 +325,26 @@ export class PostCard extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
<div className="actionsWrapper">
|
<div className="actionsWrapper">
|
||||||
<PostActions
|
<PostActions
|
||||||
|
self={this.props.self}
|
||||||
onClickLike={this.onClickLike}
|
onClickLike={this.onClickLike}
|
||||||
defaultLiked={hasLiked}
|
defaultLiked={hasLiked}
|
||||||
isSelf={this.isSelf()}
|
actions={{
|
||||||
|
edit: this.onClickEdit,
|
||||||
|
delete: this.onClickDelete,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PostCardAnimated = ({
|
export const PostCardAnimated = (props, ref,) => {
|
||||||
data,
|
|
||||||
onAppear,
|
|
||||||
motionAppear,
|
|
||||||
}, ref,) => {
|
|
||||||
const motionRef = React.useRef(false)
|
const motionRef = React.useRef(false)
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
if (motionRef.current) {
|
if (motionRef.current) {
|
||||||
onAppear()
|
props.onAppear()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
@ -219,23 +352,23 @@ export const PostCardAnimated = ({
|
|||||||
return <CSSMotion
|
return <CSSMotion
|
||||||
ref={ref}
|
ref={ref}
|
||||||
motionName="motion"
|
motionName="motion"
|
||||||
motionAppear={motionAppear}
|
motionAppear={props.motionAppear}
|
||||||
onAppearStart={getCollapsedHeight}
|
onAppearStart={getCollapsedHeight}
|
||||||
onAppearActive={node => {
|
onAppearActive={node => {
|
||||||
motionRef.current = true
|
motionRef.current = true
|
||||||
return getMaxHeight(node)
|
return getMaxHeight(node)
|
||||||
}}
|
}}
|
||||||
onAppearEnd={onAppear}
|
onAppearEnd={props.onAppear}
|
||||||
onLeaveStart={getCurrentHeight}
|
onLeaveStart={getCurrentHeight}
|
||||||
onLeaveActive={getCollapsedHeight}
|
onLeaveActive={getCollapsedHeight}
|
||||||
onLeaveEnd={() => {
|
onLeaveEnd={() => {
|
||||||
onLeave(id)
|
props.onLeave(id)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{(props, passedMotionRef) => {
|
{(_args, passedMotionRef) => {
|
||||||
return <PostCard
|
return <PostCard
|
||||||
ref={passedMotionRef}
|
ref={passedMotionRef}
|
||||||
data={data}
|
{...props}
|
||||||
/>
|
/>
|
||||||
}}
|
}}
|
||||||
</CSSMotion>
|
</CSSMotion>
|
||||||
|
@ -128,6 +128,29 @@
|
|||||||
overflow : hidden;
|
overflow : hidden;
|
||||||
word-break : break-all;
|
word-break : break-all;
|
||||||
user-select: text;
|
user-select: text;
|
||||||
|
|
||||||
|
.additions {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.addition {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
// fixtures for media content
|
||||||
|
img {
|
||||||
|
width : 100%;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
video {
|
||||||
|
border-radius: 12px;
|
||||||
|
width : 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
audio {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
>div {
|
>div {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user