import {isMaybePreviewIframe, isMaybePreviewWindow} from '@sanity/presentation-comlink' import {lazy, startTransition, Suspense, useDeferredValue, useEffect, useState, useSyncExternalStore} from 'react' import {createPortal} from 'react-dom' import type {VisualEditingOptions} from '../types' import {setEnvironment} from './environment/context' import {History} from './History' import { getQueryListenerStatus, subscribeQueryListenerStatus, } from './loader-comlink/context' import {Meta} from './Meta' import {Overlays} from './Overlays' import {Refresh} from './Refresh' import {useComlink} from './useComlink' import {useDatasetMutator} from './useDatasetMutator' const LoaderComlink = lazy(() => import('./LoaderComlink')) /** * @public */ export const VisualEditing = (props: VisualEditingOptions & {portal: boolean}): React.ReactNode => { const {components, plugins, history, portal = true, refresh, zIndex, onPerspectiveChange} = props const [inFrame, setInFrame] = useState(null) const [inPopUp, setInPopUp] = useState(null) useEffect(() => { startTransition(() => { setInFrame(isMaybePreviewIframe()) setInPopUp(isMaybePreviewWindow()) }) }, []) const [portalElement, setPortalElement] = useState(null) useEffect(() => { if (portal === false) return undefined const node = document.createElement('sanity-visual-editing') document.documentElement.appendChild(node) startTransition(() => setPortalElement(node)) return () => { startTransition(() => setPortalElement(null)) if (document.documentElement.contains(node)) { document.documentElement.removeChild(node) } } }, [portal]) const [comlink, comlinkStatus] = useComlink(inFrame === true || inPopUp === true) useDatasetMutator(comlinkStatus === 'connected' ? comlink : undefined) const hasQueryListeners = useDeferredValue(useSyncExternalStore( subscribeQueryListenerStatus, getQueryListenerStatus, () => false, ), false) useEffect(() => { if (comlinkStatus === 'connected') { // Once we have connected over comlink we know for sure that presentation is present and can set env status setEnvironment(inPopUp ? 'presentation-window' : 'presentation-iframe') } else if (inFrame || inPopUp) { // If thre might be a presentation tool present wait a little bit before setting the status, give comlink a moment to handshake const timeout = setTimeout(() => setEnvironment('standalone'), 1_000) return () => { clearTimeout(timeout) setEnvironment(null) } } else { // No iframe or popup window means we are for sure in standalone mode setEnvironment('standalone') } return () => setEnvironment(null) }, [comlinkStatus, inPopUp, inFrame]) const children = ( <> {inFrame !== null && inPopUp !== null && ( )} {comlink && ( <> {refresh && } )} {comlinkStatus === 'connected' && hasQueryListeners && ( )} ) if (portal === false || !portalElement) return children return createPortal(children, portalElement) } VisualEditing.displayName = 'VisualEditing'