diff --git a/src/assets/svgs/IconTypes.ts b/src/assets/svgs/IconTypes.ts new file mode 100644 index 0000000..e11b2aa --- /dev/null +++ b/src/assets/svgs/IconTypes.ts @@ -0,0 +1,7 @@ +export interface IconTypes { + color?: string; + height: string; + width: string; + className?: string; + onClickFunc?: (e?: any) => void; +} diff --git a/src/components/AudioElement.tsx b/src/components/AudioElement.tsx deleted file mode 100644 index 2271122..0000000 --- a/src/components/AudioElement.tsx +++ /dev/null @@ -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(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 ( - - - - - - - - - {author} - - - {title} - - - {description} - - - - {((resourceStatus.status && resourceStatus?.status !== 'READY') || - isLoading) && ( - - - {resourceStatus && ( - - {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... - )} - - )} - - )} - - - ) -} diff --git a/src/components/common/TextEditor/utils.ts b/src/components/common/TextEditor/utils.ts index 76ca5fe..073459a 100644 --- a/src/components/common/TextEditor/utils.ts +++ b/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; diff --git a/src/components/common/VideoPlayer.tsx b/src/components/common/VideoPlayer.tsx deleted file mode 100644 index 4cdd2ef..0000000 --- a/src/components/common/VideoPlayer.tsx +++ /dev/null @@ -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 = ({ - poster, - name, - identifier, - service, - autoplay = true, - from = null, - setCount, - customStyle = {}, - user = '', - postId = '' -}) => { - const videoRef = useRef(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(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 ( - - {isLoading && ( - - - {resourceStatus && ( - - {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... - )} - - )} - - )} - {((!src && !isLoading) || !startPlay) && ( - { - if (from === 'create') return - - togglePlay() - }} - sx={{ - cursor: 'pointer' - }} - > - - - )} - - - - - {playing ? : } - - - - {progress && videoRef.current?.duration && formatTime(progress)}/ - {progress && - videoRef.current?.duration && - formatTime(videoRef.current?.duration)} - - - - - - - - - - - - ) -} diff --git a/src/pages/BlogIndividualPost/BlogIndividualPost.tsx b/src/pages/BlogIndividualPost/BlogIndividualPost.tsx deleted file mode 100644 index 57d148f..0000000 --- a/src/pages/BlogIndividualPost/BlogIndividualPost.tsx +++ /dev/null @@ -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('') - const dispatch = useDispatch() - const navigate = useNavigate() - const theme = useTheme() - // const [currAudio, setCurrAudio] = React.useState(null) - const [layouts, setLayouts] = React.useState({ md, sm, xs }) - const [count, setCount] = React.useState(1) - const [layoutGeneralSettings, setLayoutGeneralSettings] = - React.useState(null) - const [currentBreakpoint, setCurrentBreakpoint] = React.useState() - const handleLayoutChange = (layout: any, layoutss: any) => { - // const redoLayouts = setAutoHeight(layoutss) - setLayouts(layoutss) - // saveLayoutsToLocalStorage(layoutss) - } - const [blogContent, setBlogContent] = React.useState(null) - const [isOpenSwitchPlaylistModal, setisOpenSwitchPlaylistModal] = - useState(false) - const tempSaveAudio = useRef(null) - const saveAudio = React.useRef(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(() => { - // 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 ( - - - {user === userState?.name && ( - - )} - - { - navigate(`/${user}/${blog}`) - }} - sx={{ - cursor: 'pointer', - '& .MuiCardHeader-content': { - overflow: 'hidden' - }, - padding: '10px 0px' - }} - avatar={} - subheader={ - {` ${user}`} - } - /> - {user && ( - { - // setNameTip('') - }} - onClose={() => { - // setNameTip('') - }} - /> - )} - - - - {blogContent?.title} - - - - - {(layoutGeneralSettings?.blogPostType === 'builder' || - !layoutGeneralSettings?.blogPostType) && ( - - {blogContent?.postContent?.map((section: any) => { - if (section?.type === 'editor') { - return ( -
- - Error loading content: Invalid Data - - } - > - - - - -
- ) - } - if (section?.type === 'image') { - return ( -
- - Error loading content: Invalid Data - - } - > - - - - -
- ) - } - if (section?.type === 'video') { - return ( -
- - Error loading content: Invalid Data - - } - > - - - - -
- ) - } - if (section?.type === 'audio') { - return ( -
- - Error loading content: Invalid Data - - } - > - - { - 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="" - /> - - -
- ) - } - if (section?.type === 'file') { - return ( -
- - Error loading content: Invalid Data - - } - > - - - - -
- ) - } - })} -
- )} - {layoutGeneralSettings?.blogPostType === 'minimal' && ( - <> - {layouts?.rows?.map((row: any, rowIndex: number) => { - return ( - - {row?.ids?.map((elementId: string) => { - const section: any = blogContent?.postContent?.find( - (el) => el?.id === elementId - ) - if (!section) return null - if (section?.type === 'editor') { - return ( -
- - Error loading content: Invalid Data - - } - > - - - - -
- ) - } - if (section?.type === 'image') { - return ( -
- - Error loading content: Invalid Data - - } - > - - - - - - -
- ) - } - - if (section?.type === 'video') { - return ( -
- - Error loading content: Invalid Data - - } - > - - - - - - -
- ) - } - if (section?.type === 'audio') { - return ( -
- - Error loading content: Invalid Data - - } - > - - { - 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="" - /> - - -
- ) - } - if (section?.type === 'file') { - return ( -
- - Error loading content: Invalid Data - - } - > - - - - -
- ) - } - })} -
- ) - })} - - )} - - - - {tempSaveAudio?.current?.message - ? tempSaveAudio?.current?.message - : 'You are current on a playlist. Would you like to switch?'} - - - - - -
-
- ) -} - -const Content = ({ - children, - layouts, - blogContent, - onResizeStop, - onBreakpointChange, - handleLayoutChange -}: any) => { - if (layouts && blogContent?.layouts) { - return ( - Error loading content: Invalid Layout - } - > - - {children} - - - ) - } - return children -} diff --git a/src/pages/CreatePost/CreatePost.tsx b/src/pages/CreatePost/CreatePost.tsx deleted file mode 100644 index e66783d..0000000 --- a/src/pages/CreatePost/CreatePost.tsx +++ /dev/null @@ -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( - null - ) - const [blogContentForEdit, setBlogContentForEdit] = useState(null) - const [blogMetadataForEdit, setBlogMetadataForEdit] = useState(null) - const [editType, setEditType] = useState(null) - const [isOpen, setIsOpen] = useState(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' && ( - - )} - {toggleEditorType === 'builder' && ( - - )} */} - {isOpen && ( - - {toggleEditorType && ( - - Switching editor type will delete your current progress - - )} - - - { - setToggleEditorType('minimal') - setIsOpen(false) - }} - sx={{ - display: 'flex', - flexDirection: 'column', - justifyContent: 'center', - alignItems: 'center', - padding: '20px', - borderRadius: '6px', - border: '1px solid', - cursor: 'pointer' - }} - > - Minimal Editor - - - { - setToggleEditorType('builder') - setIsOpen(false) - }} - sx={{ - display: 'flex', - flexDirection: 'column', - justifyContent: 'center', - alignItems: 'center', - padding: '20px', - borderRadius: '6px', - border: '1px solid', - cursor: 'pointer' - }} - > - Builder Editor - - - - - - )} - - {toggleEditorType === 'minimal' && ( - - )} - {toggleEditorType === 'builder' && ( - - )} - {mode === 'edit' && editType === 'minimal' && ( - - )} - {mode === 'edit' && editType === 'builder' && ( - - )} - - ) -} diff --git a/src/pages/CreatePost/CreatePostBuilder.tsx b/src/pages/CreatePost/CreatePostBuilder.tsx deleted file mode 100644 index 7dbc14c..0000000 --- a/src/pages/CreatePost/CreatePostBuilder.tsx +++ /dev/null @@ -1,1408 +0,0 @@ -import React, { useCallback, useEffect } from 'react' - -import BlogEditor from '../../components/editor/BlogEditor' -import ShortUniqueId from 'short-unique-id' -import ReadOnlySlate from '../../components/editor/ReadOnlySlate' -import { useDispatch, useSelector } from 'react-redux' -import { RootState } from '../../state/store' -import TextField from '@mui/material/TextField' -import AddPhotoAlternateIcon from '@mui/icons-material/AddPhotoAlternate' -import ImageUploader from '../../components/common/ImageUploader' -import AudiotrackIcon from '@mui/icons-material/Audiotrack' -import DeleteIcon from '@mui/icons-material/Delete' -import { Button, Box, useTheme } from '@mui/material' -import { styled } from '@mui/system' -import { Descendant } from 'slate' -import EditIcon from '@mui/icons-material/Edit' -import { extractTextFromSlate } from '../../utils/extractTextFromSlate' -import { setNotification } from '../../state/features/notificationsSlice' -import { VideoPanel } from '../../components/common/VideoPanel' -import PostPublishModal from '../../components/common/PostPublishModal' -import DynamicHeightItem from '../../components/DynamicHeightItem' -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 { ReusableModal } from '../../components/modals/ReusableModal' -import { VideoPlayer } from '../../components/common/VideoPlayer' -import { EditorToolbar } from './components/Toolbar/EditorToolbar' -import { Navbar } from './components/Navbar/NavbarBuilder' -import { UserNavbar } from '../../components/common/UserNavbar/UserNavbar' -import { setCurrentBlog } from '../../state/features/globalSlice' -import AudioElement from '../../components/AudioElement' -import { AudioPanel } from '../../components/common/AudioPanel' -import { - addPostToBeginning, - addToHashMap, - updateInHashMap, - updatePost -} from '../../state/features/blogSlice' -import { removePrefix } from '../../utils/blogIdformats' -import { useNavigate } from 'react-router-dom' -import { BuilderButton } from './CreatePost-styles' -import FileElement from '../../components/FileElement' -const ResponsiveGridLayout = WidthProvider(Responsive) -const initialMinHeight = 2 // Define an initial minimum height for grid items -const uid = new ShortUniqueId() -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 } -] -const initialValue: Descendant[] = [ - { - type: 'paragraph', - children: [{ text: '' }] - } -] - -const BlogTitleInput = styled(TextField)(({ theme }) => ({ - marginBottom: '15px', - '& .MuiInputBase-input': { - fontSize: '28px', - height: '28px', - background: 'transparent', - '&::placeholder': { - fontSize: '28px', - color: theme.palette.text.primary - } - }, - '& .MuiInputLabel-root': { - fontSize: '28px' - }, - '& .MuiInputBase-root': { - background: 'transparent', - '&:hover': { - background: 'transparent' - }, - '&.Mui-focused': { - background: 'transparent' - } - }, - '& .MuiOutlinedInput-root': { - '&:hover .MuiOutlinedInput-notchedOutline': { - borderColor: theme.palette.primary.main - }, - '&.Mui-focused .MuiOutlinedInput-notchedOutline': { - borderColor: theme.palette.primary.main - } - } -})) -interface CreatePostBuilderProps { - blogContentForEdit?: any - postIdForEdit?: string - blogMetadataForEdit?: any - switchType?: () => void -} - -export const CreatePostBuilder = ({ - blogContentForEdit, - postIdForEdit, - blogMetadataForEdit, - switchType -}: CreatePostBuilderProps) => { - const navigate = useNavigate() - - const theme = useTheme() - const { user } = useSelector((state: RootState) => state.auth) - const { currentBlog } = useSelector((state: RootState) => state.global) - const [editingSection, setEditingSection] = React.useState(null) - const [layouts, setLayouts] = React.useState({ md, sm, xs }) - const [currentBreakpoint, setCurrentBreakpoint] = React.useState() - const handleLayoutChange = (layout: any, layoutss: any) => { - setLayouts(layoutss) - } - const [newPostContent, setNewPostContent] = React.useState([]) - const [title, setTitle] = React.useState('') - const [isOpenPostModal, setIsOpenPostModal] = React.useState(false) - const [isOpenEditTextModal, setIsOpenEditTextModal] = - React.useState(false) - const [value, setValue] = React.useState(initialValue) - const [editorKey, setEditorKey] = React.useState(1) - const [count, setCount] = React.useState(1) - const [isOpenAddTextModal, setIsOpenAddTextModal] = - React.useState(false) - const [paddingValue, onChangePadding] = React.useState(5) - const [isEditNavOpen, setIsEditNavOpen] = React.useState(false) - const dispatch = useDispatch() - const [navbarConfig, setNavbarConfig] = React.useState(null) - const addPostSection = React.useCallback((content: any) => { - const section = { - type: 'editor', - version: 1, - content, - id: uid() - } - - setNewPostContent((prev) => [...prev, section]) - setEditorKey((prev) => prev + 1) - }, []) - - async function getBlog(name: string, identifier: string, blog: any) { - const urlBlog = `/arbitrary/BLOG/${name}/${identifier}` - const response = await fetch(urlBlog, { - method: 'GET', - headers: { - 'Content-Type': 'application/json' - } - }) - const responseData = await response.json() - dispatch( - setCurrentBlog({ - createdAt: responseData?.createdAt || '', - blogId: blog.identifier, - title: responseData?.title || '', - description: responseData?.description || '', - blogImage: responseData?.blogImage || '', - category: blog.metadata?.category, - tags: blog.metadata?.tags || [], - navbarConfig: responseData?.navbarConfig || null - }) - ) - } - - useEffect(() => { - if (blogContentForEdit && postIdForEdit && blogMetadataForEdit) { - setTitle(blogContentForEdit?.title || '') - setLayouts( - blogContentForEdit?.layouts || { - rows: [] - } - ) - setNewPostContent(blogContentForEdit?.postContent || []) - onChangePadding(blogContentForEdit?.layoutGeneralSettings?.padding || 5) - } - }, [blogContentForEdit, postIdForEdit, blogMetadataForEdit]) - - const editBlog = React.useCallback( - async (navbarConfig: any) => { - if (!user || !user.name) - throw new Error('Cannot update: your Qortal name is not accessible') - - if (!currentBlog) - throw new Error('Your blog is not available. Refresh and try again.') - - const name = user.name - const formattedTags: { [key: string]: string } = {} - const tags = currentBlog?.tags || [] - const category = currentBlog?.category || '' - const title = currentBlog?.title || '' - const description = currentBlog?.description || '' - tags.forEach((tag: string, i: number) => { - formattedTags[`tag${i + 1}`] = tag - }) - - const blogProps: any = { - ...currentBlog, - navbarConfig - } - - const blogPostToBase64 = await objectToBase64(blogProps) - try { - const resourceResponse = await qortalRequest({ - action: 'PUBLISH_QDN_RESOURCE', - name: name, - service: 'BLOG', - data64: blogPostToBase64, - title, - description, - category, - ...formattedTags, - identifier: currentBlog.blogId - }) - - await new Promise((res, rej) => { - setTimeout(() => { - res() - }, 1000) - }) - - // getBlog(name, currentBlog.blogId, currentBlog) - dispatch(setCurrentBlog(blogProps)) - dispatch( - setNotification({ - msg: 'Blog successfully updated', - alertType: 'success' - }) - ) - } catch (error: any) { - let notificationObj: any = null - if (typeof error === 'string') { - notificationObj = { - msg: error || 'Failed to save blog', - alertType: 'error' - } - } else if (typeof error?.error === 'string') { - notificationObj = { - msg: error?.error || 'Failed to save blog', - alertType: 'error' - } - } else { - notificationObj = { - msg: error?.message || 'Failed to save blog', - alertType: 'error' - } - } - if (!notificationObj) return - dispatch(setNotification(notificationObj)) - if (error instanceof Error) { - throw new Error(error.message) - } else { - throw new Error('An unknown error occurred') - } - } - }, - [user, currentBlog] - ) - - const handleSaveNavBar = useCallback( - async (navMenu: any, navbarConfig: any) => { - try { - const config = { - type: 'topNav', - title: '', - logo: '', - ...navbarConfig, - navItems: navMenu - } - await editBlog(config) - setIsEditNavOpen(false) - setNavbarConfig(config) - } catch (error: any) { - dispatch( - setNotification({ - msg: error?.message || 'Could not save the navbar', - alertType: 'error' - }) - ) - } - }, - [] - ) - - const handleRemoveNavBar = useCallback(async () => { - try { - await editBlog(null) - setNavbarConfig(null) - setIsEditNavOpen(false) - } catch (error: any) { - dispatch( - setNotification({ - msg: error?.message || 'Could not save the navbar', - alertType: 'error' - }) - ) - } - }, []) - - function objectToBase64(obj: any) { - // Step 1: Convert the object to a JSON string - const jsonString = JSON.stringify(obj) - - // Step 2: Create a Blob from the JSON string - const blob = new Blob([jsonString], { type: 'application/json' }) - - // Step 3: Create a FileReader to read the Blob as a base64-encoded string - return new Promise((resolve, reject) => { - const reader = new FileReader() - reader.onloadend = () => { - if (typeof reader.result === 'string') { - // Remove 'data:application/json;base64,' prefix - const base64 = reader.result.replace( - 'data:application/json;base64,', - '' - ) - resolve(base64) - } else { - reject( - new Error('Failed to read the Blob as a base64-encoded string') - ) - } - } - reader.onerror = () => { - reject(reader.error) - } - reader.readAsDataURL(blob) - }) - } - - const description = React.useMemo(() => { - let description = '' - const findText = newPostContent.find((data) => data?.type === 'editor') - if (findText && findText.content) { - description = extractTextFromSlate(findText?.content) - description = description.slice(0, 180) - } - return description - }, [newPostContent]) - const post = React.useMemo(() => { - return { - description, - title - } - }, [title, description]) - - async function publishQDNResource(params: any) { - let address - let name - let errorMsg = '' - - address = user?.address - name = user?.name || '' - - const missingFields = [] - if (!address) { - errorMsg = "Cannot post: your address isn't available" - } - if (!name) { - errorMsg = 'Cannot post without a name' - } - if (!title) missingFields.push('title') - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', ') - const errMsg = `Missing: ${missingFieldsString}` - errorMsg = errMsg - } - if (newPostContent.length === 0) { - errorMsg = 'Your post has no content' - } - - if (!currentBlog) { - errorMsg = 'Cannot publish without first creating a blog.' - } - - if (errorMsg) { - dispatch( - setNotification({ - msg: errorMsg, - alertType: 'error' - }) - ) - throw new Error(errorMsg) - } - - const layoutGeneralSettings = { - padding: paddingValue ?? 0, - blogPostType: 'builder' - } - - const postObject = { - title, - createdAt: Date.now(), - postContent: newPostContent, - layouts, - layoutGeneralSettings - } - try { - if (!currentBlog) return - const id = uid() - let createTitleId = title - .replace(/[^a-zA-Z0-9\s-]/g, '') - .replace(/\s+/g, '-') - .replace(/-+/g, '-') - .trim() - - if (createTitleId.toLowerCase().includes('post')) { - createTitleId = createTitleId.replace(/post/gi, '') - } - if (createTitleId.toLowerCase().includes('q-blog')) { - createTitleId = createTitleId.replace(/q-blog/gi, '') - } - - if (createTitleId.endsWith('-')) { - createTitleId = createTitleId.slice(0, -1) - } - if (createTitleId.startsWith('-')) { - createTitleId = createTitleId.slice(1) - } - const identifier = `${currentBlog.blogId}-post-${createTitleId}-${id}` - const blogPostToBase64 = await objectToBase64(postObject) - let description = '' - const findText = newPostContent.find((data) => data?.type === 'editor') - if (findText && findText.content) { - description = extractTextFromSlate(findText?.content) - description = description.slice(0, 180) - } - - let requestBody: any = { - action: 'PUBLISH_QDN_RESOURCE', - name: name, - service: 'BLOG_POST', - data64: blogPostToBase64, - title: title, - description: params?.description || description, - category: params?.category || '', - identifier: identifier - } - - const formattedTags: { [key: string]: string } = {} - let tag4 = '' - let tag5 = '' - if (params?.tags) { - params.tags.slice(0, 3).forEach((tag: string, i: number) => { - formattedTags[`tag${i + 1}`] = tag - }) - } - - const findVideo: any = postObject?.postContent?.find( - (data: any) => data?.type === 'video' - ) - const findAudio: any = postObject?.postContent?.find( - (data: any) => data?.type === 'audio' - ) - const findImage: any = postObject?.postContent?.find( - (data: any) => data?.type === 'image' - ) - - const tag5Array = ['t'] - if (findVideo) tag5Array.push('v') - if (findAudio) tag5Array.push('a') - if (findImage) { - tag5Array.push('i') - const imageElement = document.querySelector( - `[id="${findImage.id}"] img` - ) as HTMLImageElement | null - if (imageElement) { - tag4 = `v1.${imageElement?.width}x${imageElement?.height}` - } else { - tag4 = 'v1.0x0' - } - } - if (!findImage) { - tag4 = 'v1.0x0' - } - tag5 = tag5Array.join(', ') - requestBody = { - ...requestBody, - ...formattedTags, - tag4: tag4, - tag5: tag5 - } - - const resourceResponse = await qortalRequest(requestBody) - - const postobj: any = { - ...postObject, - title: title, - description: params?.description || description, - category: params?.category || '', - tags: [...(params?.tags || []), tag4, tag5], - id: identifier, - user: name, - postImage: findImage ? findImage?.content?.image : '' - } - - const withoutImage = { ...postobj } - delete withoutImage.postImage - dispatch(addPostToBeginning(withoutImage)) - dispatch(addToHashMap(postobj)) - dispatch( - setNotification({ - msg: 'Blog post successfully published', - alertType: 'success' - }) - ) - const str = identifier - const arr = str.split('-post-') - const str1 = arr[0] - const str2 = arr[1] - const blogId = removePrefix(str1) - navigate(`/${name}/${blogId}/${str2}`) - } catch (error: any) { - let notificationObj = null - if (typeof error === 'string') { - notificationObj = { - msg: error || 'Failed to publish post', - alertType: 'error' - } - } else if (typeof error?.error === 'string') { - notificationObj = { - msg: error?.error || 'Failed to publish post', - alertType: 'error' - } - } else { - notificationObj = { - msg: error?.message || 'Failed to publish post', - alertType: 'error' - } - } - if (!notificationObj) return - dispatch(setNotification(notificationObj)) - - throw new Error('Failed to publish post') - } - } - - async function updateQDNResource(params: any) { - if (!blogContentForEdit || !postIdForEdit || !blogMetadataForEdit) return - let address - let name - let errorMsg = '' - - address = user?.address - name = user?.name || '' - - const missingFields = [] - if (!address) { - errorMsg = "Cannot post: your address isn't available" - } - if (!name) { - errorMsg = 'Cannot post without a name' - } - if (!title) missingFields.push('title') - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', ') - const errMsg = `Missing: ${missingFieldsString}` - errorMsg = errMsg - } - if (newPostContent.length === 0) { - errorMsg = 'Your post has no content' - } - - if (!currentBlog) { - errorMsg = 'Cannot publish without first creating a blog.' - } - - if (errorMsg) { - dispatch( - setNotification({ - msg: errorMsg, - alertType: 'error' - }) - ) - throw new Error(errorMsg) - } - - const layoutGeneralSettings = { - padding: paddingValue ?? 0, - blogPostType: 'builder' - } - - const postObject = { - ...blogContentForEdit, - title, - postContent: newPostContent, - layouts, - layoutGeneralSettings - } - try { - if (!currentBlog) return - - const identifier = postIdForEdit - const blogPostToBase64 = await objectToBase64(postObject) - let description = '' - const findText = newPostContent.find((data) => data?.type === 'editor') - if (findText && findText.content) { - description = extractTextFromSlate(findText?.content) - description = description.slice(0, 180) - } - - let requestBody: any = { - action: 'PUBLISH_QDN_RESOURCE', - name: name, - service: 'BLOG_POST', - data64: blogPostToBase64, - title: title, - description: params?.description || description, - category: params?.category || '', - identifier: identifier - } - - const formattedTags: { [key: string]: string } = {} - let tag4 = '' - let tag5 = '' - if (params?.tags) { - params.tags.slice(0, 3).forEach((tag: string, i: number) => { - formattedTags[`tag${i + 1}`] = tag - }) - } - - const findVideo: any = postObject?.postContent?.find( - (data: any) => data?.type === 'video' - ) - const findAudio: any = postObject?.postContent?.find( - (data: any) => data?.type === 'audio' - ) - const findImage: any = postObject?.postContent?.find( - (data: any) => data?.type === 'image' - ) - - const tag5Array = ['t'] - if (findVideo) tag5Array.push('v') - if (findAudio) tag5Array.push('a') - if (findImage) { - tag5Array.push('i') - const imageElement = document.querySelector( - `[id="${findImage.id}"] img` - ) as HTMLImageElement | null - if (imageElement) { - tag4 = `v1.${imageElement?.width}x${imageElement?.height}` - } else { - tag4 = 'v1.0x0' - } - } - if (!findImage) { - tag4 = 'v1.0x0' - } - tag5 = tag5Array.join(', ') - requestBody = { - ...requestBody, - ...formattedTags, - tag4: tag4, - tag5: tag5 - } - - const resourceResponse = await qortalRequest(requestBody) - const postobj = { - ...postObject, - title: title, - description: params?.description || description, - category: params?.category || '', - tags: [...(params?.tags || []), tag4, tag5], - id: identifier, - user: name - } - const withoutImage = { ...postobj } - delete withoutImage.postImage - dispatch(updatePost(withoutImage)) - dispatch(updateInHashMap(postobj)) - dispatch( - setNotification({ - msg: 'Blog post successfully updated', - alertType: 'success' - }) - ) - } catch (error: any) { - let notificationObj = null - if (typeof error === 'string') { - notificationObj = { - msg: error || 'Failed to update post', - alertType: 'error' - } - } else if (typeof error?.error === 'string') { - notificationObj = { - msg: error?.error || 'Failed to update post', - alertType: 'error' - } - } else { - notificationObj = { - msg: error?.message || 'Failed to update post', - alertType: 'error' - } - } - if (!notificationObj) return - dispatch(setNotification(notificationObj)) - - throw new Error('Failed to update post') - } - } - const addImage = (base64: string) => { - const section = { - type: 'image', - version: 1, - content: { - image: base64, - caption: '' - }, - id: uid() - } - setNewPostContent((prev) => [...prev, section]) - } - - interface IaddVideo { - name: string - identifier: string - service: string - title: string - description: string - mimeType?: string - } - - const addVideo = ({ - name, - identifier, - service, - title, - description - }: IaddVideo) => { - const section = { - type: 'video', - version: 1, - content: { - name: name, - identifier: identifier, - service: service, - title, - description - }, - id: uid() - } - setNewPostContent((prev) => [...prev, section]) - } - - const addAudio = ({ - name, - identifier, - service, - title, - description - }: IaddVideo) => { - const section = { - type: 'audio', - version: 1, - content: { - name: name, - identifier: identifier, - service: service, - title, - description - }, - id: uid() - } - setNewPostContent((prev) => [...prev, section]) - } - - const addFile = ({ - name, - identifier, - service, - title, - description, - mimeType - }: IaddVideo) => { - const id = uid() - const type = 'file' - const section = { - type, - version: 1, - content: { - name: name, - identifier: identifier, - service: service, - title, - description, - mimeType - }, - id - } - setNewPostContent((prev) => [...prev, section]) - } - - const addSection = () => { - addPostSection(value) - setValue(initialValue) - } - - const removeSection = (section: any) => { - const newContent = newPostContent.filter((s) => s.id !== section.id) - setNewPostContent(newContent) - } - const editImage = (base64: string, section: any) => { - const newSection = { - ...section, - content: { - image: base64, - caption: section.content.caption - } - } - const findSectionIndex = newPostContent.findIndex( - (s) => s.id === section.id - ) - if (findSectionIndex !== -1) { - const copyNewPostContent = [...newPostContent] - copyNewPostContent[findSectionIndex] = newSection - - setNewPostContent(copyNewPostContent) - } - } - const editVideo = ( - { name, identifier, service, description, title }: IaddVideo, - section: any - ) => { - const newSection = { - ...section, - content: { - name: name, - identifier: identifier, - service: service, - description, - title - } - } - const findSectionIndex = newPostContent.findIndex( - (s) => s.id === section.id - ) - if (findSectionIndex !== -1) { - const copyNewPostContent = [...newPostContent] - copyNewPostContent[findSectionIndex] = newSection - - setNewPostContent(copyNewPostContent) - } - } - const editAudio = ( - { name, identifier, service, description, title }: IaddVideo, - section: any - ) => { - const newSection = { - ...section, - content: { - name: name, - identifier: identifier, - service: service, - description, - title - } - } - const findSectionIndex = newPostContent.findIndex( - (s) => s.id === section.id - ) - if (findSectionIndex !== -1) { - const copyNewPostContent = [...newPostContent] - copyNewPostContent[findSectionIndex] = newSection - - setNewPostContent(copyNewPostContent) - } - } - const editSection = (section: any) => { - setIsOpenEditTextModal(true) - setEditingSection(section) - setValue(section.content) - } - const editPostSection = React.useCallback( - (content: any, section: any) => { - const findSectionIndex = newPostContent.findIndex( - (s) => s.id === section.id - ) - if (findSectionIndex !== -1) { - const copyNewPostContent = [...newPostContent] - copyNewPostContent[findSectionIndex] = { - ...section, - content - } - - setNewPostContent(copyNewPostContent) - } - - setEditingSection(null) - setIsOpenEditTextModal(false) - }, - [newPostContent] - ) - - const onSelectVideo = React.useCallback((video: any) => { - addVideo({ - name: video.name, - identifier: video.identifier, - service: video.service, - title: video?.metadata?.title, - description: video?.metadata?.description - }) - }, []) - - const onSelectAudio = React.useCallback((video: any) => { - addAudio({ - name: video.name, - identifier: video.identifier, - service: video.service, - title: video?.metadata?.title, - description: video?.metadata?.description - }) - }, []) - - const onBreakpointChange = (newBreakpoint: any) => { - setCurrentBreakpoint(newBreakpoint) - } - - const onSelectFile = React.useCallback((video: any) => { - addFile({ - name: video.name, - identifier: video.identifier, - service: video.service, - title: video?.metadata?.title, - description: video?.metadata?.description, - mimeType: video?.metadata?.mimeType - }) - }, []) - - const closeAddTextModal = React.useCallback(() => { - setIsOpenAddTextModal(false) - }, []) - - const closeEditTextModal = React.useCallback(() => { - setIsOpenEditTextModal(false) - setEditingSection(null) - }, []) - - const onResizeStop = (layout: any, layoutItem: any) => { - setCount((prev) => prev + 1) - } - const handleResize = () => { - setCount((prev) => prev + 1) - } - - React.useEffect(() => { - window.addEventListener('resize', handleResize) - - return () => { - window.removeEventListener('resize', handleResize) - } - }, []) - - const gridItemCount = - currentBreakpoint === 'md' ? 4 : currentBreakpoint === 'sm' ? 3 : 1 - - const addNav = () => { - setIsEditNavOpen(true) - } - return ( - <> - - {/* {navbarConfig && Array.isArray(navbarConfig?.navItems) && ( - - )} */} - - setTitle(e.target.value)} - fullWidth - placeholder="Title" - variant="filled" - multiline - maxRows={2} - InputLabelProps={{ shrink: false }} - sx={{ maxWidth: '700px' }} - /> - -
- {Array.from({ length: gridItemCount }, (_, i) => ( -
- ))} -
- - {newPostContent.map((section: any) => { - if (section.type === 'editor') { - return ( -
- - {editingSection && - editingSection.id === section.id ? null : ( - - - - removeSection(section)} - sx={{ - cursor: 'pointer', - height: '18px', - width: 'auto' - }} - /> - editSection(section)} - sx={{ - cursor: 'pointer', - height: '18px', - width: 'auto' - }} - /> - - - )} - -
- ) - } - if (section.type === 'image') { - return ( -
- - - - - removeSection(section)} - sx={{ - cursor: 'pointer', - height: '18px', - width: 'auto' - }} - /> - editImage(base64, section)} - > - - - - - -
- ) - } - - if (section.type === 'video') { - return ( -
- - - - - removeSection(section)} - sx={{ - cursor: 'pointer', - height: '18px', - width: 'auto' - }} - /> - - editVideo( - { - name: video.name, - identifier: video.identifier, - service: video.service, - title: video?.metadata?.title, - description: video?.metadata?.description - }, - section - ) - } - /> - - - -
- ) - } - if (section.type === 'audio') { - return ( -
- - - {}} - title={section.content?.title} - description={section.content?.description} - author="" - /> - - removeSection(section)} - sx={{ - cursor: 'pointer', - height: '18px', - width: 'auto' - }} - /> - - editAudio( - { - name: audio.name, - identifier: audio.identifier, - service: audio.service, - title: audio?.metadata?.title, - description: audio?.metadata?.description - }, - section - ) - } - /> - - - -
- ) - } - if (section.type === 'file') { - return ( -
- - - - - - removeSection(section)} - sx={{ - cursor: 'pointer', - height: '18px', - width: 'auto' - }} - /> - - - -
- ) - } - })} -
- - - - -
- - - - - Add Text - Close - - - - - - editPostSection(value, editingSection)}> - Update Text - - Close - - - setIsEditNavOpen(false)} - /> - - - {!blogContentForEdit && ( - { - setIsOpenPostModal(false) - }} - open={isOpenPostModal} - post={post} - onPublish={publishQDNResource} - /> - )} - - {blogContentForEdit && blogMetadataForEdit?.metadata && ( - { - setIsOpenPostModal(false) - }} - open={isOpenPostModal} - post={post} - onPublish={updateQDNResource} - mode="edit" - metadata={blogMetadataForEdit?.metadata} - /> - )} -
- - ) -} - -export const EditButtons = ({ children }: any) => { - const theme = useTheme() - return ( - - {children} - - ) -} diff --git a/src/pages/CreatePost/CreatePostMinimal.tsx b/src/pages/CreatePost/CreatePostMinimal.tsx deleted file mode 100644 index 897cb47..0000000 --- a/src/pages/CreatePost/CreatePostMinimal.tsx +++ /dev/null @@ -1,1389 +0,0 @@ -import React, { useEffect } from 'react' -import BlogEditor from '../../components/editor/BlogEditor' -import ShortUniqueId from 'short-unique-id' -import ReadOnlySlate from '../../components/editor/ReadOnlySlate' -import { useDispatch, useSelector } from 'react-redux' -import { RootState } from '../../state/store' -import TextField from '@mui/material/TextField' -import AddPhotoAlternateIcon from '@mui/icons-material/AddPhotoAlternate' -import ImageUploader from '../../components/common/ImageUploader' -import AudiotrackIcon from '@mui/icons-material/Audiotrack' -import AddCircleRoundedIcon from '@mui/icons-material/AddCircleRounded' -import { Button, Box, useTheme } from '@mui/material' -import { styled } from '@mui/system' -import { Descendant } from 'slate' -import EditIcon from '@mui/icons-material/Edit' -import RemoveCircleIcon from '@mui/icons-material/RemoveCircle' -import { extractTextFromSlate } from '../../utils/extractTextFromSlate' -import { setNotification } from '../../state/features/notificationsSlice' -import { VideoPanel } from '../../components/common/VideoPanel' -import PostPublishModal from '../../components/common/PostPublishModal' -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 { ReusableModal } from '../../components/modals/ReusableModal' -import { VideoPlayer } from '../../components/common/VideoPlayer' -import { EditorToolbar } from './components/Toolbar/EditorToolbar' -import { DynamicHeightItemMinimal } from '../../components/DynamicHeightItemMinimal' -import AudioElement from '../../components/AudioElement' -import DeleteIcon from '@mui/icons-material/Delete' - -import { AudioPanel } from '../../components/common/AudioPanel' -import { EditButtons } from './CreatePostBuilder' -import { - addPostToBeginning, - addToHashMap, - updateInHashMap, - updatePost -} from '../../state/features/blogSlice' -import { useNavigate } from 'react-router-dom' -import { removePrefix } from '../../utils/blogIdformats' -import { BuilderButton } from './CreatePost-styles' -import FileElement from '../../components/FileElement' -const ResponsiveGridLayout = WidthProvider(Responsive) -const initialMinHeight = 2 // Define an initial minimum height for grid items -const uid = new ShortUniqueId() -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 } -] -const initialValue: Descendant[] = [ - { - type: 'paragraph', - children: [{ text: '' }] - } -] - -const BlogTitleInput = styled(TextField)(({ theme }) => ({ - '& .MuiInputBase-input': { - fontSize: '28px', - height: '28px', - background: 'transparent', - '&::placeholder': { - fontSize: '28px', - color: theme.palette.text.primary - } - }, - '& .MuiInputLabel-root': { - fontSize: '28px' - }, - '& .MuiInputBase-root': { - background: 'transparent', - '&:hover': { - background: 'transparent' - }, - '&.Mui-focused': { - background: 'transparent' - } - }, - '& .MuiOutlinedInput-root': { - '&:hover .MuiOutlinedInput-notchedOutline': { - borderColor: theme.palette.primary.main - }, - '&.Mui-focused .MuiOutlinedInput-notchedOutline': { - borderColor: theme.palette.primary.main - } - } -})) - -interface CreatePostMinimalProps { - blogContentForEdit?: any - postIdForEdit?: string - blogMetadataForEdit?: any - switchType?: () => void -} -export const CreatePostMinimal = ({ - blogContentForEdit, - postIdForEdit, - blogMetadataForEdit, - switchType -}: CreatePostMinimalProps) => { - const navigate = useNavigate() - const { user } = useSelector((state: RootState) => state.auth) - const { currentBlog } = useSelector((state: RootState) => state.global) - const theme = useTheme() - const [editingSection, setEditingSection] = React.useState(null) - const [layouts, setLayouts] = React.useState({ - rows: [] - }) - const [currentBreakpoint, setCurrentBreakpoint] = React.useState() - - const [newPostContent, setNewPostContent] = React.useState([]) - const [title, setTitle] = React.useState('') - const [isOpenPostModal, setIsOpenPostModal] = React.useState(false) - const [value, setValue] = React.useState(initialValue) - const [editorKey, setEditorKey] = React.useState(1) - const [count, setCount] = React.useState(1) - const [isOpenAddTextModal, setIsOpenAddTextModal] = - React.useState(false) - const [isOpenEditTextModal, setIsOpenEditTextModal] = - React.useState(false) - - const [paddingValue, onChangePadding] = React.useState(5) - const dispatch = useDispatch() - const addPostSection = React.useCallback((content: any) => { - const id = uid() - const type = 'editor' - const section = { - type, - version: 1, - content, - id - } - - setNewPostContent((prev) => [...prev, section]) - setLayouts((prev: any) => { - return { - ...prev, - rows: [ - ...prev.rows, - { - ids: [id], - id: uid(), - type - } - ] - } - }) - setEditorKey((prev) => prev + 1) - }, []) - - useEffect(() => { - if (blogContentForEdit && postIdForEdit && blogMetadataForEdit) { - setTitle(blogContentForEdit?.title || '') - setLayouts( - blogContentForEdit?.layouts || { - rows: [] - } - ) - setNewPostContent(blogContentForEdit?.postContent || []) - onChangePadding(blogContentForEdit?.layoutGeneralSettings?.padding || 5) - } - }, [blogContentForEdit, postIdForEdit, blogMetadataForEdit]) - - function objectToBase64(obj: any) { - // Step 1: Convert the object to a JSON string - const jsonString = JSON.stringify(obj) - - // Step 2: Create a Blob from the JSON string - const blob = new Blob([jsonString], { type: 'application/json' }) - - // Step 3: Create a FileReader to read the Blob as a base64-encoded string - return new Promise((resolve, reject) => { - const reader = new FileReader() - reader.onloadend = () => { - if (typeof reader.result === 'string') { - // Remove 'data:application/json;base64,' prefix - const base64 = reader.result.replace( - 'data:application/json;base64,', - '' - ) - resolve(base64) - } else { - reject( - new Error('Failed to read the Blob as a base64-encoded string') - ) - } - } - reader.onerror = () => { - reject(reader.error) - } - reader.readAsDataURL(blob) - }) - } - - const description = React.useMemo(() => { - let description = '' - const findText = newPostContent.find((data) => data?.type === 'editor') - if (findText && findText.content) { - description = extractTextFromSlate(findText?.content) - description = description.slice(0, 180) - } - return description - }, [newPostContent]) - const post = React.useMemo(() => { - return { - description, - title - } - }, [title, description]) - - async function publishQDNResource(params: any) { - let address - let name - let errorMsg = '' - - address = user?.address - name = user?.name || '' - - const missingFields = [] - if (!address) { - errorMsg = "Cannot post: your address isn't available" - } - if (!name) { - errorMsg = 'Cannot post without a name' - } - if (!title) missingFields.push('title') - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', ') - const errMsg = `Missing: ${missingFieldsString}` - errorMsg = errMsg - } - if (newPostContent.length === 0) { - errorMsg = 'Your post has no content' - } - - if (!currentBlog) { - errorMsg = 'Cannot publish without first creating a blog.' - } - - if (errorMsg) { - dispatch( - setNotification({ - msg: errorMsg, - alertType: 'error' - }) - ) - throw new Error(errorMsg) - } - - const layoutGeneralSettings = { - padding: paddingValue ?? 0, - blogPostType: 'minimal' - } - - const postObject = { - title, - createdAt: Date.now(), - postContent: newPostContent, - layouts, - layoutGeneralSettings - } - try { - if (!currentBlog) return - const id = uid() - let createTitleId = title - .replace(/[^a-zA-Z0-9\s-]/g, '') - .replace(/\s+/g, '-') - .replace(/-+/g, '-') - .trim() - - if (createTitleId.toLowerCase().includes('post')) { - createTitleId = createTitleId.replace(/post/gi, '') - } - if (createTitleId.toLowerCase().includes('q-blog')) { - createTitleId = createTitleId.replace(/q-blog/gi, '') - } - - if (createTitleId.endsWith('-')) { - createTitleId = createTitleId.slice(0, -1) - } - if (createTitleId.startsWith('-')) { - createTitleId = createTitleId.slice(1) - } - const identifier = `${currentBlog.blogId}-post-${createTitleId}-${id}` - const blogPostToBase64 = await objectToBase64(postObject) - let description = '' - const findText = newPostContent.find((data) => data?.type === 'editor') - if (findText && findText.content) { - description = extractTextFromSlate(findText?.content) - description = description.slice(0, 180) - } - - let requestBody: any = { - action: 'PUBLISH_QDN_RESOURCE', - name: name, - service: 'BLOG_POST', - data64: blogPostToBase64, - title: title, - description: params?.description || description, - category: params?.category || '', - identifier: identifier - } - - const formattedTags: { [key: string]: string } = {} - let tag4 = '' - let tag5 = '' - if (params?.tags) { - params.tags.slice(0, 3).forEach((tag: string, i: number) => { - formattedTags[`tag${i + 1}`] = tag - }) - } - - const findVideo: any = postObject?.postContent?.find( - (data: any) => data?.type === 'video' - ) - const findAudio: any = postObject?.postContent?.find( - (data: any) => data?.type === 'audio' - ) - const findImage: any = postObject?.postContent?.find( - (data: any) => data?.type === 'image' - ) - - const tag5Array = ['t'] - if (findVideo) tag5Array.push('v') - if (findAudio) tag5Array.push('a') - if (findImage) { - tag5Array.push('i') - const imageElement = document.querySelector( - `[id="${findImage.id}"] img` - ) as HTMLImageElement | null - if (imageElement) { - tag4 = `v1.${imageElement?.width}x${imageElement?.height}` - } else { - tag4 = 'v1.0x0' - } - } - if (!findImage) { - tag4 = 'v1.0x0' - } - tag5 = tag5Array.join(', ') - requestBody = { - ...requestBody, - ...formattedTags, - tag4: tag4, - tag5: tag5 - } - - const resourceResponse = await qortalRequest(requestBody) - - const postobj: any = { - ...postObject, - title: title, - description: params?.description || description, - category: params?.category || '', - tags: [...(params?.tags || []), tag4, tag5], - id: identifier, - user: name, - postImage: findImage ? findImage?.content?.image : '' - } - - const withoutImage = { ...postobj } - delete withoutImage.postImage - dispatch(addPostToBeginning(withoutImage)) - dispatch(addToHashMap(postobj)) - dispatch( - setNotification({ - msg: 'Blog post successfully published', - alertType: 'success' - }) - ) - const str = identifier - const arr = str.split('-post-') - const str1 = arr[0] - const str2 = arr[1] - const blogId = removePrefix(str1) - navigate(`/${name}/${blogId}/${str2}`) - } catch (error: any) { - let notificationObj = null - if (typeof error === 'string') { - notificationObj = { - msg: error || 'Failed to publish post', - alertType: 'error' - } - } else if (typeof error?.error === 'string') { - notificationObj = { - msg: error?.error || 'Failed to publish post', - alertType: 'error' - } - } else { - notificationObj = { - msg: error?.message || 'Failed to publish post', - alertType: 'error' - } - } - if (!notificationObj) return - dispatch(setNotification(notificationObj)) - - throw new Error('Failed to publish post') - } - } - async function updateQDNResource(params: any) { - if (!blogContentForEdit || !postIdForEdit || !blogMetadataForEdit) return - let address - let name - let errorMsg = '' - - address = user?.address - name = user?.name || '' - - const missingFields = [] - if (!address) { - errorMsg = "Cannot post: your address isn't available" - } - if (!name) { - errorMsg = 'Cannot post without a name' - } - if (!title) missingFields.push('title') - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', ') - const errMsg = `Missing: ${missingFieldsString}` - errorMsg = errMsg - } - if (newPostContent.length === 0) { - errorMsg = 'Your post has no content' - } - - if (!currentBlog) { - errorMsg = 'Cannot publish without first creating a blog.' - } - - if (errorMsg) { - dispatch( - setNotification({ - msg: errorMsg, - alertType: 'error' - }) - ) - throw new Error(errorMsg) - } - - const layoutGeneralSettings = { - padding: paddingValue ?? 0, - blogPostType: 'minimal' - } - - const postObject = { - ...blogContentForEdit, - title, - postContent: newPostContent, - layouts, - layoutGeneralSettings - } - try { - if (!currentBlog) return - - const identifier = postIdForEdit - const blogPostToBase64 = await objectToBase64(postObject) - let description = '' - const findText = newPostContent.find((data) => data?.type === 'editor') - if (findText && findText.content) { - description = extractTextFromSlate(findText?.content) - description = description.slice(0, 180) - } - - let requestBody: any = { - action: 'PUBLISH_QDN_RESOURCE', - name: name, - service: 'BLOG_POST', - data64: blogPostToBase64, - title: title, - description: params?.description || description, - category: params?.category || '', - identifier: identifier - } - - const formattedTags: { [key: string]: string } = {} - let tag4 = '' - let tag5 = '' - if (params?.tags) { - params.tags.slice(0, 3).forEach((tag: string, i: number) => { - formattedTags[`tag${i + 1}`] = tag - }) - } - - const findVideo: any = postObject?.postContent?.find( - (data: any) => data?.type === 'video' - ) - const findAudio: any = postObject?.postContent?.find( - (data: any) => data?.type === 'audio' - ) - const findImage: any = postObject?.postContent?.find( - (data: any) => data?.type === 'image' - ) - - const tag5Array = ['t'] - if (findVideo) tag5Array.push('v') - if (findAudio) tag5Array.push('a') - if (findImage) { - tag5Array.push('i') - const imageElement = document.querySelector( - `[id="${findImage.id}"] img` - ) as HTMLImageElement | null - if (imageElement) { - tag4 = `v1.${imageElement?.width}x${imageElement?.height}` - } else { - tag4 = 'v1.0x0' - } - } - if (!findImage) { - tag4 = 'v1.0x0' - } - tag5 = tag5Array.join(', ') - requestBody = { - ...requestBody, - ...formattedTags, - tag4: tag4, - tag5: tag5 - } - - const resourceResponse = await qortalRequest(requestBody) - - const postobj = { - ...postObject, - title: title, - description: params?.description || description, - category: params?.category || '', - tags: [...(params?.tags || []), tag4, tag5], - user: name, - id: identifier - } - const withoutImage = { ...postobj } - delete withoutImage.postImage - dispatch(updatePost(withoutImage)) - dispatch(updateInHashMap(postobj)) - dispatch( - setNotification({ - msg: 'Blog post successfully updated', - alertType: 'success' - }) - ) - } catch (error: any) { - let notificationObj = null - if (typeof error === 'string') { - notificationObj = { - msg: error || 'Failed to publish post', - alertType: 'error' - } - } else if (typeof error?.error === 'string') { - notificationObj = { - msg: error?.error || 'Failed to publish post', - alertType: 'error' - } - } else { - notificationObj = { - msg: error?.message || 'Failed to publish post', - alertType: 'error' - } - } - if (!notificationObj) return - dispatch(setNotification(notificationObj)) - - throw new Error('Failed to update post') - } - } - const addImage = (base64: string) => { - const id = uid() - const type = 'image' - const section = { - type, - version: 1, - content: { - image: base64, - caption: '' - }, - id - } - setNewPostContent((prev) => [...prev, section]) - setLayouts((prev: any) => { - return { - ...prev, - rows: [ - ...prev.rows, - { - ids: [id], - id: uid(), - type - } - ] - } - }) - } - - interface IaddVideo { - name: string - identifier: string - service: string - title: string - description: string - mimeType?: string - } - const addVideo = ({ - name, - identifier, - service, - title, - description - }: IaddVideo) => { - const id = uid() - const type = 'video' - const section = { - type, - version: 1, - content: { - name: name, - identifier: identifier, - service: service, - title, - description - }, - id - } - setNewPostContent((prev) => [...prev, section]) - setLayouts((prev: any) => { - return { - ...prev, - rows: [ - ...prev.rows, - { - ids: [id], - id: uid(), - type - } - ] - } - }) - } - - const removeFromLayouts = (rowIndex: number, id: string) => { - setLayouts((prev: any) => { - const copyRows = [...prev.rows] - const copyRow = copyRows[rowIndex] - const indexToRemove = copyRow.ids.indexOf(id) - - // Remove the element using splice() - if (indexToRemove !== -1) { - copyRow.ids.splice(indexToRemove, 1) - } - if (copyRow.ids.length === 0) { - copyRows.splice(rowIndex, 1) - } - return { - ...prev, - rows: copyRows - } - }) - } - - const addAudio = ({ - name, - identifier, - service, - title, - description - }: IaddVideo) => { - const id = uid() - const type = 'audio' - const section = { - type, - version: 1, - content: { - name: name, - identifier: identifier, - service: service, - title, - description - }, - id - } - setNewPostContent((prev) => [...prev, section]) - setLayouts((prev: any) => { - return { - ...prev, - rows: [ - ...prev.rows, - { - ids: [id], - id: uid(), - type - } - ] - } - }) - } - const addFile = ({ - name, - identifier, - service, - title, - description, - mimeType - }: IaddVideo) => { - const id = uid() - const type = 'file' - const section = { - type, - version: 1, - content: { - name: name, - identifier: identifier, - service: service, - title, - description, - mimeType - }, - id - } - setNewPostContent((prev) => [...prev, section]) - setLayouts((prev: any) => { - return { - ...prev, - rows: [ - ...prev.rows, - { - ids: [id], - id: uid(), - type - } - ] - } - }) - } - const addSection = () => { - setValue(initialValue) - addPostSection(value) - } - - const removeSection = (section: any, rowIndex: number) => { - const newContent = newPostContent.filter((s) => s.id !== section.id) - setNewPostContent(newContent) - removeFromLayouts(rowIndex, section.id) - } - const editImage = (base64: string, section: any) => { - const newSection = { - ...section, - content: { - image: base64, - caption: section.content.caption - } - } - const findSectionIndex = newPostContent.findIndex( - (s) => s.id === section.id - ) - if (findSectionIndex !== -1) { - const copyNewPostContent = [...newPostContent] - copyNewPostContent[findSectionIndex] = newSection - - setNewPostContent(copyNewPostContent) - } - } - const editVideo = ( - { name, identifier, service, description, title }: IaddVideo, - section: any - ) => { - const newSection = { - ...section, - content: { - name: name, - identifier: identifier, - service: service, - description, - title - } - } - const findSectionIndex = newPostContent.findIndex( - (s) => s.id === section.id - ) - if (findSectionIndex !== -1) { - const copyNewPostContent = [...newPostContent] - copyNewPostContent[findSectionIndex] = newSection - - setNewPostContent(copyNewPostContent) - } - } - - const editAudio = ( - { name, identifier, service, description, title }: IaddVideo, - section: any - ) => { - const newSection = { - ...section, - content: { - name: name, - identifier: identifier, - service: service, - description, - title - } - } - const findSectionIndex = newPostContent.findIndex( - (s) => s.id === section.id - ) - if (findSectionIndex !== -1) { - const copyNewPostContent = [...newPostContent] - copyNewPostContent[findSectionIndex] = newSection - - setNewPostContent(copyNewPostContent) - } - } - - const editSection = (section: any) => { - setIsOpenEditTextModal(true) - setEditingSection(section) - setValue(section.content) - } - - const editPostSection = React.useCallback( - (content: any, section: any) => { - const findSectionIndex = newPostContent.findIndex( - (s) => s.id === section.id - ) - - if (findSectionIndex !== -1) { - const copyNewPostContent = [...newPostContent] - copyNewPostContent[findSectionIndex] = { - ...section, - content - } - - setNewPostContent(copyNewPostContent) - } - - setEditingSection(null) - setIsOpenEditTextModal(false) - }, - [newPostContent] - ) - - const onSelectVideo = React.useCallback((video: any) => { - addVideo({ - name: video.name, - identifier: video.identifier, - service: video.service, - title: video?.metadata?.title, - description: video?.metadata?.description - }) - }, []) - - const onSelectAudio = React.useCallback((video: any) => { - addAudio({ - name: video.name, - identifier: video.identifier, - service: video.service, - title: video?.metadata?.title, - description: video?.metadata?.description - }) - }, []) - const onSelectFile = React.useCallback((video: any) => { - addFile({ - name: video.name, - identifier: video.identifier, - service: video.service, - title: video?.metadata?.title, - description: video?.metadata?.description, - mimeType: video?.metadata?.mimeType - }) - }, []) - - const closeAddTextModal = React.useCallback(() => { - setIsOpenAddTextModal(false) - }, []) - const closeEditTextModal = React.useCallback(() => { - setIsOpenEditTextModal(false) - setEditingSection(null) - }, []) - - const handleResize = () => { - setCount((prev) => prev + 1) - } - - React.useEffect(() => { - window.addEventListener('resize', handleResize) - - return () => { - window.removeEventListener('resize', handleResize) - } - }, []) - - const addImageToRow = ( - row: any, - rowIndex: number, - position: string, - base64: string - ) => { - const newId = uid() - const type = 'image' - setLayouts((prev: any) => { - const { id } = row - if (!id) return prev - const copyRows: any = [...prev.rows] - const copyRow: any = copyRows[rowIndex] - if (position === 'left') { - copyRow.ids = [newId, ...copyRow.ids] - } - if (position === 'right') { - copyRow.ids = [...copyRow.ids, newId] - } - copyRows[rowIndex] = copyRow - return { - ...prev, - rows: copyRows - } - }) - - const section = { - type, - version: 1, - content: { - image: base64, - caption: '' - }, - id: newId - } - setNewPostContent((prev) => [...prev, section]) - } - - return ( - <> - - - - setTitle(e.target.value)} - fullWidth - placeholder="Title" - variant="filled" - multiline - maxRows={2} - InputLabelProps={{ shrink: false }} - sx={{ maxWidth: '700px' }} - /> - - {layouts.rows.map((row: any, rowIndex: number) => { - const { type } = row - return ( - - {row.type === 'image' && row.ids.length < 3 && ( - - - addImageToRow(row, rowIndex, 'left', base64) - } - > - - - - )} - {row.ids.map((elementId: string) => { - const section = newPostContent.find( - (el) => el.id === elementId - ) - if (!section) return null - if (section.type === 'editor') { - return ( -
- - {editingSection && - editingSection.id === section.id ? null : ( - - - - - removeSection(section, rowIndex) - } - sx={{ - cursor: 'pointer', - height: '18px', - width: 'auto' - }} - /> - editSection(section)} - sx={{ - cursor: 'pointer', - height: '18px', - width: 'auto' - }} - /> - - - )} - -
- ) - } - if (section.type === 'image') { - return ( -
- - - - - removeSection(section, rowIndex)} - sx={{ - cursor: 'pointer', - height: '18px', - width: 'auto' - }} - /> - editImage(base64, section)} - > - - - - - -
- ) - } - - if (section.type === 'video') { - return ( -
- - - - - removeSection(section, rowIndex)} - sx={{ - cursor: 'pointer', - height: '18px', - width: 'auto' - }} - /> - - editVideo( - { - name: video.name, - identifier: video.identifier, - service: video.service, - title: video?.metadata?.title, - description: video?.metadata?.description - }, - section - ) - } - /> - - - -
- ) - } - if (section.type === 'audio') { - return ( -
- - - {}} - title={section.content?.title} - description={section.content?.description} - author="" - /> - - removeSection(section, rowIndex)} - sx={{ - cursor: 'pointer', - height: '18px', - width: 'auto' - }} - /> - - editAudio( - { - name: audio.name, - identifier: audio.identifier, - service: audio.service, - title: audio?.metadata?.title, - description: audio?.metadata?.description - }, - section - ) - } - /> - - - -
- ) - } - if (section.type === 'file') { - return ( -
- - - - - - removeSection(section, rowIndex)} - sx={{ - cursor: 'pointer', - height: '18px', - width: 'auto' - }} - /> - - - -
- ) - } - })} - {row.type === 'image' && row.ids.length < 3 && ( - - - addImageToRow(row, rowIndex, 'right', base64) - } - > - - - - )} -
- ) - })} - - - - -
- - - - - Add Text - Close - - - - - - editPostSection(value, editingSection)}> - Update Text - - Close - - {!blogContentForEdit && ( - { - setIsOpenPostModal(false) - }} - open={isOpenPostModal} - post={post} - onPublish={publishQDNResource} - /> - )} - - {blogContentForEdit && blogMetadataForEdit?.metadata && ( - { - setIsOpenPostModal(false) - }} - open={isOpenPostModal} - post={post} - onPublish={updateQDNResource} - mode="edit" - metadata={blogMetadataForEdit?.metadata} - /> - )} -
- - ) -}