@sanity/sdk-react 2.4.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +652 -4
- package/dist/index.d.ts +85 -14
- package/dist/index.js +184 -57
- package/dist/index.js.map +1 -1
- package/package.json +9 -8
- package/src/_exports/sdk-react.ts +5 -0
- package/src/components/SDKProvider.tsx +8 -3
- package/src/components/SanityApp.tsx +2 -1
- package/src/context/SourcesContext.tsx +7 -0
- package/src/context/renderSanityApp.test.tsx +355 -0
- package/src/context/renderSanityApp.tsx +48 -0
- package/src/hooks/agent/useAgentResourceContext.test.tsx +245 -0
- package/src/hooks/agent/useAgentResourceContext.ts +106 -0
- package/src/hooks/context/useSource.tsx +34 -0
- package/src/hooks/dashboard/useDispatchIntent.test.ts +25 -22
- package/src/hooks/dashboard/useDispatchIntent.ts +9 -10
- package/src/hooks/dashboard/utils/{getResourceIdFromDocumentHandle.test.ts → useResourceIdFromDocumentHandle.test.ts} +33 -59
- package/src/hooks/dashboard/utils/useResourceIdFromDocumentHandle.ts +46 -0
- package/src/hooks/document/useApplyDocumentActions.test.ts +124 -9
- package/src/hooks/document/useApplyDocumentActions.ts +44 -4
- package/src/hooks/document/useDocumentPermissions.test.tsx +3 -3
- package/src/hooks/document/useDocumentPermissions.ts +9 -6
- package/src/hooks/document/useEditDocument.ts +3 -0
- package/src/hooks/documents/useDocuments.ts +3 -2
- package/src/hooks/paginatedDocuments/usePaginatedDocuments.ts +1 -0
- package/src/hooks/query/useQuery.ts +21 -8
- package/src/hooks/releases/usePerspective.test.tsx +1 -0
- package/src/hooks/releases/usePerspective.ts +1 -1
- package/src/hooks/dashboard/types.ts +0 -12
- package/src/hooks/dashboard/utils/getResourceIdFromDocumentHandle.ts +0 -53
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import {renderHook} from '@testing-library/react'
|
|
2
|
+
import {beforeEach, describe, expect, it, vi} from 'vitest'
|
|
3
|
+
|
|
4
|
+
import {AppProviders} from '../../../test/test-utils'
|
|
5
|
+
import {useWindowConnection} from '../comlink/useWindowConnection'
|
|
6
|
+
import {useAgentResourceContext} from './useAgentResourceContext'
|
|
7
|
+
|
|
8
|
+
vi.mock('../comlink/useWindowConnection', () => ({
|
|
9
|
+
useWindowConnection: vi.fn(),
|
|
10
|
+
}))
|
|
11
|
+
|
|
12
|
+
describe('useAgentResourceContext', () => {
|
|
13
|
+
let mockSendMessage = vi.fn()
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
mockSendMessage = vi.fn()
|
|
17
|
+
vi.mocked(useWindowConnection).mockImplementation(() => {
|
|
18
|
+
return {
|
|
19
|
+
sendMessage: mockSendMessage,
|
|
20
|
+
fetch: vi.fn(),
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('should send context update on mount', () => {
|
|
26
|
+
renderHook(
|
|
27
|
+
() =>
|
|
28
|
+
useAgentResourceContext({
|
|
29
|
+
projectId: 'test-project',
|
|
30
|
+
dataset: 'production',
|
|
31
|
+
documentId: 'doc-123',
|
|
32
|
+
}),
|
|
33
|
+
{wrapper: AppProviders},
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
expect(mockSendMessage).toHaveBeenCalledWith('dashboard/v1/events/agent/resource/update', {
|
|
37
|
+
projectId: 'test-project',
|
|
38
|
+
dataset: 'production',
|
|
39
|
+
documentId: 'doc-123',
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('should send context update without documentId', () => {
|
|
44
|
+
renderHook(
|
|
45
|
+
() =>
|
|
46
|
+
useAgentResourceContext({
|
|
47
|
+
projectId: 'test-project',
|
|
48
|
+
dataset: 'production',
|
|
49
|
+
}),
|
|
50
|
+
{wrapper: AppProviders},
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
expect(mockSendMessage).toHaveBeenCalledWith('dashboard/v1/events/agent/resource/update', {
|
|
54
|
+
projectId: 'test-project',
|
|
55
|
+
dataset: 'production',
|
|
56
|
+
documentId: undefined,
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('should send context update when context changes', () => {
|
|
61
|
+
const {rerender} = renderHook(
|
|
62
|
+
({documentId}: {documentId: string}) =>
|
|
63
|
+
useAgentResourceContext({
|
|
64
|
+
projectId: 'test-project',
|
|
65
|
+
dataset: 'production',
|
|
66
|
+
documentId,
|
|
67
|
+
}),
|
|
68
|
+
{
|
|
69
|
+
wrapper: AppProviders,
|
|
70
|
+
initialProps: {documentId: 'doc-123'},
|
|
71
|
+
},
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
expect(mockSendMessage).toHaveBeenCalledTimes(1)
|
|
75
|
+
expect(mockSendMessage).toHaveBeenCalledWith('dashboard/v1/events/agent/resource/update', {
|
|
76
|
+
projectId: 'test-project',
|
|
77
|
+
dataset: 'production',
|
|
78
|
+
documentId: 'doc-123',
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
// Change documentId
|
|
82
|
+
rerender({documentId: 'doc-456'})
|
|
83
|
+
|
|
84
|
+
expect(mockSendMessage).toHaveBeenCalledTimes(2)
|
|
85
|
+
expect(mockSendMessage).toHaveBeenLastCalledWith('dashboard/v1/events/agent/resource/update', {
|
|
86
|
+
projectId: 'test-project',
|
|
87
|
+
dataset: 'production',
|
|
88
|
+
documentId: 'doc-456',
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('should not send duplicate updates for the same context', () => {
|
|
93
|
+
const {rerender} = renderHook(
|
|
94
|
+
({documentId}: {documentId: string}) =>
|
|
95
|
+
useAgentResourceContext({
|
|
96
|
+
projectId: 'test-project',
|
|
97
|
+
dataset: 'production',
|
|
98
|
+
documentId,
|
|
99
|
+
}),
|
|
100
|
+
{
|
|
101
|
+
wrapper: AppProviders,
|
|
102
|
+
initialProps: {documentId: 'doc-123'},
|
|
103
|
+
},
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
expect(mockSendMessage).toHaveBeenCalledTimes(1)
|
|
107
|
+
|
|
108
|
+
// Re-render with the same documentId
|
|
109
|
+
rerender({documentId: 'doc-123'})
|
|
110
|
+
|
|
111
|
+
// Should still only be called once
|
|
112
|
+
expect(mockSendMessage).toHaveBeenCalledTimes(1)
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('should warn when projectId is missing', () => {
|
|
116
|
+
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
117
|
+
|
|
118
|
+
renderHook(
|
|
119
|
+
() =>
|
|
120
|
+
useAgentResourceContext({
|
|
121
|
+
projectId: '',
|
|
122
|
+
dataset: 'production',
|
|
123
|
+
documentId: 'doc-123',
|
|
124
|
+
}),
|
|
125
|
+
{wrapper: AppProviders},
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
|
129
|
+
'[useAgentResourceContext] projectId and dataset are required',
|
|
130
|
+
{projectId: '', dataset: 'production'},
|
|
131
|
+
)
|
|
132
|
+
expect(mockSendMessage).not.toHaveBeenCalled()
|
|
133
|
+
|
|
134
|
+
consoleWarnSpy.mockRestore()
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('should warn when dataset is missing', () => {
|
|
138
|
+
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
139
|
+
|
|
140
|
+
renderHook(
|
|
141
|
+
() =>
|
|
142
|
+
useAgentResourceContext({
|
|
143
|
+
projectId: 'test-project',
|
|
144
|
+
dataset: '',
|
|
145
|
+
documentId: 'doc-123',
|
|
146
|
+
}),
|
|
147
|
+
{wrapper: AppProviders},
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
|
151
|
+
'[useAgentResourceContext] projectId and dataset are required',
|
|
152
|
+
{projectId: 'test-project', dataset: ''},
|
|
153
|
+
)
|
|
154
|
+
expect(mockSendMessage).not.toHaveBeenCalled()
|
|
155
|
+
|
|
156
|
+
consoleWarnSpy.mockRestore()
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
it('should handle errors when sending messages', () => {
|
|
160
|
+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
161
|
+
mockSendMessage.mockImplementation(() => {
|
|
162
|
+
throw new Error('Failed to send message')
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
// Should not throw, but should log error
|
|
166
|
+
expect(() =>
|
|
167
|
+
renderHook(
|
|
168
|
+
() =>
|
|
169
|
+
useAgentResourceContext({
|
|
170
|
+
projectId: 'test-project',
|
|
171
|
+
dataset: 'production',
|
|
172
|
+
documentId: 'doc-123',
|
|
173
|
+
}),
|
|
174
|
+
{wrapper: AppProviders},
|
|
175
|
+
),
|
|
176
|
+
).not.toThrow()
|
|
177
|
+
|
|
178
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
179
|
+
'[useAgentResourceContext] Failed to update context:',
|
|
180
|
+
expect.any(Error),
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
consoleErrorSpy.mockRestore()
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
it('should update context when switching between documents', () => {
|
|
187
|
+
const {rerender} = renderHook(
|
|
188
|
+
({documentId}: {documentId: string}) =>
|
|
189
|
+
useAgentResourceContext({
|
|
190
|
+
projectId: 'test-project',
|
|
191
|
+
dataset: 'production',
|
|
192
|
+
documentId,
|
|
193
|
+
}),
|
|
194
|
+
{
|
|
195
|
+
wrapper: AppProviders,
|
|
196
|
+
initialProps: {documentId: 'doc-123'},
|
|
197
|
+
},
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
expect(mockSendMessage).toHaveBeenCalledTimes(1)
|
|
201
|
+
|
|
202
|
+
// Switch to document 456
|
|
203
|
+
rerender({documentId: 'doc-456'})
|
|
204
|
+
expect(mockSendMessage).toHaveBeenCalledTimes(2)
|
|
205
|
+
|
|
206
|
+
// Switch to document 789
|
|
207
|
+
rerender({documentId: 'doc-789'})
|
|
208
|
+
expect(mockSendMessage).toHaveBeenCalledTimes(3)
|
|
209
|
+
|
|
210
|
+
// Switch back to document 123
|
|
211
|
+
rerender({documentId: 'doc-123'})
|
|
212
|
+
expect(mockSendMessage).toHaveBeenCalledTimes(4)
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
it('should update context when document is cleared', () => {
|
|
216
|
+
const {rerender} = renderHook(
|
|
217
|
+
({documentId}: {documentId: string | undefined}) =>
|
|
218
|
+
useAgentResourceContext({
|
|
219
|
+
projectId: 'test-project',
|
|
220
|
+
dataset: 'production',
|
|
221
|
+
documentId,
|
|
222
|
+
}),
|
|
223
|
+
{
|
|
224
|
+
wrapper: AppProviders,
|
|
225
|
+
initialProps: {documentId: 'doc-123' as string | undefined},
|
|
226
|
+
},
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
expect(mockSendMessage).toHaveBeenCalledWith('dashboard/v1/events/agent/resource/update', {
|
|
230
|
+
projectId: 'test-project',
|
|
231
|
+
dataset: 'production',
|
|
232
|
+
documentId: 'doc-123',
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
// Clear documentId
|
|
236
|
+
rerender({documentId: undefined})
|
|
237
|
+
|
|
238
|
+
expect(mockSendMessage).toHaveBeenCalledTimes(2)
|
|
239
|
+
expect(mockSendMessage).toHaveBeenLastCalledWith('dashboard/v1/events/agent/resource/update', {
|
|
240
|
+
projectId: 'test-project',
|
|
241
|
+
dataset: 'production',
|
|
242
|
+
documentId: undefined,
|
|
243
|
+
})
|
|
244
|
+
})
|
|
245
|
+
})
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import {type Events, SDK_CHANNEL_NAME, SDK_NODE_NAME} from '@sanity/message-protocol'
|
|
2
|
+
import {type FrameMessage} from '@sanity/sdk'
|
|
3
|
+
import {useCallback, useEffect, useRef} from 'react'
|
|
4
|
+
|
|
5
|
+
import {useWindowConnection} from '../comlink/useWindowConnection'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @public
|
|
9
|
+
*/
|
|
10
|
+
export interface AgentResourceContextOptions {
|
|
11
|
+
/**
|
|
12
|
+
* The project ID of the current context
|
|
13
|
+
*/
|
|
14
|
+
projectId: string
|
|
15
|
+
/**
|
|
16
|
+
* The dataset of the current context
|
|
17
|
+
*/
|
|
18
|
+
dataset: string
|
|
19
|
+
/**
|
|
20
|
+
* Optional document ID if the user is viewing/editing a specific document
|
|
21
|
+
*/
|
|
22
|
+
documentId?: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @public
|
|
27
|
+
* Hook for emitting agent resource context updates to the Dashboard.
|
|
28
|
+
* This allows the Agent to understand what resource the user is currently
|
|
29
|
+
* interacting with (e.g., which document they're editing).
|
|
30
|
+
*
|
|
31
|
+
* The hook will automatically emit the context when it changes, and also
|
|
32
|
+
* emit the initial context when the hook is first mounted.
|
|
33
|
+
*
|
|
34
|
+
* @category Agent
|
|
35
|
+
* @param options - The resource context options containing projectId, dataset, and optional documentId
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```tsx
|
|
39
|
+
* import {useAgentResourceContext} from '@sanity/sdk-react'
|
|
40
|
+
*
|
|
41
|
+
* function MyComponent() {
|
|
42
|
+
* const documentId = 'my-document-id'
|
|
43
|
+
*
|
|
44
|
+
* // Automatically updates the Agent's context whenever the document changes
|
|
45
|
+
* useAgentResourceContext({
|
|
46
|
+
* projectId: 'my-project',
|
|
47
|
+
* dataset: 'production',
|
|
48
|
+
* documentId,
|
|
49
|
+
* })
|
|
50
|
+
*
|
|
51
|
+
* return <div>Editing document: {documentId}</div>
|
|
52
|
+
* }
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export function useAgentResourceContext(options: AgentResourceContextOptions): void {
|
|
56
|
+
const {projectId, dataset, documentId} = options
|
|
57
|
+
const {sendMessage} = useWindowConnection<Events.AgentResourceUpdateMessage, FrameMessage>({
|
|
58
|
+
name: SDK_NODE_NAME,
|
|
59
|
+
connectTo: SDK_CHANNEL_NAME,
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
// Track the last sent context to avoid duplicate updates
|
|
63
|
+
const lastContextRef = useRef<string | null>(null)
|
|
64
|
+
|
|
65
|
+
const updateContext = useCallback(() => {
|
|
66
|
+
// Validate required fields
|
|
67
|
+
if (!projectId || !dataset) {
|
|
68
|
+
// eslint-disable-next-line no-console
|
|
69
|
+
console.warn('[useAgentResourceContext] projectId and dataset are required', {
|
|
70
|
+
projectId,
|
|
71
|
+
dataset,
|
|
72
|
+
})
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Create a stable key for the current context
|
|
77
|
+
const contextKey = `${projectId}:${dataset}:${documentId || ''}`
|
|
78
|
+
|
|
79
|
+
// Skip if context hasn't changed
|
|
80
|
+
if (lastContextRef.current === contextKey) {
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
const message: Events.AgentResourceUpdateMessage = {
|
|
86
|
+
type: 'dashboard/v1/events/agent/resource/update',
|
|
87
|
+
data: {
|
|
88
|
+
projectId,
|
|
89
|
+
dataset,
|
|
90
|
+
documentId,
|
|
91
|
+
},
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
sendMessage(message.type, message.data)
|
|
95
|
+
lastContextRef.current = contextKey
|
|
96
|
+
} catch (error) {
|
|
97
|
+
// eslint-disable-next-line no-console
|
|
98
|
+
console.error('[useAgentResourceContext] Failed to update context:', error)
|
|
99
|
+
}
|
|
100
|
+
}, [projectId, dataset, documentId, sendMessage])
|
|
101
|
+
|
|
102
|
+
// Update context whenever it changes
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
updateContext()
|
|
105
|
+
}, [updateContext])
|
|
106
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {type DatasetHandle, type DocumentHandle, type DocumentSource} from '@sanity/sdk'
|
|
2
|
+
import {useContext} from 'react'
|
|
3
|
+
|
|
4
|
+
import {SourcesContext} from '../../context/SourcesContext'
|
|
5
|
+
|
|
6
|
+
/** Retrieves the named source from context.
|
|
7
|
+
* @beta
|
|
8
|
+
* @param name - The name of the source to retrieve.
|
|
9
|
+
* @returns The source.
|
|
10
|
+
* @example
|
|
11
|
+
* ```tsx
|
|
12
|
+
* const source = useSource('my-source')
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export function useSource(options: DocumentHandle | DatasetHandle): DocumentSource | undefined {
|
|
16
|
+
const sources = useContext(SourcesContext)
|
|
17
|
+
|
|
18
|
+
// this might return the "default" source in the future once we implement it?
|
|
19
|
+
if (!options.sourceName && !options.source) {
|
|
20
|
+
return undefined
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (options.source) {
|
|
24
|
+
return options.source
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (options.sourceName && !Object.hasOwn(sources, options.sourceName)) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
`There's no source named ${JSON.stringify(options.sourceName)} in context. Please use <SourceProvider>.`,
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return options.sourceName ? sources[options.sourceName] : undefined
|
|
34
|
+
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {renderHook} from '@testing-library/react'
|
|
1
|
+
import {type DocumentHandle} from '@sanity/sdk'
|
|
3
2
|
import {beforeEach, describe, expect, it, vi} from 'vitest'
|
|
4
3
|
|
|
5
|
-
import {
|
|
4
|
+
import {renderHook} from '../../../test/test-utils'
|
|
6
5
|
import {useDispatchIntent} from './useDispatchIntent'
|
|
7
6
|
|
|
8
7
|
// Mock the useWindowConnection hook
|
|
@@ -38,10 +37,7 @@ describe('useDispatchIntent', () => {
|
|
|
38
37
|
})
|
|
39
38
|
|
|
40
39
|
it('should throw error when neither action nor intentId is provided', () => {
|
|
41
|
-
const {result} = renderHook(() =>
|
|
42
|
-
// @ts-expect-error - Testing runtime error when neither is provided
|
|
43
|
-
useDispatchIntent({documentHandle: mockDocumentHandle}),
|
|
44
|
-
)
|
|
40
|
+
const {result} = renderHook(() => useDispatchIntent({documentHandle: mockDocumentHandle}))
|
|
45
41
|
|
|
46
42
|
expect(() => result.current.dispatchIntent()).toThrow(
|
|
47
43
|
'useDispatchIntent: Either `action` or `intentId` must be provided.',
|
|
@@ -65,9 +61,13 @@ describe('useDispatchIntent', () => {
|
|
|
65
61
|
})
|
|
66
62
|
|
|
67
63
|
it('should use memoized dispatchIntent function', () => {
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
64
|
+
const params = {action: 'edit' as const, documentHandle: mockDocumentHandle}
|
|
65
|
+
const {result, rerender} = renderHook(
|
|
66
|
+
({params: hookParams}: {params: typeof params}) => useDispatchIntent(hookParams),
|
|
67
|
+
{
|
|
68
|
+
initialProps: {params},
|
|
69
|
+
},
|
|
70
|
+
)
|
|
71
71
|
|
|
72
72
|
const firstDispatchIntent = result.current.dispatchIntent
|
|
73
73
|
|
|
@@ -78,9 +78,12 @@ describe('useDispatchIntent', () => {
|
|
|
78
78
|
})
|
|
79
79
|
|
|
80
80
|
it('should create new dispatchIntent function when documentHandle changes', () => {
|
|
81
|
-
const {result, rerender} = renderHook(
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
const {result, rerender} = renderHook(
|
|
82
|
+
(params: {action: 'edit'; documentHandle: DocumentHandle}) => useDispatchIntent(params),
|
|
83
|
+
{
|
|
84
|
+
initialProps: {action: 'edit' as const, documentHandle: mockDocumentHandle},
|
|
85
|
+
},
|
|
86
|
+
)
|
|
84
87
|
|
|
85
88
|
const firstDispatchIntent = result.current.dispatchIntent
|
|
86
89
|
|
|
@@ -91,7 +94,7 @@ describe('useDispatchIntent', () => {
|
|
|
91
94
|
dataset: 'new-dataset',
|
|
92
95
|
}
|
|
93
96
|
|
|
94
|
-
rerender({
|
|
97
|
+
rerender({action: 'edit' as const, documentHandle: newDocumentHandle})
|
|
95
98
|
|
|
96
99
|
expect(result.current.dispatchIntent).not.toBe(firstDispatchIntent)
|
|
97
100
|
})
|
|
@@ -163,10 +166,10 @@ describe('useDispatchIntent', () => {
|
|
|
163
166
|
})
|
|
164
167
|
|
|
165
168
|
it('should send intent message with media library source', () => {
|
|
166
|
-
const mockMediaLibraryHandle:
|
|
169
|
+
const mockMediaLibraryHandle: DocumentHandle = {
|
|
167
170
|
documentId: 'test-asset-id',
|
|
168
171
|
documentType: 'sanity.asset',
|
|
169
|
-
|
|
172
|
+
sourceName: 'media-library',
|
|
170
173
|
}
|
|
171
174
|
|
|
172
175
|
const {result} = renderHook(() =>
|
|
@@ -185,17 +188,17 @@ describe('useDispatchIntent', () => {
|
|
|
185
188
|
type: 'sanity.asset',
|
|
186
189
|
},
|
|
187
190
|
resource: {
|
|
188
|
-
id: '
|
|
191
|
+
id: 'media-library-id',
|
|
189
192
|
type: 'media-library',
|
|
190
193
|
},
|
|
191
194
|
})
|
|
192
195
|
})
|
|
193
196
|
|
|
194
197
|
it('should send intent message with canvas source', () => {
|
|
195
|
-
const mockCanvasHandle:
|
|
198
|
+
const mockCanvasHandle: DocumentHandle = {
|
|
196
199
|
documentId: 'test-canvas-document-id',
|
|
197
200
|
documentType: 'sanity.canvas.document',
|
|
198
|
-
|
|
201
|
+
sourceName: 'canvas',
|
|
199
202
|
}
|
|
200
203
|
|
|
201
204
|
const {result} = renderHook(() =>
|
|
@@ -214,7 +217,7 @@ describe('useDispatchIntent', () => {
|
|
|
214
217
|
type: 'sanity.canvas.document',
|
|
215
218
|
},
|
|
216
219
|
resource: {
|
|
217
|
-
id: '
|
|
220
|
+
id: 'canvas-id',
|
|
218
221
|
type: 'canvas',
|
|
219
222
|
},
|
|
220
223
|
})
|
|
@@ -230,12 +233,12 @@ describe('useDispatchIntent', () => {
|
|
|
230
233
|
const {result} = renderHook(() =>
|
|
231
234
|
useDispatchIntent({
|
|
232
235
|
action: 'edit',
|
|
233
|
-
documentHandle: invalidHandle as unknown as
|
|
236
|
+
documentHandle: invalidHandle as unknown as DocumentHandle,
|
|
234
237
|
}),
|
|
235
238
|
)
|
|
236
239
|
|
|
237
240
|
expect(() => result.current.dispatchIntent()).toThrow(
|
|
238
|
-
'useDispatchIntent: Either `
|
|
241
|
+
'useDispatchIntent: Either `sourceName` or both `projectId` and `dataset` must be provided in documentHandle.',
|
|
239
242
|
)
|
|
240
243
|
})
|
|
241
244
|
})
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import {SDK_CHANNEL_NAME, SDK_NODE_NAME} from '@sanity/message-protocol'
|
|
2
|
-
import {type FrameMessage} from '@sanity/sdk'
|
|
2
|
+
import {type DocumentHandle, type FrameMessage} from '@sanity/sdk'
|
|
3
3
|
import {useCallback} from 'react'
|
|
4
4
|
|
|
5
5
|
import {useWindowConnection} from '../comlink/useWindowConnection'
|
|
6
|
-
import {
|
|
7
|
-
import {getResourceIdFromDocumentHandle} from './utils/getResourceIdFromDocumentHandle'
|
|
6
|
+
import {useResourceIdFromDocumentHandle} from './utils/useResourceIdFromDocumentHandle'
|
|
8
7
|
|
|
9
8
|
/**
|
|
10
9
|
* Message type for sending intents to the dashboard
|
|
@@ -42,7 +41,7 @@ interface DispatchIntent {
|
|
|
42
41
|
interface UseDispatchIntentParams {
|
|
43
42
|
action?: 'edit'
|
|
44
43
|
intentId?: string
|
|
45
|
-
documentHandle:
|
|
44
|
+
documentHandle: DocumentHandle
|
|
46
45
|
parameters?: Record<string, unknown>
|
|
47
46
|
}
|
|
48
47
|
|
|
@@ -104,13 +103,15 @@ export function useDispatchIntent(params: UseDispatchIntentParams): DispatchInte
|
|
|
104
103
|
connectTo: SDK_CHANNEL_NAME,
|
|
105
104
|
})
|
|
106
105
|
|
|
106
|
+
const resource = useResourceIdFromDocumentHandle(documentHandle)
|
|
107
|
+
|
|
107
108
|
const dispatchIntent = useCallback(() => {
|
|
108
109
|
try {
|
|
109
110
|
if (!action && !intentId) {
|
|
110
111
|
throw new Error('useDispatchIntent: Either `action` or `intentId` must be provided.')
|
|
111
112
|
}
|
|
112
113
|
|
|
113
|
-
const {projectId, dataset,
|
|
114
|
+
const {projectId, dataset, sourceName} = documentHandle
|
|
114
115
|
|
|
115
116
|
if (action && intentId) {
|
|
116
117
|
// eslint-disable-next-line no-console -- warn if both action and intentId are provided
|
|
@@ -119,14 +120,12 @@ export function useDispatchIntent(params: UseDispatchIntentParams): DispatchInte
|
|
|
119
120
|
)
|
|
120
121
|
}
|
|
121
122
|
|
|
122
|
-
if (!
|
|
123
|
+
if (!sourceName && (!projectId || !dataset)) {
|
|
123
124
|
throw new Error(
|
|
124
|
-
'useDispatchIntent: Either `
|
|
125
|
+
'useDispatchIntent: Either `sourceName` or both `projectId` and `dataset` must be provided in documentHandle.',
|
|
125
126
|
)
|
|
126
127
|
}
|
|
127
128
|
|
|
128
|
-
const resource = getResourceIdFromDocumentHandle(documentHandle)
|
|
129
|
-
|
|
130
129
|
const message: IntentMessage = {
|
|
131
130
|
type: 'dashboard/v1/events/intents/dispatch-intent',
|
|
132
131
|
data: {
|
|
@@ -150,7 +149,7 @@ export function useDispatchIntent(params: UseDispatchIntentParams): DispatchInte
|
|
|
150
149
|
console.error('Failed to dispatch intent:', error)
|
|
151
150
|
throw error
|
|
152
151
|
}
|
|
153
|
-
}, [action, intentId, documentHandle, parameters, sendMessage])
|
|
152
|
+
}, [action, intentId, documentHandle, parameters, sendMessage, resource.id, resource.type])
|
|
154
153
|
|
|
155
154
|
return {
|
|
156
155
|
dispatchIntent,
|