improve attachment render behavior

This commit is contained in:
SrGooglo 2022-12-12 10:38:30 +00:00
parent 330b1b9cec
commit 15214cff3f
4 changed files with 160 additions and 151 deletions

View File

@ -1,4 +1,5 @@
import React from "react" import React from "react"
import { Skeleton } from "antd"
import loadable from "@loadable/component" import loadable from "@loadable/component"
import { Carousel } from "react-responsive-carousel" import { Carousel } from "react-responsive-carousel"
import Plyr from "plyr-react" import Plyr from "plyr-react"
@ -28,96 +29,139 @@ import "react-responsive-carousel/lib/styles/carousel.min.css"
import "plyr-react/dist/plyr.css" import "plyr-react/dist/plyr.css"
import "./index.less" import "./index.less"
export default class PostAttachments extends React.PureComponent { const Attachment = React.memo((props) => {
getAttachments = (data) => { const { url, id, name } = props.attachment
return data.map((addition, index) => {
if (typeof addition === "string") {
addition = {
url: addition,
}
}
const { url, id, name } = addition const [loaded, setLoaded] = React.useState(false)
const MediaRender = loadable(async () => { const [mediaType, setMediaType] = React.useState(null)
let extension = null const [mimeType, setMimeType] = React.useState(null)
try { const getMediaType = async () => {
// get media type by parsing the url let extension = null
const mediaTypeExt = /\.([a-zA-Z0-9]+)$/.exec(url)
if (mediaTypeExt) { // get media type by parsing the url
extension = mediaTypeExt[1] const mediaTypeExt = /\.([a-zA-Z0-9]+)$/.exec(url)
} else {
// try to get media by creating requesting the url
const response = await fetch(url, {
method: "HEAD",
})
extension = response.headers.get("content-type").split("/")[1] if (mediaTypeExt) {
} extension = mediaTypeExt[1]
} else {
extension = extension.toLowerCase() // try to get media by creating requesting the url
const response = await fetch(url, {
const mediaType = mediaTypes[extension] method: "HEAD",
const mimeType = `${mediaType}/${extension}`
if (!mediaType) {
return () => <ContentFailed />
}
switch (mediaType.split("/")[0]) {
case "image": {
return () => <img src={url} />
}
case "video": {
return () => <Plyr
source={{
type: "video",
sources: [{
src: url,
}],
}}
options={{
controls: ["play", "progress", "current-time", "mute", "volume"],
}}
/>
}
case "audio": {
return () => <audio controls>
<source src={url} type={mimeType} />
</audio>
}
default: {
return () => <h4>
Unsupported media type [{mediaType}/{mediaTypeExt}]
</h4>
}
}
} catch (error) {
console.error(error)
return () => <ContentFailed />
}
}) })
return <div key={index} className="addition"> extension = response.headers.get("content-type").split("/")[1]
<React.Suspense fallback={<div>Loading</div>} > }
<MediaRender />
</React.Suspense> extension = extension.toLowerCase()
</div>
}) const mediaType = mediaTypes[extension]
const mimeType = `${mediaType}/${extension}`
setMediaType(mediaType)
setMimeType(mimeType)
setLoaded(true)
} }
render() { const renderMedia = () => {
return <div className="post_attachments"> switch (mediaType.split("/")[0]) {
<Carousel case "image": {
showArrows={true} return <img src={url} />
showStatus={false} }
showThumbs={false} case "video": {
showIndicators={this.props.attachments?.length > 1 ?? false} return <Plyr
> source={{
{this.getAttachments(this.props.attachments)} type: "video",
</Carousel> sources: [{
</div> src: url,
}],
}}
options={{
controls: ["play", "progress", "current-time", "mute", "volume"],
}}
/>
}
case "audio": {
return <audio controls>
<source src={url} type={mimeType} />
</audio>
}
default: {
return <h4>
Unsupported media type [{mediaType}/{mediaTypeExt}]
</h4>
}
}
} }
React.useEffect(() => {
getMediaType()
}, [])
if (!loaded) {
return <Skeleton active />
}
if (loaded && !mediaType && !mimeType) {
return <ContentFailed />
}
return <div className="attachment" id={id}>
{renderMedia()}
</div>
})
export default (props) => {
const carouselRef = React.useRef(null)
const [attachmentIndex, setAttachmentIndex] = React.useState(0)
const handleAttachmentChange = (index) => {
const currentAttachmentIndex = carouselRef.current.state.selectedItem
const currentAttachment = carouselRef.current.itemsRef[currentAttachmentIndex].querySelector("video, audio")
if (currentAttachmentIndex !== index) {
// if the attachment is a video, pause it
if (currentAttachment) {
currentAttachment.pause()
}
} else {
// else if the attachment is a video, play it
if (currentAttachment) {
currentAttachment.play()
}
}
setAttachmentIndex(index)
}
React.useEffect(() => {
// get attachment index from query string
const attachmentIndex = parseInt(new URLSearchParams(window.location.search).get("attachment"))
if (attachmentIndex) {
setAttachmentIndex(attachmentIndex)
}
}, [])
return <div className="post_attachments">
<Carousel
ref={carouselRef}
showArrows={true}
showStatus={false}
showThumbs={false}
showIndicators={props.attachments?.length > 1 ?? false}
selectedItem={attachmentIndex}
onChange={handleAttachmentChange}
transitionTime={150}
stopOnHover={true}
>
{
props.attachments.map((attachment, index) => {
return <Attachment key={index} attachment={attachment} />
})
}
</Carousel>
</div>
} }

View File

@ -19,8 +19,13 @@
width: 100%; width: 100%;
.carousel { .carousel {
display: flex;
background-color: black; background-color: black;
border-radius: 8px; border-radius: 8px;
.slider-wrapper {
align-self: center;
}
} }
.control-prev, .control-prev,
@ -82,7 +87,7 @@
} }
} }
.addition { .attachment {
width: 100%; width: 100%;
height: 100%; height: 100%;

View File

@ -5,19 +5,13 @@ import classnames from "classnames"
import { processString } from "utils" import { processString } from "utils"
import { Icons } from "components/Icons"
import PostAttachments from "../attachments"
import "./index.less" import "./index.less"
export default React.memo((props) => { export default (props) => {
let { message, attachments, type, data, flags } = props.data let { message, data } = props.data
const [nsfwAccepted, setNsfwAccepted] = React.useState(false) const [nsfwAccepted, setNsfwAccepted] = React.useState(false)
const isNSFW = flags?.includes("nsfw")
if (typeof data === "string") { if (typeof data === "string") {
try { try {
data = JSON.parse(data) data = JSON.parse(data)
@ -27,12 +21,6 @@ export default React.memo((props) => {
} }
} }
const onClickPlaylist = () => {
if (data.playlist) {
app.AudioPlayer.startPlaylist(data.playlist)
}
}
// parse message // parse message
const regexs = [ const regexs = [
{ {
@ -63,57 +51,17 @@ export default React.memo((props) => {
message = processString(regexs)(message) message = processString(regexs)(message)
const renderContent = () => {
switch (type) {
case "playlist": {
return <>
<div
className="playlistCover"
onClick={onClickPlaylist}
style={{
backgroundImage: `url(${data?.cover ?? "/assets/no_song.png"})`,
}}
/>
<div className="playlistTitle">
<div>
<h1>
{data.title ?? "Untitled Playlist"}
</h1>
<h3>
{data.artist}
</h3>
</div>
<h4>
{message}
</h4>
<div className="actions">
<antd.Button onClick={onClickPlaylist}>
<Icons.PlayCircle />
Play
</antd.Button>
</div>
</div>
</>
}
default: {
return <>
<div className="message">
{message}
</div>
{attachments.length > 0 && <PostAttachments attachments={attachments} />}
</>
}
}
}
return <div return <div
className={classnames("post_content", { ["nsfw"]: isNSFW && !nsfwAccepted })} className={
classnames(
"post_content",
{
["nsfw"]: props.nsfw && !nsfwAccepted
}
)
}
> >
{isNSFW && !nsfwAccepted && {props.nsfw && !nsfwAccepted &&
<div className="nsfw_alert"> <div className="nsfw_alert">
<h2> <h2>
This post may contain sensitive content. This post may contain sensitive content.
@ -125,6 +73,12 @@ export default React.memo((props) => {
</div> </div>
} }
{renderContent()} <div className="message">
{message}
</div>
{
props.children
}
</div> </div>
}) }

View File

@ -7,10 +7,11 @@ import { Icons } from "components/Icons"
import PostHeader from "./components/header" import PostHeader from "./components/header"
import PostContent from "./components/content" import PostContent from "./components/content"
import PostActions from "./components/actions" import PostActions from "./components/actions"
import PostAttachments from "./components/attachments"
import "./index.less" import "./index.less"
export default React.memo(({ export default ({
expansibleActions = window.app.settings.get("postCard_expansible_actions"), expansibleActions = window.app.settings.get("postCard_expansible_actions"),
autoCarrousel = window.app.settings.get("postCard_carrusel_auto"), autoCarrousel = window.app.settings.get("postCard_carrusel_auto"),
data = {}, data = {},
@ -160,7 +161,12 @@ export default React.memo(({
autoCarrousel={autoCarrousel} autoCarrousel={autoCarrousel}
fullmode={fullmode} fullmode={fullmode}
onDoubleClick={onDoubleClick} onDoubleClick={onDoubleClick}
/> nsfw={data.flags && data.flags.includes("nsfw")}
>
{data.attachments && data.attachments.length > 0 && <PostAttachments
attachments={data.attachments}
/>}
</PostContent>
</div> </div>
{!fullmode && {!fullmode &&
<div className="post_actionsIndicator"> <div className="post_actionsIndicator">
@ -181,4 +187,4 @@ export default React.memo(({
/> />
} }
</div> </div>
}) }