Browse Source

remove files

version1
PhilReact 8 months ago
parent
commit
8a6f803c12
  1. 7
      src/assets/svgs/IconTypes.ts
  2. 218
      src/components/AudioElement.tsx
  3. 2
      src/components/common/TextEditor/utils.ts
  4. 492
      src/components/common/VideoPlayer.tsx
  5. 855
      src/pages/BlogIndividualPost/BlogIndividualPost.tsx
  6. 194
      src/pages/CreatePost/CreatePost.tsx
  7. 1408
      src/pages/CreatePost/CreatePostBuilder.tsx
  8. 1389
      src/pages/CreatePost/CreatePostMinimal.tsx

7
src/assets/svgs/IconTypes.ts

@ -0,0 +1,7 @@
export interface IconTypes {
color?: string;
height: string;
width: string;
className?: string;
onClickFunc?: (e?: any) => void;
}

218
src/components/AudioElement.tsx

@ -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>
)
}

2
src/components/common/TextEditor/utils.ts

@ -1,4 +1,4 @@
export function convertQortalLinks(inputHtml) {
export function convertQortalLinks(inputHtml:string) {
// Regular expression to match 'qortal://...' URLs.
// This will stop at the first whitespace, comma, or HTML tag
var regex = /(qortal:\/\/[^\s,<]+)/g;

492
src/components/common/VideoPlayer.tsx

@ -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>
)
}

855
src/pages/BlogIndividualPost/BlogIndividualPost.tsx

@ -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
}

194
src/pages/CreatePost/CreatePost.tsx

@ -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}
/>
)}
</>
)
}

1408
src/pages/CreatePost/CreatePostBuilder.tsx

File diff suppressed because it is too large Load Diff

1389
src/pages/CreatePost/CreatePostMinimal.tsx

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save