mirror of https://github.com/Qortal/q-mail
PhilReact
8 months ago
8 changed files with 8 additions and 4557 deletions
@ -0,0 +1,7 @@
|
||||
export interface IconTypes { |
||||
color?: string; |
||||
height: string; |
||||
width: string; |
||||
className?: string; |
||||
onClickFunc?: (e?: any) => void; |
||||
} |
@ -1,218 +0,0 @@
|
||||
import * as React from 'react' |
||||
import { styled, useTheme } from '@mui/material/styles' |
||||
import Box from '@mui/material/Box' |
||||
import Typography from '@mui/material/Typography' |
||||
|
||||
import AudiotrackIcon from '@mui/icons-material/Audiotrack' |
||||
import { MyContext } from '../wrappers/DownloadWrapper' |
||||
import { useDispatch, useSelector } from 'react-redux' |
||||
import { RootState } from '../state/store' |
||||
import { CircularProgress } from '@mui/material' |
||||
import { |
||||
setCurrAudio, |
||||
setShowingAudioPlayer |
||||
} from '../state/features/globalSlice' |
||||
|
||||
const Widget = styled('div')(({ theme }) => ({ |
||||
padding: 16, |
||||
borderRadius: 16, |
||||
maxWidth: '100%', |
||||
position: 'relative', |
||||
zIndex: 1, |
||||
// backgroundColor:
|
||||
// theme.palette.mode === 'dark' ? 'rgba(0,0,0,0.6)' : 'rgba(255,255,255,0.4)',
|
||||
backdropFilter: 'blur(40px)', |
||||
background: 'skyblue', |
||||
transition: '0.2s all', |
||||
'&:hover': { |
||||
opacity: 0.75 |
||||
} |
||||
})) |
||||
|
||||
const CoverImage = styled('div')({ |
||||
width: 100, |
||||
height: 100, |
||||
objectFit: 'cover', |
||||
overflow: 'hidden', |
||||
flexShrink: 0, |
||||
borderRadius: 8, |
||||
backgroundColor: 'rgba(0,0,0,0.08)', |
||||
'& > img': { |
||||
width: '100%' |
||||
} |
||||
}) |
||||
|
||||
const TinyText = styled(Typography)({ |
||||
fontSize: '0.75rem', |
||||
opacity: 0.38, |
||||
fontWeight: 500, |
||||
letterSpacing: 0.2 |
||||
}) |
||||
|
||||
interface IAudioElement { |
||||
onClick: () => void |
||||
title: string |
||||
description: string |
||||
author: string |
||||
audioInfo?: any |
||||
postId?: string |
||||
user?: string |
||||
} |
||||
|
||||
export default function AudioElement({ |
||||
onClick, |
||||
title, |
||||
description, |
||||
author, |
||||
audioInfo, |
||||
postId, |
||||
user |
||||
}: IAudioElement) { |
||||
const { downloadVideo } = React.useContext(MyContext) |
||||
const [isLoading, setIsLoading] = React.useState<boolean>(false) |
||||
const { downloads } = useSelector((state: RootState) => state.global) |
||||
const dispatch = useDispatch() |
||||
const download = React.useMemo(() => { |
||||
if (!downloads || !audioInfo?.identifier) return {} |
||||
const findDownload = downloads[audioInfo?.identifier] |
||||
|
||||
if (!findDownload) return {} |
||||
return findDownload |
||||
}, [downloads, audioInfo]) |
||||
|
||||
const resourceStatus = React.useMemo(() => { |
||||
return download?.status || {} |
||||
}, [download]) |
||||
const handlePlay = () => { |
||||
if (!postId) return |
||||
const { name, service, identifier } = audioInfo |
||||
|
||||
if (download && resourceStatus?.status === 'READY') { |
||||
dispatch(setShowingAudioPlayer(true)) |
||||
dispatch(setCurrAudio(identifier)) |
||||
return |
||||
} |
||||
setIsLoading(true) |
||||
downloadVideo({ |
||||
name, |
||||
service, |
||||
identifier, |
||||
blogPost: { |
||||
postId, |
||||
user, |
||||
audioTitle: title, |
||||
audioDescription: description, |
||||
audioAuthor: author |
||||
} |
||||
}) |
||||
dispatch(setCurrAudio(identifier)) |
||||
dispatch(setShowingAudioPlayer(true)) |
||||
} |
||||
|
||||
React.useEffect(() => { |
||||
if (resourceStatus?.status === 'READY') { |
||||
setIsLoading(false) |
||||
} |
||||
}, [resourceStatus]) |
||||
return ( |
||||
<Box |
||||
onClick={handlePlay} |
||||
sx={{ |
||||
width: '100%', |
||||
overflow: 'hidden', |
||||
position: 'relative', |
||||
cursor: 'pointer' |
||||
}} |
||||
> |
||||
<Widget> |
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}> |
||||
<CoverImage> |
||||
<AudiotrackIcon |
||||
sx={{ |
||||
width: '90%', |
||||
height: 'auto' |
||||
}} |
||||
/> |
||||
</CoverImage> |
||||
<Box sx={{ ml: 1.5, minWidth: 0 }}> |
||||
<Typography |
||||
variant="caption" |
||||
color="text.secondary" |
||||
fontWeight={500} |
||||
> |
||||
{author} |
||||
</Typography> |
||||
<Typography noWrap> |
||||
<b>{title}</b> |
||||
</Typography> |
||||
<Typography noWrap letterSpacing={-0.25}> |
||||
{description} |
||||
</Typography> |
||||
</Box> |
||||
</Box> |
||||
{((resourceStatus.status && resourceStatus?.status !== 'READY') || |
||||
isLoading) && ( |
||||
<Box |
||||
position="absolute" |
||||
top={0} |
||||
left={0} |
||||
right={0} |
||||
bottom={0} |
||||
display="flex" |
||||
justifyContent="center" |
||||
alignItems="center" |
||||
zIndex={4999} |
||||
bgcolor="rgba(0, 0, 0, 0.6)" |
||||
sx={{ |
||||
display: 'flex', |
||||
flexDirection: 'column', |
||||
gap: '10px', |
||||
padding: '16px', |
||||
borderRadius: '16px' |
||||
}} |
||||
> |
||||
<CircularProgress color="secondary" /> |
||||
{resourceStatus && ( |
||||
<Typography |
||||
variant="subtitle2" |
||||
component="div" |
||||
sx={{ |
||||
color: 'white', |
||||
fontSize: '14px' |
||||
}} |
||||
> |
||||
{resourceStatus?.status === 'REFETCHING' ? ( |
||||
<> |
||||
<> |
||||
{( |
||||
(resourceStatus?.localChunkCount / |
||||
resourceStatus?.totalChunkCount) * |
||||
100 |
||||
)?.toFixed(0)} |
||||
% |
||||
</> |
||||
|
||||
<> Refetching in 2 minutes</> |
||||
</> |
||||
) : resourceStatus?.status === 'DOWNLOADED' ? ( |
||||
<>Download Completed: building audio...</> |
||||
) : resourceStatus?.status !== 'READY' ? ( |
||||
<> |
||||
{( |
||||
(resourceStatus?.localChunkCount / |
||||
resourceStatus?.totalChunkCount) * |
||||
100 |
||||
)?.toFixed(0)} |
||||
% |
||||
</> |
||||
) : ( |
||||
<>Download Completed: fetching audio...</> |
||||
)} |
||||
</Typography> |
||||
)} |
||||
</Box> |
||||
)} |
||||
</Widget> |
||||
</Box> |
||||
) |
||||
} |
@ -1,492 +0,0 @@
|
||||
import React, { useContext, useMemo, useRef, useState } from 'react' |
||||
import ReactDOM from 'react-dom' |
||||
import { Box, IconButton, Slider } from '@mui/material' |
||||
import { CircularProgress, Typography } from '@mui/material' |
||||
|
||||
import { |
||||
PlayArrow, |
||||
Pause, |
||||
VolumeUp, |
||||
Fullscreen, |
||||
PictureInPicture |
||||
} from '@mui/icons-material' |
||||
import { styled } from '@mui/system' |
||||
import { MyContext } from '../../wrappers/DownloadWrapper' |
||||
import { useSelector } from 'react-redux' |
||||
import { RootState } from '../../state/store' |
||||
|
||||
const VideoContainer = styled(Box)` |
||||
position: relative; |
||||
display: flex; |
||||
flex-direction: column; |
||||
align-items: center; |
||||
justify-content: center; |
||||
width: 100%; |
||||
height: 100%; |
||||
margin: 0px; |
||||
padding: 0px; |
||||
` |
||||
|
||||
const VideoElement = styled('video')` |
||||
width: 100%; |
||||
height: auto; |
||||
background: rgb(33, 33, 33); |
||||
` |
||||
|
||||
const ControlsContainer = styled(Box)` |
||||
position: absolute; |
||||
display: flex; |
||||
align-items: center; |
||||
justify-content: space-between; |
||||
bottom: 0; |
||||
left: 0; |
||||
right: 0; |
||||
padding: 8px; |
||||
background-color: rgba(0, 0, 0, 0.6); |
||||
` |
||||
|
||||
interface VideoPlayerProps { |
||||
src?: string |
||||
poster?: string |
||||
name?: string |
||||
identifier?: string |
||||
service?: string |
||||
autoplay?: boolean |
||||
from?: string | null |
||||
setCount?: () => void |
||||
customStyle?: any |
||||
user?: string |
||||
postId?: string |
||||
} |
||||
|
||||
export const VideoPlayer: React.FC<VideoPlayerProps> = ({ |
||||
poster, |
||||
name, |
||||
identifier, |
||||
service, |
||||
autoplay = true, |
||||
from = null, |
||||
setCount, |
||||
customStyle = {}, |
||||
user = '', |
||||
postId = '' |
||||
}) => { |
||||
const videoRef = useRef<HTMLVideoElement | null>(null) |
||||
const [playing, setPlaying] = useState(false) |
||||
const [volume, setVolume] = useState(1) |
||||
const [progress, setProgress] = useState(0) |
||||
const [isLoading, setIsLoading] = useState(false) |
||||
const [startPlay, setStartPlay] = useState(false) |
||||
|
||||
const { downloads } = useSelector((state: RootState) => state.global) |
||||
|
||||
const download = useMemo(() => { |
||||
if (!downloads || !identifier) return {} |
||||
const findDownload = downloads[identifier] |
||||
|
||||
if (!findDownload) return {} |
||||
return findDownload |
||||
}, [downloads, identifier]) |
||||
|
||||
const src = useMemo(() => { |
||||
return download?.url || '' |
||||
}, [download?.url]) |
||||
const resourceStatus = useMemo(() => { |
||||
return download?.status || {} |
||||
}, [download]) |
||||
|
||||
const toggleRef = useRef<any>(null) |
||||
const { downloadVideo } = useContext(MyContext) |
||||
const togglePlay = async () => { |
||||
if (!videoRef.current) return |
||||
setStartPlay(true) |
||||
if (!src) { |
||||
const el = document.getElementById('videoWrapper') |
||||
if (el) { |
||||
el?.parentElement?.removeChild(el) |
||||
} |
||||
ReactDOM.flushSync(() => { |
||||
setIsLoading(true) |
||||
}) |
||||
getSrc() |
||||
} |
||||
if (playing) { |
||||
videoRef.current.pause() |
||||
} else { |
||||
videoRef.current.play() |
||||
} |
||||
setPlaying(!playing) |
||||
} |
||||
|
||||
const onVolumeChange = (_: any, value: number | number[]) => { |
||||
if (!videoRef.current) return |
||||
videoRef.current.volume = value as number |
||||
setVolume(value as number) |
||||
} |
||||
|
||||
const onProgressChange = (_: any, value: number | number[]) => { |
||||
if (!videoRef.current) return |
||||
videoRef.current.currentTime = value as number |
||||
setProgress(value as number) |
||||
if (!playing) { |
||||
videoRef.current.play() |
||||
setPlaying(true) |
||||
} |
||||
} |
||||
|
||||
const handleEnded = () => { |
||||
setPlaying(false) |
||||
} |
||||
|
||||
const updateProgress = () => { |
||||
if (!videoRef.current) return |
||||
setProgress(videoRef.current.currentTime) |
||||
} |
||||
|
||||
const [isFullscreen, setIsFullscreen] = useState(false) |
||||
|
||||
const enterFullscreen = () => { |
||||
if (!videoRef.current) return |
||||
if (videoRef.current.requestFullscreen) { |
||||
videoRef.current.requestFullscreen() |
||||
} |
||||
} |
||||
|
||||
const exitFullscreen = () => { |
||||
if (document.exitFullscreen) { |
||||
document.exitFullscreen() |
||||
} |
||||
} |
||||
|
||||
const toggleFullscreen = () => { |
||||
if (!isFullscreen) { |
||||
enterFullscreen() |
||||
} else { |
||||
exitFullscreen() |
||||
} |
||||
} |
||||
const togglePictureInPicture = async () => { |
||||
if (!videoRef.current) return |
||||
if (document.pictureInPictureElement === videoRef.current) { |
||||
await document.exitPictureInPicture() |
||||
} else { |
||||
await videoRef.current.requestPictureInPicture() |
||||
} |
||||
} |
||||
|
||||
React.useEffect(() => { |
||||
const handleFullscreenChange = () => { |
||||
setIsFullscreen(!!document.fullscreenElement) |
||||
} |
||||
|
||||
document.addEventListener('fullscreenchange', handleFullscreenChange) |
||||
return () => { |
||||
document.removeEventListener('fullscreenchange', handleFullscreenChange) |
||||
} |
||||
}, []) |
||||
|
||||
const handleLoadedMetadata = () => { |
||||
setIsLoading(false) |
||||
} |
||||
|
||||
const handleCanPlay = () => { |
||||
if (setCount) { |
||||
setCount() |
||||
} |
||||
setIsLoading(false) |
||||
} |
||||
|
||||
const getSrc = React.useCallback(async () => { |
||||
if (!name || !identifier || !service || !postId || !user) return |
||||
try { |
||||
downloadVideo({ |
||||
name, |
||||
service, |
||||
identifier, |
||||
blogPost: { |
||||
postId, |
||||
user |
||||
} |
||||
}) |
||||
} catch (error) {} |
||||
}, [identifier, name, service]) |
||||
|
||||
React.useEffect(() => { |
||||
const videoElement = videoRef.current |
||||
|
||||
const handleLeavePictureInPicture = async (event: any) => { |
||||
const target = event?.target |
||||
if (target) { |
||||
target.pause() |
||||
if (setPlaying) { |
||||
setPlaying(false) |
||||
} |
||||
} |
||||
} |
||||
|
||||
if (videoElement) { |
||||
videoElement.addEventListener( |
||||
'leavepictureinpicture', |
||||
handleLeavePictureInPicture |
||||
) |
||||
} |
||||
|
||||
return () => { |
||||
if (videoElement) { |
||||
videoElement.removeEventListener( |
||||
'leavepictureinpicture', |
||||
handleLeavePictureInPicture |
||||
) |
||||
} |
||||
} |
||||
}, []) |
||||
|
||||
React.useEffect(() => { |
||||
const videoElement = videoRef.current |
||||
|
||||
const minimizeVideo = async () => { |
||||
if (!videoElement) return |
||||
const handleClose = () => { |
||||
if (videoElement && videoElement.parentElement) { |
||||
const el = document.getElementById('videoWrapper') |
||||
if (el) { |
||||
el?.parentElement?.removeChild(el) |
||||
} |
||||
} |
||||
} |
||||
const createCloseButton = (): HTMLButtonElement => { |
||||
const closeButton = document.createElement('button') |
||||
closeButton.textContent = 'X' |
||||
closeButton.style.position = 'absolute' |
||||
closeButton.style.top = '0' |
||||
closeButton.style.right = '0' |
||||
closeButton.style.backgroundColor = 'rgba(255, 255, 255, 0.7)' |
||||
closeButton.style.border = 'none' |
||||
closeButton.style.fontWeight = 'bold' |
||||
closeButton.style.fontSize = '1.2rem' |
||||
closeButton.style.cursor = 'pointer' |
||||
closeButton.style.padding = '2px 8px' |
||||
closeButton.style.borderRadius = '0 0 0 4px' |
||||
|
||||
closeButton.addEventListener('click', handleClose) |
||||
|
||||
return closeButton |
||||
} |
||||
const buttonClose = createCloseButton() |
||||
const videoWrapper = document.createElement('div') |
||||
videoWrapper.id = 'videoWrapper' |
||||
videoWrapper.style.position = 'fixed' |
||||
videoWrapper.style.zIndex = '900000009' |
||||
videoWrapper.style.bottom = '0px' |
||||
videoWrapper.style.right = '0px' |
||||
|
||||
videoElement.parentElement?.insertBefore(videoWrapper, videoElement) |
||||
videoWrapper.appendChild(videoElement) |
||||
|
||||
videoWrapper.appendChild(buttonClose) |
||||
videoElement.controls = true |
||||
videoElement.style.height = 'auto' |
||||
videoElement.style.width = '300px' |
||||
|
||||
document.body.appendChild(videoWrapper) |
||||
} |
||||
|
||||
return () => { |
||||
if (videoElement) { |
||||
if (videoElement && !videoElement.paused && !videoElement.ended) { |
||||
minimizeVideo() |
||||
} |
||||
} |
||||
} |
||||
}, []) |
||||
|
||||
function formatTime(seconds: number): string { |
||||
seconds = Math.floor(seconds) |
||||
|
||||
let minutes: number | string = Math.floor(seconds / 60) |
||||
let remainingSeconds: number | string = seconds % 60 |
||||
|
||||
if (minutes < 10) { |
||||
minutes = '0' + minutes |
||||
} |
||||
if (remainingSeconds < 10) { |
||||
remainingSeconds = '0' + remainingSeconds |
||||
} |
||||
|
||||
return minutes + ':' + remainingSeconds |
||||
} |
||||
|
||||
return ( |
||||
<VideoContainer |
||||
style={{ |
||||
padding: from === 'create' ? '8px' : 0 |
||||
}} |
||||
> |
||||
{isLoading && ( |
||||
<Box |
||||
position="absolute" |
||||
top={0} |
||||
left={0} |
||||
right={0} |
||||
bottom={0} |
||||
display="flex" |
||||
justifyContent="center" |
||||
alignItems="center" |
||||
zIndex={4999} |
||||
bgcolor="rgba(0, 0, 0, 0.6)" |
||||
sx={{ |
||||
display: 'flex', |
||||
flexDirection: 'column', |
||||
gap: '10px' |
||||
}} |
||||
> |
||||
<CircularProgress color="secondary" /> |
||||
{resourceStatus && ( |
||||
<Typography |
||||
variant="subtitle2" |
||||
component="div" |
||||
sx={{ |
||||
color: 'white', |
||||
fontSize: '18px' |
||||
}} |
||||
> |
||||
{resourceStatus?.status === 'REFETCHING' ? ( |
||||
<> |
||||
<> |
||||
{( |
||||
(resourceStatus?.localChunkCount / |
||||
resourceStatus?.totalChunkCount) * |
||||
100 |
||||
)?.toFixed(0)} |
||||
% |
||||
</> |
||||
|
||||
<> Refetching in 2 minutes</> |
||||
</> |
||||
) : resourceStatus?.status === 'DOWNLOADED' ? ( |
||||
<>Download Completed: building video...</> |
||||
) : resourceStatus?.status !== 'READY' ? ( |
||||
<> |
||||
{( |
||||
(resourceStatus?.localChunkCount / |
||||
resourceStatus?.totalChunkCount) * |
||||
100 |
||||
)?.toFixed(0)} |
||||
% |
||||
</> |
||||
) : ( |
||||
<>Download Completed: fetching video...</> |
||||
)} |
||||
</Typography> |
||||
)} |
||||
</Box> |
||||
)} |
||||
{((!src && !isLoading) || !startPlay) && ( |
||||
<Box |
||||
position="absolute" |
||||
top={0} |
||||
left={0} |
||||
right={0} |
||||
bottom={0} |
||||
display="flex" |
||||
justifyContent="center" |
||||
alignItems="center" |
||||
zIndex={500} |
||||
bgcolor="rgba(0, 0, 0, 0.6)" |
||||
onClick={() => { |
||||
if (from === 'create') return |
||||
|
||||
togglePlay() |
||||
}} |
||||
sx={{ |
||||
cursor: 'pointer' |
||||
}} |
||||
> |
||||
<PlayArrow |
||||
sx={{ |
||||
width: '50px', |
||||
height: '50px', |
||||
color: 'white' |
||||
}} |
||||
/> |
||||
</Box> |
||||
)} |
||||
|
||||
<VideoElement |
||||
ref={videoRef} |
||||
src={!startPlay ? '' : src} |
||||
poster={poster} |
||||
onTimeUpdate={updateProgress} |
||||
autoPlay={autoplay} |
||||
onEnded={handleEnded} |
||||
// onLoadedMetadata={handleLoadedMetadata}
|
||||
onCanPlay={handleCanPlay} |
||||
preload="metadata" |
||||
style={{ |
||||
...customStyle |
||||
}} |
||||
/> |
||||
<ControlsContainer |
||||
style={{ |
||||
bottom: from === 'create' ? '15px' : 0 |
||||
}} |
||||
> |
||||
<IconButton |
||||
sx={{ |
||||
color: 'rgba(255, 255, 255, 0.7)' |
||||
}} |
||||
onClick={togglePlay} |
||||
> |
||||
{playing ? <Pause /> : <PlayArrow />} |
||||
</IconButton> |
||||
<Slider |
||||
value={progress} |
||||
onChange={onProgressChange} |
||||
min={0} |
||||
max={videoRef.current?.duration || 100} |
||||
sx={{ flexGrow: 1, mx: 2 }} |
||||
/> |
||||
<Typography |
||||
sx={{ |
||||
fontSize: '14px', |
||||
marginRight: '5px', |
||||
color: 'rgba(255, 255, 255, 0.7)', |
||||
visibility: |
||||
!videoRef.current?.duration || !progress ? 'hidden' : 'visible' |
||||
}} |
||||
> |
||||
{progress && videoRef.current?.duration && formatTime(progress)}/ |
||||
{progress && |
||||
videoRef.current?.duration && |
||||
formatTime(videoRef.current?.duration)} |
||||
</Typography> |
||||
<VolumeUp /> |
||||
<Slider |
||||
value={volume} |
||||
onChange={onVolumeChange} |
||||
min={0} |
||||
max={1} |
||||
step={0.01} |
||||
/> |
||||
<IconButton |
||||
sx={{ |
||||
color: 'rgba(255, 255, 255, 0.7)', |
||||
marginLeft: '15px' |
||||
}} |
||||
ref={toggleRef} |
||||
onClick={togglePictureInPicture} |
||||
> |
||||
<PictureInPicture /> |
||||
</IconButton> |
||||
<IconButton |
||||
sx={{ |
||||
color: 'rgba(255, 255, 255, 0.7)' |
||||
}} |
||||
onClick={toggleFullscreen} |
||||
> |
||||
<Fullscreen /> |
||||
</IconButton> |
||||
</ControlsContainer> |
||||
</VideoContainer> |
||||
) |
||||
} |
@ -1,855 +0,0 @@
|
||||
import React, { useMemo, useRef, useState } from 'react' |
||||
import { useParams } from 'react-router-dom' |
||||
import { |
||||
Button, |
||||
Box, |
||||
Typography, |
||||
CardHeader, |
||||
Avatar, |
||||
useTheme |
||||
} from '@mui/material' |
||||
import { useNavigate } from 'react-router-dom' |
||||
import { styled } from '@mui/system' |
||||
import AudiotrackIcon from '@mui/icons-material/Audiotrack' |
||||
import ReadOnlySlate from '../../components/editor/ReadOnlySlate' |
||||
import { useDispatch, useSelector } from 'react-redux' |
||||
import { RootState } from '../../state/store' |
||||
import { checkStructure } from '../../utils/checkStructure' |
||||
import { BlogContent } from '../../interfaces/interfaces' |
||||
import { |
||||
setAudio, |
||||
setCurrAudio, |
||||
setIsLoadingGlobal, |
||||
setVisitingBlog |
||||
} from '../../state/features/globalSlice' |
||||
import { VideoPlayer } from '../../components/common/VideoPlayer' |
||||
import { AudioPlayer, IPlaylist } from '../../components/common/AudioPlayer' |
||||
import { Responsive, WidthProvider } from 'react-grid-layout' |
||||
import '/node_modules/react-grid-layout/css/styles.css' |
||||
import '/node_modules/react-resizable/css/styles.css' |
||||
import DynamicHeightItem from '../../components/DynamicHeightItem' |
||||
import { |
||||
addPrefix, |
||||
buildIdentifierFromCreateTitleIdAndId |
||||
} from '../../utils/blogIdformats' |
||||
import { DynamicHeightItemMinimal } from '../../components/DynamicHeightItemMinimal' |
||||
import { ReusableModal } from '../../components/modals/ReusableModal' |
||||
import AudioElement from '../../components/AudioElement' |
||||
import ErrorBoundary from '../../components/common/ErrorBoundary' |
||||
import { CommentSection } from '../../components/common/Comments/CommentSection' |
||||
import { Tipping } from '../../components/common/Tipping/Tipping' |
||||
import FileElement from '../../components/FileElement' |
||||
const ResponsiveGridLayout = WidthProvider(Responsive) |
||||
const initialMinHeight = 2 // Define an initial minimum height for grid items
|
||||
|
||||
const md = [ |
||||
{ i: 'a', x: 0, y: 0, w: 4, h: initialMinHeight }, |
||||
{ i: 'b', x: 6, y: 0, w: 4, h: initialMinHeight } |
||||
] |
||||
const sm = [ |
||||
{ i: 'a', x: 0, y: 0, w: 6, h: initialMinHeight }, |
||||
{ i: 'b', x: 6, y: 0, w: 6, h: initialMinHeight } |
||||
] |
||||
const xs = [ |
||||
{ i: 'a', x: 0, y: 0, w: 6, h: initialMinHeight }, |
||||
{ i: 'b', x: 6, y: 0, w: 6, h: initialMinHeight } |
||||
] |
||||
|
||||
interface ILayoutGeneralSettings { |
||||
padding: number |
||||
blogPostType: string |
||||
} |
||||
export const BlogIndividualPost = () => { |
||||
const { user, postId, blog } = useParams() |
||||
const blogFull = React.useMemo(() => { |
||||
if (!blog) return '' |
||||
return addPrefix(blog) |
||||
}, [blog]) |
||||
const { user: userState } = useSelector((state: RootState) => state.auth) |
||||
const { audios, audioPostId } = useSelector( |
||||
(state: RootState) => state.global |
||||
) |
||||
|
||||
const [avatarUrl, setAvatarUrl] = React.useState<string>('') |
||||
const dispatch = useDispatch() |
||||
const navigate = useNavigate() |
||||
const theme = useTheme() |
||||
// const [currAudio, setCurrAudio] = React.useState<number | null>(null)
|
||||
const [layouts, setLayouts] = React.useState<any>({ md, sm, xs }) |
||||
const [count, setCount] = React.useState<number>(1) |
||||
const [layoutGeneralSettings, setLayoutGeneralSettings] = |
||||
React.useState<ILayoutGeneralSettings | null>(null) |
||||
const [currentBreakpoint, setCurrentBreakpoint] = React.useState<any>() |
||||
const handleLayoutChange = (layout: any, layoutss: any) => { |
||||
// const redoLayouts = setAutoHeight(layoutss)
|
||||
setLayouts(layoutss) |
||||
// saveLayoutsToLocalStorage(layoutss)
|
||||
} |
||||
const [blogContent, setBlogContent] = React.useState<BlogContent | null>(null) |
||||
const [isOpenSwitchPlaylistModal, setisOpenSwitchPlaylistModal] = |
||||
useState<boolean>(false) |
||||
const tempSaveAudio = useRef<any>(null) |
||||
const saveAudio = React.useRef<any>(null) |
||||
|
||||
const fullPostId = useMemo(() => { |
||||
if (!blog || !postId) return '' |
||||
dispatch(setIsLoadingGlobal(true)) |
||||
const formBlogId = addPrefix(blog) |
||||
const formPostId = buildIdentifierFromCreateTitleIdAndId(formBlogId, postId) |
||||
return formPostId |
||||
}, [blog, postId]) |
||||
const getBlogPost = React.useCallback(async () => { |
||||
try { |
||||
if (!blog || !postId) return |
||||
dispatch(setIsLoadingGlobal(true)) |
||||
const formBlogId = addPrefix(blog) |
||||
const formPostId = buildIdentifierFromCreateTitleIdAndId( |
||||
formBlogId, |
||||
postId |
||||
) |
||||
const url = `/arbitrary/BLOG_POST/${user}/${formPostId}` |
||||
const response = await fetch(url, { |
||||
method: 'GET', |
||||
headers: { |
||||
'Content-Type': 'application/json' |
||||
} |
||||
}) |
||||
|
||||
const responseData = await response.json() |
||||
|
||||
if (checkStructure(responseData)) { |
||||
setBlogContent(responseData) |
||||
if (responseData?.layouts) { |
||||
setLayouts(responseData?.layouts) |
||||
} |
||||
if (responseData?.layoutGeneralSettings) { |
||||
setLayoutGeneralSettings(responseData.layoutGeneralSettings) |
||||
} |
||||
const filteredAudios = (responseData?.postContent || []).filter( |
||||
(content: any) => content?.type === 'audio' |
||||
) |
||||
|
||||
const transformAudios = filteredAudios?.map((fa: any) => { |
||||
return { |
||||
...(fa?.content || {}), |
||||
id: fa?.id |
||||
} |
||||
}) |
||||
|
||||
if (!audios && transformAudios.length > 0) { |
||||
saveAudio.current = { audios: transformAudios, postId: formPostId } |
||||
dispatch(setAudio({ audios: transformAudios, postId: formPostId })) |
||||
} else if ( |
||||
formPostId === audioPostId && |
||||
audios?.length !== transformAudios.length |
||||
) { |
||||
tempSaveAudio.current = { |
||||
message: |
||||
"This post's audio playlist has updated. Would you like to switch?" |
||||
} |
||||
setisOpenSwitchPlaylistModal(true) |
||||
} |
||||
} |
||||
} catch (error) { |
||||
} finally { |
||||
dispatch(setIsLoadingGlobal(false)) |
||||
} |
||||
}, [user, postId, blog]) |
||||
React.useEffect(() => { |
||||
getBlogPost() |
||||
}, [postId]) |
||||
|
||||
const switchPlayList = () => { |
||||
const filteredAudios = (blogContent?.postContent || []).filter( |
||||
(content) => content?.type === 'audio' |
||||
) |
||||
|
||||
const formatAudios = filteredAudios.map((fa) => { |
||||
return { |
||||
...(fa?.content || {}), |
||||
id: fa?.id |
||||
} |
||||
}) |
||||
if (!blog || !postId) return |
||||
const formBlogId = addPrefix(blog) |
||||
const formPostId = buildIdentifierFromCreateTitleIdAndId(formBlogId, postId) |
||||
dispatch(setAudio({ audios: formatAudios, postId: formPostId })) |
||||
if (tempSaveAudio?.current?.currentSelection) { |
||||
const findIndex = (formatAudios || []).findIndex( |
||||
(item) => |
||||
item?.identifier === |
||||
tempSaveAudio?.current?.currentSelection?.content?.identifier |
||||
) |
||||
if (findIndex >= 0) { |
||||
dispatch(setCurrAudio(findIndex)) |
||||
} |
||||
} |
||||
setisOpenSwitchPlaylistModal(false) |
||||
} |
||||
|
||||
const getAvatar = React.useCallback(async () => { |
||||
try { |
||||
let url = await qortalRequest({ |
||||
action: 'GET_QDN_RESOURCE_URL', |
||||
name: user, |
||||
service: 'THUMBNAIL', |
||||
identifier: 'qortal_avatar' |
||||
}) |
||||
|
||||
setAvatarUrl(url) |
||||
} catch (error) {} |
||||
}, [user]) |
||||
React.useEffect(() => { |
||||
getAvatar() |
||||
}, []) |
||||
|
||||
const onBreakpointChange = React.useCallback((newBreakpoint: any) => { |
||||
setCurrentBreakpoint(newBreakpoint) |
||||
}, []) |
||||
|
||||
const onResizeStop = React.useCallback((layout: any, layoutItem: any) => { |
||||
// Update the layout state with the new position and size of the component
|
||||
setCount((prev) => prev + 1) |
||||
}, []) |
||||
|
||||
// const audios = React.useMemo<IPlaylist[]>(() => {
|
||||
// const filteredAudios = (blogContent?.postContent || []).filter(
|
||||
// (content) => content.type === 'audio'
|
||||
// )
|
||||
|
||||
// return filteredAudios.map((fa) => {
|
||||
// return {
|
||||
// ...fa.content,
|
||||
// id: fa.id
|
||||
// }
|
||||
// })
|
||||
// }, [blogContent])
|
||||
|
||||
const handleResize = () => { |
||||
setCount((prev) => prev + 1) |
||||
} |
||||
|
||||
React.useEffect(() => { |
||||
window.addEventListener('resize', handleResize) |
||||
|
||||
return () => { |
||||
window.removeEventListener('resize', handleResize) |
||||
} |
||||
}, []) |
||||
|
||||
const handleCount = React.useCallback(() => { |
||||
// Update the layout state with the new position and size of the component
|
||||
setCount((prev) => prev + 1) |
||||
}, []) |
||||
|
||||
const getBlog = React.useCallback(async () => { |
||||
let name = user |
||||
if (!name) return |
||||
if (!blogFull) return |
||||
try { |
||||
const urlBlog = `/arbitrary/BLOG/${name}/${blogFull}` |
||||
const response = await fetch(urlBlog, { |
||||
method: 'GET', |
||||
headers: { |
||||
'Content-Type': 'application/json' |
||||
} |
||||
}) |
||||
const responseData = await response.json() |
||||
dispatch(setVisitingBlog({ ...responseData, name })) |
||||
} catch (error) {} |
||||
}, [user, blogFull]) |
||||
|
||||
React.useEffect(() => { |
||||
getBlog() |
||||
}, [user, blogFull]) |
||||
|
||||
if (!blogContent) return null |
||||
|
||||
return ( |
||||
<Box |
||||
sx={{ |
||||
display: 'flex', |
||||
alignItems: 'center', |
||||
flexDirection: 'column' |
||||
}} |
||||
> |
||||
<Box |
||||
sx={{ |
||||
maxWidth: '1400px', |
||||
// margin: '15px',
|
||||
width: '95%', |
||||
paddingBottom: '50px' |
||||
}} |
||||
> |
||||
{user === userState?.name && ( |
||||
<Button |
||||
sx={{ backgroundColor: theme.palette.secondary.main }} |
||||
onClick={() => { |
||||
navigate(`/${user}/${blog}/${postId}/edit`) |
||||
}} |
||||
> |
||||
Edit Post |
||||
</Button> |
||||
)} |
||||
<Box |
||||
sx={{ |
||||
display: 'flex', |
||||
alignItems: 'center', |
||||
gap: 1 |
||||
}} |
||||
> |
||||
<CardHeader |
||||
onClick={() => { |
||||
navigate(`/${user}/${blog}`) |
||||
}} |
||||
sx={{ |
||||
cursor: 'pointer', |
||||
'& .MuiCardHeader-content': { |
||||
overflow: 'hidden' |
||||
}, |
||||
padding: '10px 0px' |
||||
}} |
||||
avatar={<Avatar src={avatarUrl} alt={`${user}'s avatar`} />} |
||||
subheader={ |
||||
<Typography |
||||
sx={{ fontFamily: 'Cairo', fontSize: '25px' }} |
||||
color={theme.palette.text.primary} |
||||
>{` ${user}`}</Typography> |
||||
} |
||||
/> |
||||
{user && ( |
||||
<Tipping |
||||
name={user || ''} |
||||
onSubmit={() => { |
||||
// setNameTip('')
|
||||
}} |
||||
onClose={() => { |
||||
// setNameTip('')
|
||||
}} |
||||
/> |
||||
)} |
||||
</Box> |
||||
<Box |
||||
sx={{ |
||||
display: 'flex', |
||||
gap: 1, |
||||
alignItems: 'center', |
||||
justifyContent: 'center' |
||||
}} |
||||
> |
||||
<Typography |
||||
variant="h1" |
||||
color="textPrimary" |
||||
sx={{ |
||||
textAlign: 'center' |
||||
}} |
||||
> |
||||
{blogContent?.title} |
||||
</Typography> |
||||
<CommentSection postId={fullPostId} /> |
||||
</Box> |
||||
|
||||
{(layoutGeneralSettings?.blogPostType === 'builder' || |
||||
!layoutGeneralSettings?.blogPostType) && ( |
||||
<Content |
||||
layouts={layouts} |
||||
blogContent={blogContent} |
||||
onResizeStop={onResizeStop} |
||||
onBreakpointChange={onBreakpointChange} |
||||
handleLayoutChange={handleLayoutChange} |
||||
> |
||||
{blogContent?.postContent?.map((section: any) => { |
||||
if (section?.type === 'editor') { |
||||
return ( |
||||
<div key={section?.id} className="grid-item-view"> |
||||
<ErrorBoundary |
||||
fallback={ |
||||
<Typography> |
||||
Error loading content: Invalid Data |
||||
</Typography> |
||||
} |
||||
> |
||||
<DynamicHeightItem |
||||
layouts={layouts} |
||||
setLayouts={setLayouts} |
||||
i={section.id} |
||||
breakpoint={currentBreakpoint} |
||||
count={count} |
||||
padding={layoutGeneralSettings?.padding} |
||||
> |
||||
<ReadOnlySlate content={section.content} /> |
||||
</DynamicHeightItem> |
||||
</ErrorBoundary> |
||||
</div> |
||||
) |
||||
} |
||||
if (section?.type === 'image') { |
||||
return ( |
||||
<div key={section?.id} className="grid-item-view"> |
||||
<ErrorBoundary |
||||
fallback={ |
||||
<Typography> |
||||
Error loading content: Invalid Data |
||||
</Typography> |
||||
} |
||||
> |
||||
<DynamicHeightItem |
||||
layouts={layouts} |
||||
setLayouts={setLayouts} |
||||
i={section.id} |
||||
breakpoint={currentBreakpoint} |
||||
count={count} |
||||
padding={layoutGeneralSettings?.padding} |
||||
> |
||||
<img |
||||
src={section.content.image} |
||||
className="post-image" |
||||
/> |
||||
</DynamicHeightItem> |
||||
</ErrorBoundary> |
||||
</div> |
||||
) |
||||
} |
||||
if (section?.type === 'video') { |
||||
return ( |
||||
<div key={section?.id} className="grid-item-view"> |
||||
<ErrorBoundary |
||||
fallback={ |
||||
<Typography> |
||||
Error loading content: Invalid Data |
||||
</Typography> |
||||
} |
||||
> |
||||
<DynamicHeightItem |
||||
layouts={layouts} |
||||
setLayouts={setLayouts} |
||||
i={section.id} |
||||
breakpoint={currentBreakpoint} |
||||
count={count} |
||||
padding={layoutGeneralSettings?.padding} |
||||
> |
||||
<VideoPlayer |
||||
name={section.content.name} |
||||
service={section.content.service} |
||||
identifier={section.content.identifier} |
||||
setCount={handleCount} |
||||
user={user} |
||||
postId={fullPostId} |
||||
/> |
||||
</DynamicHeightItem> |
||||
</ErrorBoundary> |
||||
</div> |
||||
) |
||||
} |
||||
if (section?.type === 'audio') { |
||||
return ( |
||||
<div key={section?.id} className="grid-item-view"> |
||||
<ErrorBoundary |
||||
fallback={ |
||||
<Typography> |
||||
Error loading content: Invalid Data |
||||
</Typography> |
||||
} |
||||
> |
||||
<DynamicHeightItem |
||||
layouts={layouts} |
||||
setLayouts={setLayouts} |
||||
i={section.id} |
||||
breakpoint={currentBreakpoint} |
||||
count={count} |
||||
padding={layoutGeneralSettings?.padding} |
||||
> |
||||
<AudioElement |
||||
key={section.id} |
||||
audioInfo={section.content} |
||||
postId={fullPostId} |
||||
user={user ? user : ''} |
||||
onClick={() => { |
||||
if (!blog || !postId) return |
||||
|
||||
const formBlogId = addPrefix(blog) |
||||
const formPostId = |
||||
buildIdentifierFromCreateTitleIdAndId( |
||||
formBlogId, |
||||
postId |
||||
) |
||||
if (audioPostId && formPostId !== audioPostId) { |
||||
tempSaveAudio.current = { |
||||
...(tempSaveAudio.current || {}), |
||||
currentSelection: section, |
||||
message: |
||||
'You are current on a playlist. Would you like to switch?' |
||||
} |
||||
setisOpenSwitchPlaylistModal(true) |
||||
} else { |
||||
if (!audios && saveAudio?.current) { |
||||
const findIndex = ( |
||||
saveAudio?.current?.audios || [] |
||||
).findIndex( |
||||
(item: any) => |
||||
item.identifier === |
||||
section.content.identifier |
||||
) |
||||
dispatch(setAudio(saveAudio?.current)) |
||||
dispatch(setCurrAudio(findIndex)) |
||||
return |
||||
} |
||||
|
||||
const findIndex = (audios || []).findIndex( |
||||
(item) => |
||||
item.identifier === section.content.identifier |
||||
) |
||||
if (findIndex >= 0) { |
||||
dispatch(setCurrAudio(findIndex)) |
||||
} |
||||
} |
||||
}} |
||||
title={section.content?.title} |
||||
description={section.content?.description} |
||||
author="" |
||||
/> |
||||
</DynamicHeightItem> |
||||
</ErrorBoundary> |
||||
</div> |
||||
) |
||||
} |
||||
if (section?.type === 'file') { |
||||
return ( |
||||
<div key={section?.id} className="grid-item"> |
||||
<ErrorBoundary |
||||
fallback={ |
||||
<Typography> |
||||
Error loading content: Invalid Data |
||||
</Typography> |
||||
} |
||||
> |
||||
<DynamicHeightItemMinimal |
||||
layouts={layouts} |
||||
setLayouts={setLayouts} |
||||
i={section.id} |
||||
breakpoint={currentBreakpoint} |
||||
count={count} |
||||
padding={0} |
||||
> |
||||
<FileElement |
||||
key={section.id} |
||||
fileInfo={section.content} |
||||
postId={fullPostId} |
||||
user={user ? user : ''} |
||||
title={section.content?.title} |
||||
description={section.content?.description} |
||||
mimeType={section.content?.mimeType} |
||||
author="" |
||||
/> |
||||
</DynamicHeightItemMinimal> |
||||
</ErrorBoundary> |
||||
</div> |
||||
) |
||||
} |
||||
})} |
||||
</Content> |
||||
)} |
||||
{layoutGeneralSettings?.blogPostType === 'minimal' && ( |
||||
<> |
||||
{layouts?.rows?.map((row: any, rowIndex: number) => { |
||||
return ( |
||||
<Box |
||||
sx={{ |
||||
display: 'flex', |
||||
width: '100%', |
||||
flexDirection: 'row', |
||||
alignItems: 'center', |
||||
justifyContent: 'center', |
||||
marginTop: '25px', |
||||
gap: 2 |
||||
}} |
||||
> |
||||
{row?.ids?.map((elementId: string) => { |
||||
const section: any = blogContent?.postContent?.find( |
||||
(el) => el?.id === elementId |
||||
) |
||||
if (!section) return null |
||||
if (section?.type === 'editor') { |
||||
return ( |
||||
<div |
||||
key={section?.id} |
||||
className="grid-item" |
||||
style={{ |
||||
maxWidth: '800px', |
||||
display: 'flex', |
||||
flexDirection: 'column', |
||||
width: '100%' |
||||
}} |
||||
> |
||||
<ErrorBoundary |
||||
fallback={ |
||||
<Typography> |
||||
Error loading content: Invalid Data |
||||
</Typography> |
||||
} |
||||
> |
||||
<DynamicHeightItemMinimal |
||||
layouts={layouts} |
||||
setLayouts={setLayouts} |
||||
i={section.id} |
||||
breakpoint={currentBreakpoint} |
||||
count={count} |
||||
padding={0} |
||||
> |
||||
<ReadOnlySlate |
||||
key={section.id} |
||||
content={section.content} |
||||
/> |
||||
</DynamicHeightItemMinimal> |
||||
</ErrorBoundary> |
||||
</div> |
||||
) |
||||
} |
||||
if (section?.type === 'image') { |
||||
return ( |
||||
<div key={section.id} className="grid-item"> |
||||
<ErrorBoundary |
||||
fallback={ |
||||
<Typography> |
||||
Error loading content: Invalid Data |
||||
</Typography> |
||||
} |
||||
> |
||||
<DynamicHeightItemMinimal |
||||
layouts={layouts} |
||||
setLayouts={setLayouts} |
||||
i={section.id} |
||||
breakpoint={currentBreakpoint} |
||||
count={count} |
||||
type="image" |
||||
padding={0} |
||||
> |
||||
<Box |
||||
sx={{ |
||||
position: 'relative', |
||||
width: '100%', |
||||
height: '100%' |
||||
}} |
||||
> |
||||
<img |
||||
src={section.content.image} |
||||
className="post-image" |
||||
style={{ |
||||
objectFit: 'contain', |
||||
maxHeight: '50vh' |
||||
}} |
||||
/> |
||||
</Box> |
||||
</DynamicHeightItemMinimal> |
||||
</ErrorBoundary> |
||||
</div> |
||||
) |
||||
} |
||||
|
||||
if (section?.type === 'video') { |
||||
return ( |
||||
<div key={section?.id} className="grid-item"> |
||||
<ErrorBoundary |
||||
fallback={ |
||||
<Typography> |
||||
Error loading content: Invalid Data |
||||
</Typography> |
||||
} |
||||
> |
||||
<DynamicHeightItemMinimal |
||||
layouts={layouts} |
||||
setLayouts={setLayouts} |
||||
i={section.id} |
||||
breakpoint={currentBreakpoint} |
||||
count={count} |
||||
padding={0} |
||||
> |
||||
<Box |
||||
sx={{ |
||||
position: 'relative', |
||||
width: '100%', |
||||
height: '100%' |
||||
}} |
||||
> |
||||
<VideoPlayer |
||||
name={section.content.name} |
||||
service={section.content.service} |
||||
identifier={section.content.identifier} |
||||
customStyle={{ |
||||
height: '50vh' |
||||
}} |
||||
user={user} |
||||
postId={fullPostId} |
||||
/> |
||||
</Box> |
||||
</DynamicHeightItemMinimal> |
||||
</ErrorBoundary> |
||||
</div> |
||||
) |
||||
} |
||||
if (section?.type === 'audio') { |
||||
return ( |
||||
<div key={section?.id} className="grid-item"> |
||||
<ErrorBoundary |
||||
fallback={ |
||||
<Typography> |
||||
Error loading content: Invalid Data |
||||
</Typography> |
||||
} |
||||
> |
||||
<DynamicHeightItemMinimal |
||||
layouts={layouts} |
||||
setLayouts={setLayouts} |
||||
i={section.id} |
||||
breakpoint={currentBreakpoint} |
||||
count={count} |
||||
padding={0} |
||||
> |
||||
<AudioElement |
||||
key={section.id} |
||||
audioInfo={section.content} |
||||
postId={fullPostId} |
||||
user={user ? user : ''} |
||||
onClick={() => { |
||||
if (!blog || !postId) return |
||||
const formBlogId = addPrefix(blog) |
||||
const formPostId = |
||||
buildIdentifierFromCreateTitleIdAndId( |
||||
formBlogId, |
||||
postId |
||||
) |
||||
if (formPostId !== audioPostId) { |
||||
tempSaveAudio.current = { |
||||
...(tempSaveAudio.current || {}), |
||||
currentSelection: section, |
||||
message: |
||||
'You are current on a playlist. Would you like to switch?' |
||||
} |
||||
setisOpenSwitchPlaylistModal(true) |
||||
} else { |
||||
const findIndex = (audios || []).findIndex( |
||||
(item) => |
||||
item.identifier === |
||||
section.content.identifier |
||||
) |
||||
if (findIndex >= 0) { |
||||
dispatch(setCurrAudio(findIndex)) |
||||
} |
||||
} |
||||
}} |
||||
title={section.content?.title} |
||||
description={section.content?.description} |
||||
author="" |
||||
/> |
||||
</DynamicHeightItemMinimal> |
||||
</ErrorBoundary> |
||||
</div> |
||||
) |
||||
} |
||||
if (section?.type === 'file') { |
||||
return ( |
||||
<div key={section?.id} className="grid-item"> |
||||
<ErrorBoundary |
||||
fallback={ |
||||
<Typography> |
||||
Error loading content: Invalid Data |
||||
</Typography> |
||||
} |
||||
> |
||||
<DynamicHeightItemMinimal |
||||
layouts={layouts} |
||||
setLayouts={setLayouts} |
||||
i={section.id} |
||||
breakpoint={currentBreakpoint} |
||||
count={count} |
||||
padding={0} |
||||
> |
||||
<FileElement |
||||
key={section.id} |
||||
fileInfo={section.content} |
||||
postId={fullPostId} |
||||
user={user ? user : ''} |
||||
title={section.content?.title} |
||||
description={section.content?.description} |
||||
mimeType={section.content?.mimeType} |
||||
author="" |
||||
/> |
||||
</DynamicHeightItemMinimal> |
||||
</ErrorBoundary> |
||||
</div> |
||||
) |
||||
} |
||||
})} |
||||
</Box> |
||||
) |
||||
})} |
||||
</> |
||||
)} |
||||
<ReusableModal open={isOpenSwitchPlaylistModal}> |
||||
<Box |
||||
sx={{ |
||||
display: 'flex', |
||||
alignItems: 'center', |
||||
gap: 1 |
||||
}} |
||||
> |
||||
<Typography> |
||||
{tempSaveAudio?.current?.message |
||||
? tempSaveAudio?.current?.message |
||||
: 'You are current on a playlist. Would you like to switch?'} |
||||
</Typography> |
||||
</Box> |
||||
<Button |
||||
variant="contained" |
||||
onClick={() => setisOpenSwitchPlaylistModal(false)} |
||||
> |
||||
Cancel |
||||
</Button> |
||||
<Button variant="contained" onClick={switchPlayList}> |
||||
Switch |
||||
</Button> |
||||
</ReusableModal> |
||||
</Box> |
||||
</Box> |
||||
) |
||||
} |
||||
|
||||
const Content = ({ |
||||
children, |
||||
layouts, |
||||
blogContent, |
||||
onResizeStop, |
||||
onBreakpointChange, |
||||
handleLayoutChange |
||||
}: any) => { |
||||
if (layouts && blogContent?.layouts) { |
||||
return ( |
||||
<ErrorBoundary |
||||
fallback={ |
||||
<Typography>Error loading content: Invalid Layout</Typography> |
||||
} |
||||
> |
||||
<ResponsiveGridLayout |
||||
layouts={layouts} |
||||
breakpoints={{ md: 996, sm: 768, xs: 480 }} |
||||
cols={{ md: 4, sm: 3, xs: 1 }} |
||||
measureBeforeMount={false} |
||||
onLayoutChange={handleLayoutChange} |
||||
autoSize={true} |
||||
compactType={null} |
||||
isBounded={true} |
||||
resizeHandles={['se', 'sw', 'ne', 'nw']} |
||||
rowHeight={25} |
||||
onResizeStop={onResizeStop} |
||||
onBreakpointChange={onBreakpointChange} |
||||
isDraggable={false} |
||||
isResizable={false} |
||||
margin={[0, 0]} |
||||
> |
||||
{children} |
||||
</ResponsiveGridLayout> |
||||
</ErrorBoundary> |
||||
) |
||||
} |
||||
return children |
||||
} |
@ -1,194 +0,0 @@
|
||||
import { Box, Button, Typography } from '@mui/material' |
||||
import React, { useMemo, useState } from 'react' |
||||
import { ReusableModal } from '../../components/modals/ReusableModal' |
||||
import { CreatePostBuilder } from './CreatePostBuilder' |
||||
import { CreatePostMinimal } from './CreatePostMinimal' |
||||
import HandymanRoundedIcon from '@mui/icons-material/HandymanRounded' |
||||
import HourglassFullRoundedIcon from '@mui/icons-material/HourglassFullRounded' |
||||
import { display } from '@mui/system' |
||||
import { useDispatch, useSelector } from 'react-redux' |
||||
import { setIsLoadingGlobal } from '../../state/features/globalSlice' |
||||
import { useParams } from 'react-router-dom' |
||||
import { checkStructure } from '../../utils/checkStructure' |
||||
import { RootState } from '../../state/store' |
||||
import { |
||||
addPrefix, |
||||
buildIdentifierFromCreateTitleIdAndId |
||||
} from '../../utils/blogIdformats' |
||||
import { Tipping } from '../../components/common/Tipping/Tipping' |
||||
type EditorType = 'minimal' | 'builder' |
||||
interface CreatePostProps { |
||||
mode?: string |
||||
} |
||||
export const CreatePost = ({ mode }: CreatePostProps) => { |
||||
const { user: username, postId, blog } = useParams() |
||||
const fullPostId = useMemo(() => { |
||||
if (!blog || !postId || mode !== 'edit') return '' |
||||
const formBlogId = addPrefix(blog) |
||||
const formPostId = buildIdentifierFromCreateTitleIdAndId(formBlogId, postId) |
||||
return formPostId |
||||
}, [blog, postId, mode]) |
||||
const { user } = useSelector((state: RootState) => state.auth) |
||||
|
||||
const [toggleEditorType, setToggleEditorType] = useState<EditorType | null>( |
||||
null |
||||
) |
||||
const [blogContentForEdit, setBlogContentForEdit] = useState<any>(null) |
||||
const [blogMetadataForEdit, setBlogMetadataForEdit] = useState<any>(null) |
||||
const [editType, setEditType] = useState<EditorType | null>(null) |
||||
const [isOpen, setIsOpen] = useState<boolean>(false) |
||||
const dispatch = useDispatch() |
||||
React.useEffect(() => { |
||||
if (!toggleEditorType && mode !== 'edit') { |
||||
setIsOpen(true) |
||||
} |
||||
}, [setIsOpen, toggleEditorType]) |
||||
|
||||
const switchType = () => { |
||||
setIsOpen(true) |
||||
} |
||||
|
||||
const getBlogPost = React.useCallback(async () => { |
||||
try { |
||||
dispatch(setIsLoadingGlobal(true)) |
||||
const url = `/arbitrary/BLOG_POST/${username}/${fullPostId}` |
||||
const response = await fetch(url, { |
||||
method: 'GET', |
||||
headers: { |
||||
'Content-Type': 'application/json' |
||||
} |
||||
}) |
||||
|
||||
const responseData = await response.json() |
||||
if (checkStructure(responseData)) { |
||||
// setNewPostContent(responseData.postContent)
|
||||
// setTitle(responseData?.title || '')
|
||||
// setBlogInfo(responseData)
|
||||
const blogType = responseData?.layoutGeneralSettings?.blogPostType |
||||
|
||||
if (blogType) { |
||||
setEditType(blogType) |
||||
setBlogContentForEdit(responseData) |
||||
} |
||||
//TODO - NAME SHOULD BE EXACT
|
||||
// const url2 = `/arbitrary/resources/search?mode=ALL&service=BLOG_POST&identifier=${fullPostId}&exactMatchNames=${username}&limit=1&includemetadata=true`
|
||||
const url2 = `/arbitrary/resources?service=BLOG_POST&identifier=${fullPostId}&name=${username}&limit=1&includemetadata=true` |
||||
|
||||
const responseBlogs = await fetch(url2, { |
||||
method: 'GET', |
||||
headers: { |
||||
'Content-Type': 'application/json' |
||||
} |
||||
}) |
||||
|
||||
const dataMetadata = await responseBlogs.json() |
||||
if (dataMetadata && dataMetadata.length > 0) { |
||||
setBlogMetadataForEdit(dataMetadata[0]) |
||||
} |
||||
} |
||||
} catch (error) { |
||||
} finally { |
||||
dispatch(setIsLoadingGlobal(false)) |
||||
} |
||||
}, [username, fullPostId]) |
||||
React.useEffect(() => { |
||||
if (mode === 'edit') { |
||||
getBlogPost() |
||||
} |
||||
}, [mode]) |
||||
|
||||
return ( |
||||
<> |
||||
{/* {toggleEditorType === 'minimal' && ( |
||||
<Button onClick={() => switchType()}>Switch to Builder</Button> |
||||
)} |
||||
{toggleEditorType === 'builder' && ( |
||||
<Button onClick={() => switchType()}>Switch to Minimal</Button> |
||||
)} */} |
||||
{isOpen && ( |
||||
<ReusableModal |
||||
open={isOpen} |
||||
customStyles={{ |
||||
maxWidth: '500px' |
||||
}} |
||||
> |
||||
{toggleEditorType && ( |
||||
<Typography> |
||||
Switching editor type will delete your current progress |
||||
</Typography> |
||||
)} |
||||
|
||||
<Box |
||||
sx={{ |
||||
display: 'flex', |
||||
justifyContent: 'center', |
||||
alignItems: 'center', |
||||
gap: 2 |
||||
}} |
||||
> |
||||
<Box |
||||
onClick={() => { |
||||
setToggleEditorType('minimal') |
||||
setIsOpen(false) |
||||
}} |
||||
sx={{ |
||||
display: 'flex', |
||||
flexDirection: 'column', |
||||
justifyContent: 'center', |
||||
alignItems: 'center', |
||||
padding: '20px', |
||||
borderRadius: '6px', |
||||
border: '1px solid', |
||||
cursor: 'pointer' |
||||
}} |
||||
> |
||||
<Typography>Minimal Editor</Typography> |
||||
<HourglassFullRoundedIcon /> |
||||
</Box> |
||||
<Box |
||||
onClick={() => { |
||||
setToggleEditorType('builder') |
||||
setIsOpen(false) |
||||
}} |
||||
sx={{ |
||||
display: 'flex', |
||||
flexDirection: 'column', |
||||
justifyContent: 'center', |
||||
alignItems: 'center', |
||||
padding: '20px', |
||||
borderRadius: '6px', |
||||
border: '1px solid', |
||||
cursor: 'pointer' |
||||
}} |
||||
> |
||||
<Typography>Builder Editor</Typography> |
||||
<HandymanRoundedIcon /> |
||||
</Box> |
||||
</Box> |
||||
<Button onClick={() => setIsOpen(false)}>Close</Button> |
||||
</ReusableModal> |
||||
)} |
||||
|
||||
{toggleEditorType === 'minimal' && ( |
||||
<CreatePostMinimal switchType={switchType} /> |
||||
)} |
||||
{toggleEditorType === 'builder' && ( |
||||
<CreatePostBuilder switchType={switchType} /> |
||||
)} |
||||
{mode === 'edit' && editType === 'minimal' && ( |
||||
<CreatePostMinimal |
||||
blogContentForEdit={blogContentForEdit} |
||||
postIdForEdit={fullPostId} |
||||
blogMetadataForEdit={blogMetadataForEdit} |
||||
/> |
||||
)} |
||||
{mode === 'edit' && editType === 'builder' && ( |
||||
<CreatePostBuilder |
||||
blogContentForEdit={blogContentForEdit} |
||||
postIdForEdit={fullPostId} |
||||
blogMetadataForEdit={blogMetadataForEdit} |
||||
/> |
||||
)} |
||||
</> |
||||
) |
||||
} |
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue