diff --git a/packages/sanity/src/core/store/_legacy/document/document-pair/editState.ts b/packages/sanity/src/core/store/_legacy/document/document-pair/editState.ts index baedd21604c..6565d4fac96 100644 --- a/packages/sanity/src/core/store/_legacy/document/document-pair/editState.ts +++ b/packages/sanity/src/core/store/_legacy/document/document-pair/editState.ts @@ -3,6 +3,7 @@ import {type SanityDocument, type Schema} from '@sanity/types' import {combineLatest, type Observable} from 'rxjs' import {map, publishReplay, refCount, startWith, switchMap} from 'rxjs/operators' +import {createSWR} from '../../../../util/rxSwr' import {type PairListenerOptions} from '../getPairListener' import {type IdPair, type PendingMutationsEvent} from '../types' import {memoize} from '../utils/createMemoizer' @@ -14,6 +15,8 @@ interface TransactionSyncLockState { enabled: boolean } +const swr = createSWR<[SanityDocument, SanityDocument, TransactionSyncLockState]>({maxSize: 50}) + /** * @hidden * @beta */ @@ -42,6 +45,7 @@ export const editState = memoize( typeName: string, ): Observable => { const liveEdit = isLiveEditEnabled(ctx.schema, typeName) + return snapshotPair( ctx.client, idPair, @@ -59,14 +63,15 @@ export const editState = memoize( ), ]), ), - map(([draftSnapshot, publishedSnapshot, transactionSyncLock]) => ({ + swr(`${idPair.publishedId}-${idPair.draftId}`), + map(({value: [draftSnapshot, publishedSnapshot, transactionSyncLock], fromCache}) => ({ id: idPair.publishedId, type: typeName, draft: draftSnapshot, published: publishedSnapshot, liveEdit, - ready: true, - transactionSyncLock, + ready: !fromCache, + transactionSyncLock: fromCache ? null : transactionSyncLock, })), startWith({ id: idPair.publishedId, diff --git a/packages/sanity/src/core/store/_legacy/document/document-store.ts b/packages/sanity/src/core/store/_legacy/document/document-store.ts index e80ba2a338d..8aeec78a0d8 100644 --- a/packages/sanity/src/core/store/_legacy/document/document-store.ts +++ b/packages/sanity/src/core/store/_legacy/document/document-store.ts @@ -162,7 +162,10 @@ export function createDocumentStore({ return editOperations(ctx, getIdPairFromPublished(publishedId), type) }, editState(publishedId, type) { - return editState(ctx, getIdPairFromPublished(publishedId), type) + const idPair = getIdPairFromPublished(publishedId) + + const edit = editState(ctx, idPair, type) + return edit }, operationEvents(publishedId, type) { return operationEvents({ @@ -185,7 +188,8 @@ export function createDocumentStore({ ) }, validation(publishedId, type) { - return validation(ctx, getIdPairFromPublished(publishedId), type) + const idPair = getIdPairFromPublished(publishedId) + return validation(ctx, idPair, type) }, }, } diff --git a/packages/sanity/src/structure/panes/document/documentPanel/documentViews/FormView.tsx b/packages/sanity/src/structure/panes/document/documentPanel/documentViews/FormView.tsx index e4e63948b8b..f65cad7a2ec 100644 --- a/packages/sanity/src/structure/panes/document/documentPanel/documentViews/FormView.tsx +++ b/packages/sanity/src/structure/panes/document/documentPanel/documentViews/FormView.tsx @@ -166,7 +166,7 @@ export const FormView = forwardRef(function FormV > - {connectionState === 'connecting' || !ready ? ( + {connectionState === 'connecting' && !editState?.draft && !editState?.published ? ( {/* TODO: replace with loading block */} @@ -205,7 +205,9 @@ export const FormView = forwardRef(function FormV onSetPathCollapsed={onSetCollapsedPath} openPath={openPath} presence={presence} - readOnly={connectionState === 'reconnecting' || formState.readOnly} + readOnly={ + connectionState === 'reconnecting' || formState.readOnly || !editState?.ready + } schemaType={formState.schemaType} validation={validation} value={ diff --git a/packages/sanity/src/structure/panes/document/documentPanel/header/DocumentHeaderTabs.tsx b/packages/sanity/src/structure/panes/document/documentPanel/header/DocumentHeaderTabs.tsx index 289d64f97ca..60534bcf789 100644 --- a/packages/sanity/src/structure/panes/document/documentPanel/header/DocumentHeaderTabs.tsx +++ b/packages/sanity/src/structure/panes/document/documentPanel/header/DocumentHeaderTabs.tsx @@ -35,7 +35,7 @@ function DocumentHeaderTab(props: { viewId: string | null }) { const {icon, id, isActive, label, tabPanelId, viewId, ...rest} = props - const {ready} = useDocumentPane() + const {ready, editState} = useDocumentPane() const {setView} = usePaneRouter() const handleClick = useCallback(() => setView(viewId), [setView, viewId]) @@ -43,7 +43,7 @@ function DocumentHeaderTab(props: { keyboard navigation aria-controls={tabPanelId} - disabled={!ready} + disabled={!ready && !editState?.draft && !editState?.published} icon={icon} id={id} label={label} diff --git a/packages/sanity/src/structure/panes/document/documentPanel/header/DocumentHeaderTitle.test.tsx b/packages/sanity/src/structure/panes/document/documentPanel/header/DocumentHeaderTitle.test.tsx index d34ec969a32..048bec4c3e8 100644 --- a/packages/sanity/src/structure/panes/document/documentPanel/header/DocumentHeaderTitle.test.tsx +++ b/packages/sanity/src/structure/panes/document/documentPanel/header/DocumentHeaderTitle.test.tsx @@ -39,7 +39,7 @@ describe('DocumentHeaderTitle', () => { const defaultProps = { connectionState: 'connected', schemaType: {title: 'Test Schema', name: 'testSchema'}, - value: {title: 'Test Value'}, + editState: {draft: {title: 'Test Value'}}, } const defaultValue = { @@ -63,16 +63,33 @@ describe('DocumentHeaderTitle', () => { await waitFor(() => expect(getByText('Untitled')).toBeInTheDocument()) }) - it('should return an empty fragment when connectionState is not "connected"', async () => { + it('should return an empty fragment when connectionState is not "connected" and editState is empty', async () => { mockUseDocumentPane.mockReturnValue({ ...defaultProps, connectionState: 'connecting', + editState: null, } as unknown as DocumentPaneContextValue) const {container} = render() await waitFor(() => expect(container.firstChild).toBeNull()) }) + it('should render the header title when connectionState is not "connected" and editState has values', async () => { + mockUseDocumentPane.mockReturnValue({ + ...defaultProps, + connectionState: 'connecting', + } as unknown as DocumentPaneContextValue) + + mockUseValuePreview.mockReturnValue({ + ...defaultValue, + error: undefined, + value: {title: 'Test Value'}, + }) + + const {getByText} = render() + await waitFor(() => expect(getByText('Test Value')).toBeInTheDocument()) + }) + it('should return the title if it is provided', async () => { mockUseDocumentPane.mockReturnValue({ ...defaultProps, @@ -89,7 +106,7 @@ describe('DocumentHeaderTitle', () => { it('should return "New {schemaType?.title || schemaType?.name}" if documentValue is not provided', async () => { mockUseDocumentPane.mockReturnValue({ ...defaultProps, - value: null, + editState: null, } as unknown as DocumentPaneContextValue) const client = createMockSanityClient() @@ -146,7 +163,7 @@ describe('DocumentHeaderTitle', () => { expect(mockUseValuePreview).toHaveBeenCalledWith({ enabled: true, schemaType: defaultProps.schemaType, - value: defaultProps.value, + value: defaultProps.editState.draft, }), ) }) diff --git a/packages/sanity/src/structure/panes/document/documentPanel/header/DocumentHeaderTitle.tsx b/packages/sanity/src/structure/panes/document/documentPanel/header/DocumentHeaderTitle.tsx index 80b4e848d1f..ee6fe289438 100644 --- a/packages/sanity/src/structure/panes/document/documentPanel/header/DocumentHeaderTitle.tsx +++ b/packages/sanity/src/structure/panes/document/documentPanel/header/DocumentHeaderTitle.tsx @@ -5,8 +5,9 @@ import {structureLocaleNamespace} from '../../../../i18n' import {useDocumentPane} from '../../useDocumentPane' export function DocumentHeaderTitle(): ReactElement { - const {connectionState, schemaType, title, value: documentValue} = useDocumentPane() - const subscribed = Boolean(documentValue) && connectionState !== 'connecting' + const {connectionState, schemaType, title, editState} = useDocumentPane() + const documentValue = editState?.draft || editState?.published + const subscribed = Boolean(documentValue) const {error, value} = useValuePreview({ enabled: subscribed, @@ -15,7 +16,7 @@ export function DocumentHeaderTitle(): ReactElement { }) const {t} = useTranslation(structureLocaleNamespace) - if (connectionState === 'connecting') { + if (connectionState === 'connecting' && !subscribed) { return <> } diff --git a/packages/sanity/src/structure/panes/document/documentPanel/header/DocumentPanelHeader.tsx b/packages/sanity/src/structure/panes/document/documentPanel/header/DocumentPanelHeader.tsx index 3298537e451..ef37d951f93 100644 --- a/packages/sanity/src/structure/panes/document/documentPanel/header/DocumentPanelHeader.tsx +++ b/packages/sanity/src/structure/panes/document/documentPanel/header/DocumentPanelHeader.tsx @@ -137,7 +137,7 @@ export const DocumentPanelHeader = memo( } tabs={showTabs && } tabIndex={tabIndex} diff --git a/packages/sanity/src/structure/panes/document/useDocumentTitle.ts b/packages/sanity/src/structure/panes/document/useDocumentTitle.ts index b657a92cf1a..16c657bd198 100644 --- a/packages/sanity/src/structure/panes/document/useDocumentTitle.ts +++ b/packages/sanity/src/structure/panes/document/useDocumentTitle.ts @@ -22,8 +22,9 @@ interface UseDocumentTitle { * @returns The document title or error. See {@link UseDocumentTitle} */ export function useDocumentTitle(): UseDocumentTitle { - const {connectionState, schemaType, title, value: documentValue} = useDocumentPane() - const subscribed = Boolean(documentValue) && connectionState !== 'connecting' + const {connectionState, schemaType, title, editState} = useDocumentPane() + const documentValue = editState?.draft || editState?.published + const subscribed = Boolean(documentValue) const {error, value} = useValuePreview({ enabled: subscribed, @@ -31,7 +32,7 @@ export function useDocumentTitle(): UseDocumentTitle { value: documentValue, }) - if (connectionState === 'connecting') { + if (connectionState === 'connecting' && !subscribed) { return {error: undefined, title: undefined} }