Skip to content

Commit a00b9fa

Browse files
authored
feat(react): add useNavigateToStudioDocument hook (#352)
1 parent 83feb21 commit a00b9fa

File tree

3 files changed

+119
-7
lines changed

3 files changed

+119
-7
lines changed

apps/kitchensink-react/src/DocumentCollection/DocumentCoreInteractionsRoute.tsx

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,27 @@
1-
import {useInfiniteList, useManageFavorite, useRecordDocumentHistoryEvent} from '@sanity/sdk-react'
1+
import {
2+
DocumentHandle,
3+
useInfiniteList,
4+
useManageFavorite,
5+
useNavigateToStudioDocument,
6+
useRecordDocumentHistoryEvent,
7+
} from '@sanity/sdk-react'
28
import {Box, Button, Flex, Heading} from '@sanity/ui'
39
import {type JSX} from 'react'
410

511
import {DocumentListLayout} from '../components/DocumentListLayout/DocumentListLayout'
612
import {DocumentPreview} from './DocumentPreview'
713
import {LoadMore} from './LoadMore'
814

9-
interface ActionButtonsProps {
10-
document: {_id: string; _type: string}
11-
}
12-
13-
function ActionButtons({document}: ActionButtonsProps) {
15+
function ActionButtons({document}: {document: DocumentHandle}) {
1416
const {
1517
favorite,
1618
unfavorite,
1719
isFavorited,
1820
isConnected: isFavoriteConnected,
1921
} = useManageFavorite(document)
2022
const {recordEvent, isConnected: isHistoryConnected} = useRecordDocumentHistoryEvent(document)
23+
const {navigateToStudioDocument, isConnected: isNavigateConnected} =
24+
useNavigateToStudioDocument(document)
2125

2226
return (
2327
<Flex gap={2} padding={2}>
@@ -39,6 +43,12 @@ function ActionButtons({document}: ActionButtonsProps) {
3943
onClick={() => recordEvent('viewed')}
4044
text="Record view"
4145
/>
46+
<Button
47+
mode="ghost"
48+
disabled={!isNavigateConnected}
49+
onClick={navigateToStudioDocument}
50+
text="Edit in Studio"
51+
/>
4252
</Flex>
4353
)
4454
}
@@ -49,6 +59,10 @@ export function DocumentCoreInteractionsRoute(): JSX.Element {
4959
orderings: [{field: '_updatedAt', direction: 'desc'}],
5060
})
5161

62+
// since resourceIds are being refactored at the moment, hardcode the resourceId for now
63+
// (ideally, they come from document handles returned in the "data" param per document)
64+
const datasetResourceId = 'document:ppsg7ml5.test:' // Note: each doc._id will be appended to this
65+
5266
return (
5367
<Box padding={4}>
5468
<Heading as="h1" size={5}>
@@ -59,7 +73,7 @@ export function DocumentCoreInteractionsRoute(): JSX.Element {
5973
{data.map((doc) => (
6074
<Box key={doc._id}>
6175
<DocumentPreview document={doc} />
62-
<ActionButtons document={doc} />
76+
<ActionButtons document={{...doc, resourceId: `${datasetResourceId}${doc._id}`}} />
6377
</Box>
6478
))}
6579
<LoadMore hasMore={hasMore} isPending={isPending} onLoadMore={loadMore} />

packages/react/src/_exports/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export {
2626
type WindowMessageHandler,
2727
} from '../hooks/comlink/useWindowConnection'
2828
export {useSanityInstance} from '../hooks/context/useSanityInstance'
29+
export {useNavigateToStudioDocument} from '../hooks/dashboard/useNavigateToStudioDocument'
2930
export {useStudioWorkspacesByResourceId} from '../hooks/dashboard/useStudioWorkspacesByResourceId'
3031
export {useDatasets} from '../hooks/datasets/useDatasets'
3132
export {useApplyActions} from '../hooks/document/useApplyActions'
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import {type Status} from '@sanity/comlink'
2+
import {type DocumentHandle} from '@sanity/sdk'
3+
import {useCallback, useState} from 'react'
4+
5+
import {useWindowConnection} from '../comlink/useWindowConnection'
6+
import {useStudioWorkspacesByResourceId} from './useStudioWorkspacesByResourceId'
7+
8+
interface NavigateToResourceMessage {
9+
type: 'core/v1/bridge/navigate-to-resource'
10+
data: {
11+
/**
12+
* Resource ID
13+
*/
14+
resourceId: string
15+
/**
16+
* Resource type
17+
* @example 'application' | 'studio'
18+
*/
19+
resourceType: string
20+
/**
21+
* Path within the resource to navigate to.
22+
*/
23+
path?: string
24+
}
25+
}
26+
27+
interface NavigateToStudioResult {
28+
navigateToStudioDocument: () => void
29+
isConnected: boolean
30+
}
31+
32+
/**
33+
* @public
34+
* Hook that provides a function to navigate to a studio document.
35+
* @param documentHandle - The document handle containing document ID, type, and resource ID
36+
* @returns An object containing:
37+
* - navigateToStudioDocument - Function that when called will navigate to the studio document
38+
* - isConnected - Boolean indicating if connection to Core UI is established
39+
*/
40+
export function useNavigateToStudioDocument(
41+
documentHandle: DocumentHandle,
42+
): NavigateToStudioResult {
43+
const {workspacesByResourceId, isConnected: workspacesConnected} =
44+
useStudioWorkspacesByResourceId()
45+
const [status, setStatus] = useState<Status>('idle')
46+
const {sendMessage} = useWindowConnection<NavigateToResourceMessage, never>({
47+
name: 'core/nodes/sdk',
48+
connectTo: 'core/channels/sdk',
49+
onStatus: setStatus,
50+
})
51+
52+
const navigateToStudioDocument = useCallback(() => {
53+
if (!workspacesConnected || status !== 'connected' || !documentHandle.resourceId) {
54+
return
55+
}
56+
57+
// Extract projectId and dataset from the resourceId (current format: document:projectId.dataset:documentId)
58+
const [, projectAndDataset] = documentHandle.resourceId.split(':')
59+
const [projectId, dataset] = projectAndDataset.split('.')
60+
if (!projectId || !dataset) {
61+
return
62+
}
63+
64+
// Find the workspace for this document
65+
const workspaces = workspacesByResourceId[`${projectId}:${dataset}`]
66+
if (!workspaces?.length) {
67+
// eslint-disable-next-line no-console
68+
console.warn('No workspace found for document', documentHandle.resourceId)
69+
return
70+
}
71+
72+
if (workspaces.length > 1) {
73+
// eslint-disable-next-line no-console
74+
console.warn('Multiple workspaces found for document', documentHandle.resourceId)
75+
// eslint-disable-next-line no-console
76+
console.warn('Using the first one', workspaces[0])
77+
}
78+
79+
const workspace = workspaces[0]
80+
81+
const message: NavigateToResourceMessage = {
82+
type: 'core/v1/bridge/navigate-to-resource',
83+
data: {
84+
resourceId: workspace._ref,
85+
resourceType: 'studio',
86+
path: `/intent/edit/id=${documentHandle._id};type=${documentHandle._type}`,
87+
},
88+
}
89+
90+
sendMessage(message.type, message.data)
91+
}, [documentHandle, workspacesConnected, status, sendMessage, workspacesByResourceId])
92+
93+
return {
94+
navigateToStudioDocument,
95+
isConnected: workspacesConnected && status === 'connected',
96+
}
97+
}

0 commit comments

Comments
 (0)