'use client'; import React, { FC, Fragment, ReactNode, useCallback, useEffect, useMemo, useState, } from 'react'; import { Button } from '@gitroom/react/form/button'; import { deleteDialog } from '@gitroom/react/helpers/delete.dialog'; import MDEditor, { commands } from '@uiw/react-md-editor'; import { useHideTopEditor } from '@gitroom/frontend/components/launches/helpers/use.hide.top.editor'; import { useValues } from '@gitroom/frontend/components/launches/helpers/use.values'; import { FormProvider } from 'react-hook-form'; import { useMoveToIntegrationListener } from '@gitroom/frontend/components/launches/helpers/use.move.to.integration'; import { useExistingData } from '@gitroom/frontend/components/launches/helpers/use.existing.data'; import { IntegrationContext, useIntegration, } from '@gitroom/frontend/components/launches/helpers/use.integration'; import { MultiMediaComponent } from '@gitroom/frontend/components/media/media.component'; import { createPortal } from 'react-dom'; import clsx from 'clsx'; import { newImage } from '@gitroom/frontend/components/launches/helpers/new.image.component'; import { postSelector } from '@gitroom/frontend/components/post-url-selector/post.url.selector'; import { UpDownArrow } from '@gitroom/frontend/components/launches/up.down.arrow'; import { arrayMoveImmutable } from 'array-move'; // Simple component to change back to settings on after changing tab export const SetTab: FC<{ changeTab: () => void }> = (props) => { useEffect(() => { return () => { setTimeout(() => { props.changeTab(); }, 500); }; }, []); return null; }; // This is a simple function that if we edit in place, we hide the editor on top export const EditorWrapper: FC<{ children: ReactNode }> = ({ children }) => { const showHide = useHideTopEditor(); const [showEditor, setShowEditor] = useState(false); useEffect(() => { setShowEditor(true); showHide.hide(); return () => { showHide.show(); setShowEditor(false); }; }, []); if (!showEditor) { return null; } return children; }; export const withProvider = ( SettingsComponent: FC | null, PreviewComponent: FC, dto?: any ) => { return (props: { identifier: string; id: string; value: Array<{ content: string; id?: string; image?: Array<{ path: string; id: string }>; }>; show: boolean; }) => { const existingData = useExistingData(); const { integration, date } = useIntegration(); const [editInPlace, setEditInPlace] = useState(!!existingData.integration); const [InPlaceValue, setInPlaceValue] = useState< Array<{ id?: string; content: string; image?: Array<{ id: string; path: string }>; }> >( // @ts-ignore existingData.integration ? existingData.posts.map((p) => ({ id: p.id, content: p.content, image: p.image, })) : [{ content: '' }] ); const [showTab, setShowTab] = useState(0); const Component = useMemo(() => { return SettingsComponent ? SettingsComponent : () => <>; }, [SettingsComponent]); // in case there is an error on submit, we change to the settings tab for the specific provider useMoveToIntegrationListener([props.id], true, (identifier) => { if (identifier === props.id) { setShowTab(2); } }); // this is a smart function, it updates the global value without updating the states (too heavy) and set the settings validation const form = useValues( existingData.settings, props.id, props.identifier, editInPlace ? InPlaceValue : props.value, dto ); // change editor value const changeValue = useCallback( (index: number) => (newValue: string) => { return setInPlaceValue((prev) => { prev[index].content = newValue; return [...prev]; }); }, [InPlaceValue] ); const changeImage = useCallback( (index: number) => (newValue: { target: { name: string; value?: Array<{ id: string; path: string }> }; }) => { return setInPlaceValue((prev) => { prev[index].image = newValue.target.value; return [...prev]; }); }, [InPlaceValue] ); // add another local editor const addValue = useCallback( (index: number) => () => { setInPlaceValue((prev) => { return prev.reduce((acc, p, i) => { acc.push(p); if (i === index) { acc.push({ content: '' }); } return acc; }, [] as Array<{ content: string }>); }); }, [] ); const changePosition = useCallback( (index: number) => (type: 'up' | 'down') => { if (type === 'up' && index !== 0) { setInPlaceValue((prev) => { return arrayMoveImmutable(prev, index, index - 1); }); } else if (type === 'down') { setInPlaceValue((prev) => { return arrayMoveImmutable(prev, index, index + 1); }); } }, [] ); // Delete post const deletePost = useCallback( (index: number) => async () => { if ( !(await deleteDialog( 'Are you sure you want to delete this post?', 'Yes, delete it!' )) ) { return; } setInPlaceValue((prev) => { prev.splice(index, 1); return [...prev]; }); }, [InPlaceValue] ); // This is a function if we want to switch from the global editor to edit in place const changeToEditor = useCallback(async () => { if ( !(await deleteDialog( !editInPlace ? 'Are you sure you want to edit only this?' : 'Are you sure you want to revert it back to global editing?', 'Yes, edit in place!' )) ) { return false; } setEditInPlace(!editInPlace); setInPlaceValue( editInPlace ? [{ content: '' }] : props.value.map((p) => ({ id: p.id, content: p.content, image: p.image})) ); }, [props.value, editInPlace]); // this is a trick to prevent the data from being deleted, yet we don't render the elements if (!props.show) { return null; } return ( setShowTab(0)} />
{!!SettingsComponent && (
)}
{editInPlace && createPortal(
{!existingData?.integration && (
This will edit only this provider
)} {InPlaceValue.map((val, index) => (
1 ? 200 : 250} value={val.content} commands={[ ...commands .getCommands() .filter((f) => f.name !== 'image'), newImage, postSelector(date), ]} preview="edit" // @ts-ignore onChange={changeValue(index)} /> {(!val.content || val.content.length < 6) && (
The post should be at least 6 characters long
)}
{InPlaceValue.length > 1 && (
Delete Post
)}
))}
, document.querySelector('#renderEditor')! )} {showTab === 2 && (
)} {showTab === 0 && (
{(editInPlace ? InPlaceValue : props.value) .map((p) => p.content) .join('').length ? ( ) : ( <>No Content Yet )}
)}
); }; };