@sanity/sdk-react 0.0.0-alpha.20 → 0.0.0-alpha.4
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 +73 -34
- package/dist/_chunks-es/context.js +8 -0
- package/dist/_chunks-es/context.js.map +1 -0
- package/dist/_chunks-es/useLogOut.js +44 -0
- package/dist/_chunks-es/useLogOut.js.map +1 -0
- package/dist/components.d.ts +83 -0
- package/dist/components.js +132 -0
- package/dist/components.js.map +1 -0
- package/dist/context.d.ts +39 -0
- package/dist/context.js +5 -0
- package/dist/context.js.map +1 -0
- package/dist/hooks.d.ts +277 -0
- package/dist/hooks.js +153 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.d.ts +2 -4742
- package/dist/index.js +2 -1054
- package/dist/index.js.map +1 -1
- package/package.json +43 -22
- package/src/_exports/components.ts +2 -0
- package/src/_exports/context.ts +2 -0
- package/src/_exports/hooks.ts +21 -0
- package/src/_exports/index.ts +10 -66
- package/src/components/Login/LoginLinks.test.tsx +3 -2
- package/src/components/SanityApp.test.tsx +2 -104
- package/src/components/SanityApp.tsx +16 -80
- package/src/components/auth/AuthBoundary.test.tsx +10 -4
- package/src/components/auth/AuthBoundary.tsx +2 -18
- package/src/components/auth/Login.test.tsx +9 -2
- package/src/components/auth/Login.tsx +0 -1
- package/src/components/auth/LoginCallback.test.tsx +9 -2
- package/src/components/auth/LoginError.test.tsx +10 -2
- package/src/components/auth/LoginFooter.test.tsx +9 -2
- package/src/components/auth/LoginLayout.test.tsx +9 -2
- package/src/context/SanityProvider.test.tsx +1 -1
- package/src/context/SanityProvider.tsx +13 -21
- package/src/hooks/auth/useAuthState.tsx +5 -4
- package/src/hooks/auth/useAuthToken.tsx +1 -1
- package/src/hooks/auth/useCurrentUser.tsx +4 -27
- package/src/hooks/auth/useHandleCallback.tsx +0 -1
- package/src/hooks/auth/useLogOut.tsx +1 -1
- package/src/hooks/auth/useLoginUrls.tsx +0 -1
- package/src/hooks/client/useClient.test.tsx +130 -0
- package/src/hooks/client/useClient.ts +30 -8
- package/src/hooks/comlink/useFrameConnection.test.tsx +10 -55
- package/src/hooks/comlink/useFrameConnection.ts +47 -43
- package/src/hooks/comlink/useWindowConnection.test.ts +12 -53
- package/src/hooks/comlink/useWindowConnection.ts +33 -73
- package/src/hooks/context/useSanityInstance.test.tsx +1 -1
- package/src/hooks/context/useSanityInstance.ts +7 -23
- package/src/hooks/documentCollection/useDocuments.test.ts +130 -0
- package/src/hooks/documentCollection/useDocuments.ts +87 -0
- package/src/hooks/helpers/createCallbackHook.tsx +2 -3
- package/src/hooks/helpers/createStateSourceHook.test.tsx +0 -66
- package/src/hooks/helpers/createStateSourceHook.tsx +10 -29
- package/src/hooks/preview/usePreview.test.tsx +10 -19
- package/src/hooks/preview/usePreview.tsx +12 -66
- package/src/components/SDKProvider.test.tsx +0 -79
- package/src/components/SDKProvider.tsx +0 -42
- package/src/components/auth/authTestHelpers.tsx +0 -11
- package/src/components/utils.ts +0 -22
- package/src/context/SanityInstanceContext.ts +0 -4
- package/src/hooks/_synchronous-groq-js.mjs +0 -4
- package/src/hooks/auth/useDashboardOrganizationId.test.tsx +0 -42
- package/src/hooks/auth/useDashboardOrganizationId.tsx +0 -29
- package/src/hooks/comlink/useManageFavorite.test.ts +0 -106
- package/src/hooks/comlink/useManageFavorite.ts +0 -101
- package/src/hooks/comlink/useRecordDocumentHistoryEvent.test.ts +0 -77
- package/src/hooks/comlink/useRecordDocumentHistoryEvent.ts +0 -79
- package/src/hooks/dashboard/useNavigateToStudioDocument.ts +0 -97
- package/src/hooks/dashboard/useStudioWorkspacesByResourceId.test.tsx +0 -274
- package/src/hooks/dashboard/useStudioWorkspacesByResourceId.ts +0 -91
- package/src/hooks/datasets/useDatasets.ts +0 -37
- package/src/hooks/document/useApplyActions.test.ts +0 -25
- package/src/hooks/document/useApplyActions.ts +0 -74
- package/src/hooks/document/useDocument.test.ts +0 -81
- package/src/hooks/document/useDocument.ts +0 -107
- package/src/hooks/document/useDocumentEvent.test.ts +0 -63
- package/src/hooks/document/useDocumentEvent.ts +0 -54
- package/src/hooks/document/useDocumentSyncStatus.test.ts +0 -16
- package/src/hooks/document/useDocumentSyncStatus.ts +0 -30
- package/src/hooks/document/useEditDocument.test.ts +0 -179
- package/src/hooks/document/useEditDocument.ts +0 -195
- package/src/hooks/document/usePermissions.ts +0 -82
- package/src/hooks/infiniteList/useInfiniteList.test.tsx +0 -152
- package/src/hooks/infiniteList/useInfiniteList.ts +0 -174
- package/src/hooks/paginatedList/usePaginatedList.test.tsx +0 -259
- package/src/hooks/paginatedList/usePaginatedList.ts +0 -290
- package/src/hooks/projection/useProjection.test.tsx +0 -218
- package/src/hooks/projection/useProjection.ts +0 -147
- package/src/hooks/projects/useProject.ts +0 -45
- package/src/hooks/projects/useProjects.ts +0 -41
- package/src/hooks/query/useQuery.test.tsx +0 -188
- package/src/hooks/query/useQuery.ts +0 -103
- package/src/hooks/users/useUsers.test.ts +0 -163
- package/src/hooks/users/useUsers.ts +0 -107
- package/src/utils/getEnv.ts +0 -21
- package/src/version.ts +0 -8
|
@@ -1,97 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,274 +0,0 @@
|
|
|
1
|
-
import {type Message, type Status} from '@sanity/comlink'
|
|
2
|
-
import {renderHook, waitFor} from '@testing-library/react'
|
|
3
|
-
import {describe, expect, it, vi} from 'vitest'
|
|
4
|
-
|
|
5
|
-
import {useWindowConnection, type WindowConnection} from '../comlink/useWindowConnection'
|
|
6
|
-
import {useStudioWorkspacesByResourceId} from './useStudioWorkspacesByResourceId'
|
|
7
|
-
|
|
8
|
-
vi.mock('../comlink/useWindowConnection', () => ({
|
|
9
|
-
useWindowConnection: vi.fn(),
|
|
10
|
-
}))
|
|
11
|
-
|
|
12
|
-
const mockWorkspaceData = {
|
|
13
|
-
context: {
|
|
14
|
-
availableResources: [
|
|
15
|
-
{
|
|
16
|
-
projectId: 'project1',
|
|
17
|
-
workspaces: [
|
|
18
|
-
{
|
|
19
|
-
name: 'workspace1',
|
|
20
|
-
title: 'Workspace 1',
|
|
21
|
-
basePath: '/workspace1',
|
|
22
|
-
dataset: 'dataset1',
|
|
23
|
-
userApplicationId: 'user1',
|
|
24
|
-
url: 'https://test.sanity.studio',
|
|
25
|
-
_ref: 'user1-workspace1',
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
name: 'workspace2',
|
|
29
|
-
title: 'Workspace 2',
|
|
30
|
-
basePath: '/workspace2',
|
|
31
|
-
dataset: 'dataset1',
|
|
32
|
-
userApplicationId: 'user1',
|
|
33
|
-
url: 'https://test.sanity.studio',
|
|
34
|
-
_ref: 'user1-workspace2',
|
|
35
|
-
},
|
|
36
|
-
],
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
projectId: 'project2',
|
|
40
|
-
workspaces: [
|
|
41
|
-
{
|
|
42
|
-
name: 'workspace3',
|
|
43
|
-
title: 'Workspace 3',
|
|
44
|
-
basePath: '/workspace3',
|
|
45
|
-
dataset: 'dataset2',
|
|
46
|
-
userApplicationId: 'user2',
|
|
47
|
-
url: 'https://test.sanity.studio',
|
|
48
|
-
_ref: 'user2-workspace3',
|
|
49
|
-
},
|
|
50
|
-
],
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
// Project without workspaces
|
|
54
|
-
projectId: 'project3',
|
|
55
|
-
workspaces: [],
|
|
56
|
-
},
|
|
57
|
-
],
|
|
58
|
-
},
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
describe('useStudioWorkspacesByResourceId', () => {
|
|
62
|
-
it('should return empty workspaces and connected=false when not connected', async () => {
|
|
63
|
-
// Create a mock that captures the onStatus callback
|
|
64
|
-
let capturedOnStatus: ((status: Status) => void) | undefined
|
|
65
|
-
|
|
66
|
-
vi.mocked(useWindowConnection).mockImplementation(({onStatus}) => {
|
|
67
|
-
capturedOnStatus = onStatus
|
|
68
|
-
|
|
69
|
-
return {
|
|
70
|
-
fetch: undefined,
|
|
71
|
-
sendMessage: vi.fn(),
|
|
72
|
-
} as unknown as WindowConnection<Message>
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
const {result} = renderHook(() => useStudioWorkspacesByResourceId())
|
|
76
|
-
|
|
77
|
-
// Call onStatus with 'idle' to simulate not connected
|
|
78
|
-
if (capturedOnStatus) capturedOnStatus('idle')
|
|
79
|
-
|
|
80
|
-
expect(result.current).toEqual({
|
|
81
|
-
workspacesByResourceId: {},
|
|
82
|
-
error: null,
|
|
83
|
-
isConnected: false,
|
|
84
|
-
})
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
it('should process workspaces into lookup by projectId:dataset', async () => {
|
|
88
|
-
const mockFetch = vi.fn().mockResolvedValue(mockWorkspaceData)
|
|
89
|
-
let capturedOnStatus: ((status: Status) => void) | undefined
|
|
90
|
-
|
|
91
|
-
vi.mocked(useWindowConnection).mockImplementation(({onStatus}) => {
|
|
92
|
-
capturedOnStatus = onStatus
|
|
93
|
-
|
|
94
|
-
return {
|
|
95
|
-
fetch: mockFetch,
|
|
96
|
-
sendMessage: vi.fn(),
|
|
97
|
-
} as unknown as WindowConnection<Message>
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
const {result} = renderHook(() => useStudioWorkspacesByResourceId())
|
|
101
|
-
|
|
102
|
-
// Call onStatus with 'connected' to simulate connected state
|
|
103
|
-
if (capturedOnStatus) capturedOnStatus('connected')
|
|
104
|
-
|
|
105
|
-
await waitFor(() => {
|
|
106
|
-
expect(result.current.workspacesByResourceId).toEqual({
|
|
107
|
-
'project1:dataset1': [
|
|
108
|
-
{
|
|
109
|
-
name: 'workspace1',
|
|
110
|
-
title: 'Workspace 1',
|
|
111
|
-
basePath: '/workspace1',
|
|
112
|
-
dataset: 'dataset1',
|
|
113
|
-
userApplicationId: 'user1',
|
|
114
|
-
url: 'https://test.sanity.studio',
|
|
115
|
-
_ref: 'user1-workspace1',
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
name: 'workspace2',
|
|
119
|
-
title: 'Workspace 2',
|
|
120
|
-
basePath: '/workspace2',
|
|
121
|
-
dataset: 'dataset1',
|
|
122
|
-
userApplicationId: 'user1',
|
|
123
|
-
url: 'https://test.sanity.studio',
|
|
124
|
-
_ref: 'user1-workspace2',
|
|
125
|
-
},
|
|
126
|
-
],
|
|
127
|
-
'project2:dataset2': [
|
|
128
|
-
{
|
|
129
|
-
name: 'workspace3',
|
|
130
|
-
title: 'Workspace 3',
|
|
131
|
-
basePath: '/workspace3',
|
|
132
|
-
dataset: 'dataset2',
|
|
133
|
-
userApplicationId: 'user2',
|
|
134
|
-
url: 'https://test.sanity.studio',
|
|
135
|
-
_ref: 'user2-workspace3',
|
|
136
|
-
},
|
|
137
|
-
],
|
|
138
|
-
})
|
|
139
|
-
expect(result.current.error).toBeNull()
|
|
140
|
-
expect(result.current.isConnected).toBe(true)
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
expect(mockFetch).toHaveBeenCalledWith('core/v1/bridge/context', undefined, expect.any(Object))
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
it('should handle fetch errors', async () => {
|
|
147
|
-
const mockFetch = vi.fn().mockRejectedValue(new Error('Failed to fetch'))
|
|
148
|
-
let capturedOnStatus: ((status: Status) => void) | undefined
|
|
149
|
-
|
|
150
|
-
vi.mocked(useWindowConnection).mockImplementation(({onStatus}) => {
|
|
151
|
-
capturedOnStatus = onStatus
|
|
152
|
-
|
|
153
|
-
return {
|
|
154
|
-
fetch: mockFetch,
|
|
155
|
-
sendMessage: vi.fn(),
|
|
156
|
-
} as unknown as WindowConnection<Message>
|
|
157
|
-
})
|
|
158
|
-
|
|
159
|
-
const {result} = renderHook(() => useStudioWorkspacesByResourceId())
|
|
160
|
-
|
|
161
|
-
// Call onStatus with 'connected' to simulate connected state
|
|
162
|
-
if (capturedOnStatus) capturedOnStatus('connected')
|
|
163
|
-
|
|
164
|
-
await waitFor(() => {
|
|
165
|
-
expect(result.current.workspacesByResourceId).toEqual({})
|
|
166
|
-
expect(result.current.error).toBe('Failed to fetch workspaces')
|
|
167
|
-
expect(result.current.isConnected).toBe(true)
|
|
168
|
-
})
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
it('should handle AbortError silently', async () => {
|
|
172
|
-
const abortError = new Error('Aborted')
|
|
173
|
-
abortError.name = 'AbortError'
|
|
174
|
-
const mockFetch = vi.fn().mockRejectedValue(abortError)
|
|
175
|
-
let capturedOnStatus: ((status: Status) => void) | undefined
|
|
176
|
-
|
|
177
|
-
vi.mocked(useWindowConnection).mockImplementation(({onStatus}) => {
|
|
178
|
-
capturedOnStatus = onStatus
|
|
179
|
-
|
|
180
|
-
return {
|
|
181
|
-
fetch: mockFetch,
|
|
182
|
-
sendMessage: vi.fn(),
|
|
183
|
-
} as unknown as WindowConnection<Message>
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
const {result} = renderHook(() => useStudioWorkspacesByResourceId())
|
|
187
|
-
|
|
188
|
-
// Call onStatus with 'connected' to simulate connected state
|
|
189
|
-
if (capturedOnStatus) capturedOnStatus('connected')
|
|
190
|
-
|
|
191
|
-
await waitFor(() => {
|
|
192
|
-
expect(result.current.workspacesByResourceId).toEqual({})
|
|
193
|
-
expect(result.current.error).toBeNull()
|
|
194
|
-
expect(result.current.isConnected).toBe(true)
|
|
195
|
-
})
|
|
196
|
-
})
|
|
197
|
-
|
|
198
|
-
it('should handle projects without workspaces', async () => {
|
|
199
|
-
const mockFetch = vi.fn().mockResolvedValue({
|
|
200
|
-
context: {
|
|
201
|
-
availableResources: [
|
|
202
|
-
{
|
|
203
|
-
projectId: 'project1',
|
|
204
|
-
workspaces: [],
|
|
205
|
-
},
|
|
206
|
-
],
|
|
207
|
-
},
|
|
208
|
-
})
|
|
209
|
-
let capturedOnStatus: ((status: Status) => void) | undefined
|
|
210
|
-
|
|
211
|
-
vi.mocked(useWindowConnection).mockImplementation(({onStatus}) => {
|
|
212
|
-
capturedOnStatus = onStatus
|
|
213
|
-
|
|
214
|
-
return {
|
|
215
|
-
fetch: mockFetch,
|
|
216
|
-
sendMessage: vi.fn(),
|
|
217
|
-
} as unknown as WindowConnection<Message>
|
|
218
|
-
})
|
|
219
|
-
|
|
220
|
-
const {result} = renderHook(() => useStudioWorkspacesByResourceId())
|
|
221
|
-
|
|
222
|
-
// Call onStatus with 'connected' to simulate connected state
|
|
223
|
-
if (capturedOnStatus) capturedOnStatus('connected')
|
|
224
|
-
|
|
225
|
-
await waitFor(() => {
|
|
226
|
-
expect(result.current.workspacesByResourceId).toEqual({})
|
|
227
|
-
expect(result.current.error).toBeNull()
|
|
228
|
-
expect(result.current.isConnected).toBe(true)
|
|
229
|
-
})
|
|
230
|
-
})
|
|
231
|
-
|
|
232
|
-
it('should handle projects without projectId', async () => {
|
|
233
|
-
const mockFetch = vi.fn().mockResolvedValue({
|
|
234
|
-
context: {
|
|
235
|
-
availableResources: [
|
|
236
|
-
{
|
|
237
|
-
workspaces: [
|
|
238
|
-
{
|
|
239
|
-
name: 'workspace1',
|
|
240
|
-
title: 'Workspace 1',
|
|
241
|
-
basePath: '/workspace1',
|
|
242
|
-
dataset: 'dataset1',
|
|
243
|
-
userApplicationId: 'user1',
|
|
244
|
-
url: 'https://test.sanity.studio',
|
|
245
|
-
_ref: 'user1-workspace1',
|
|
246
|
-
},
|
|
247
|
-
],
|
|
248
|
-
},
|
|
249
|
-
],
|
|
250
|
-
},
|
|
251
|
-
})
|
|
252
|
-
let capturedOnStatus: ((status: Status) => void) | undefined
|
|
253
|
-
|
|
254
|
-
vi.mocked(useWindowConnection).mockImplementation(({onStatus}) => {
|
|
255
|
-
capturedOnStatus = onStatus
|
|
256
|
-
|
|
257
|
-
return {
|
|
258
|
-
fetch: mockFetch,
|
|
259
|
-
sendMessage: vi.fn(),
|
|
260
|
-
} as unknown as WindowConnection<Message>
|
|
261
|
-
})
|
|
262
|
-
|
|
263
|
-
const {result} = renderHook(() => useStudioWorkspacesByResourceId())
|
|
264
|
-
|
|
265
|
-
// Call onStatus with 'connected' to simulate connected state
|
|
266
|
-
if (capturedOnStatus) capturedOnStatus('connected')
|
|
267
|
-
|
|
268
|
-
await waitFor(() => {
|
|
269
|
-
expect(result.current.workspacesByResourceId).toEqual({})
|
|
270
|
-
expect(result.current.error).toBeNull()
|
|
271
|
-
expect(result.current.isConnected).toBe(true)
|
|
272
|
-
})
|
|
273
|
-
})
|
|
274
|
-
})
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import {type Status} from '@sanity/comlink'
|
|
2
|
-
import {useEffect, useState} from 'react'
|
|
3
|
-
|
|
4
|
-
import {useWindowConnection} from '../comlink/useWindowConnection'
|
|
5
|
-
|
|
6
|
-
interface Workspace {
|
|
7
|
-
name: string
|
|
8
|
-
title: string
|
|
9
|
-
basePath: string
|
|
10
|
-
dataset: string
|
|
11
|
-
userApplicationId: string
|
|
12
|
-
url: string
|
|
13
|
-
_ref: string
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
interface WorkspacesByResourceId {
|
|
17
|
-
[key: string]: Workspace[] // key format: `${projectId}:${dataset}`
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
interface StudioWorkspacesResult {
|
|
21
|
-
workspacesByResourceId: WorkspacesByResourceId
|
|
22
|
-
error: string | null
|
|
23
|
-
isConnected: boolean
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Hook that fetches studio workspaces and organizes them by projectId:dataset
|
|
28
|
-
* @internal
|
|
29
|
-
*/
|
|
30
|
-
export function useStudioWorkspacesByResourceId(): StudioWorkspacesResult {
|
|
31
|
-
const [workspacesByResourceId, setWorkspacesByResourceId] = useState<WorkspacesByResourceId>({})
|
|
32
|
-
const [status, setStatus] = useState<Status>('idle')
|
|
33
|
-
const [error, setError] = useState<string | null>(null)
|
|
34
|
-
|
|
35
|
-
const {fetch} = useWindowConnection({
|
|
36
|
-
name: 'core/nodes/sdk',
|
|
37
|
-
connectTo: 'core/channels/sdk',
|
|
38
|
-
onStatus: setStatus,
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
// Once computed, this should probably be in a store and poll for changes
|
|
42
|
-
// However, our stores are currently being refactored
|
|
43
|
-
useEffect(() => {
|
|
44
|
-
if (!fetch || status !== 'connected') return
|
|
45
|
-
|
|
46
|
-
async function fetchWorkspaces(signal: AbortSignal) {
|
|
47
|
-
try {
|
|
48
|
-
const data = await fetch<{
|
|
49
|
-
context: {availableResources: Array<{projectId: string; workspaces: Workspace[]}>}
|
|
50
|
-
}>('core/v1/bridge/context', undefined, {signal})
|
|
51
|
-
|
|
52
|
-
const workspaceMap: WorkspacesByResourceId = {}
|
|
53
|
-
|
|
54
|
-
data.context.availableResources.forEach((resource) => {
|
|
55
|
-
if (!resource.projectId || !resource.workspaces?.length) return
|
|
56
|
-
|
|
57
|
-
resource.workspaces.forEach((workspace) => {
|
|
58
|
-
const key = `${resource.projectId}:${workspace.dataset}`
|
|
59
|
-
if (!workspaceMap[key]) {
|
|
60
|
-
workspaceMap[key] = []
|
|
61
|
-
}
|
|
62
|
-
workspaceMap[key].push(workspace)
|
|
63
|
-
})
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
setWorkspacesByResourceId(workspaceMap)
|
|
67
|
-
setError(null)
|
|
68
|
-
} catch (err: unknown) {
|
|
69
|
-
if (err instanceof Error) {
|
|
70
|
-
if (err.name === 'AbortError') {
|
|
71
|
-
return
|
|
72
|
-
}
|
|
73
|
-
setError('Failed to fetch workspaces')
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const controller = new AbortController()
|
|
79
|
-
fetchWorkspaces(controller.signal)
|
|
80
|
-
|
|
81
|
-
return () => {
|
|
82
|
-
controller.abort()
|
|
83
|
-
}
|
|
84
|
-
}, [fetch, status])
|
|
85
|
-
|
|
86
|
-
return {
|
|
87
|
-
workspacesByResourceId,
|
|
88
|
-
error,
|
|
89
|
-
isConnected: status === 'connected',
|
|
90
|
-
}
|
|
91
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import {type DatasetsResponse} from '@sanity/client'
|
|
2
|
-
import {getDatasetsState, resolveDatasets, type SanityInstance, type StateSource} from '@sanity/sdk'
|
|
3
|
-
|
|
4
|
-
import {createStateSourceHook} from '../helpers/createStateSourceHook'
|
|
5
|
-
|
|
6
|
-
type UseDatasets = {
|
|
7
|
-
/**
|
|
8
|
-
*
|
|
9
|
-
* Returns metadata for each dataset in your organization.
|
|
10
|
-
*
|
|
11
|
-
* @category Datasets
|
|
12
|
-
* @returns The metadata for your organization's datasets
|
|
13
|
-
*
|
|
14
|
-
* @example
|
|
15
|
-
* ```tsx
|
|
16
|
-
* const datasets = useDatasets()
|
|
17
|
-
*
|
|
18
|
-
* return (
|
|
19
|
-
* <select>
|
|
20
|
-
* {datasets.map((dataset) => (
|
|
21
|
-
* <option key={dataset.name}>{dataset.name}</option>
|
|
22
|
-
* ))}
|
|
23
|
-
* </select>
|
|
24
|
-
* )
|
|
25
|
-
* ```
|
|
26
|
-
*
|
|
27
|
-
*/
|
|
28
|
-
(): DatasetsResponse
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** @public */
|
|
32
|
-
export const useDatasets: UseDatasets = createStateSourceHook({
|
|
33
|
-
// remove `undefined` since we're suspending when that is the case
|
|
34
|
-
getState: getDatasetsState as (instance: SanityInstance) => StateSource<DatasetsResponse>,
|
|
35
|
-
shouldSuspend: (instance) => getDatasetsState(instance).getCurrent() === undefined,
|
|
36
|
-
suspender: resolveDatasets,
|
|
37
|
-
})
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import {applyActions, createDocument, type ResourceId} from '@sanity/sdk'
|
|
2
|
-
import {describe, it} from 'vitest'
|
|
3
|
-
|
|
4
|
-
import {createCallbackHook} from '../helpers/createCallbackHook'
|
|
5
|
-
|
|
6
|
-
vi.mock('../helpers/createCallbackHook', () => ({
|
|
7
|
-
createCallbackHook: vi.fn((cb) => () => cb),
|
|
8
|
-
}))
|
|
9
|
-
vi.mock('@sanity/sdk', async (importOriginal) => {
|
|
10
|
-
const original = await importOriginal<typeof import('@sanity/sdk')>()
|
|
11
|
-
return {...original, applyActions: vi.fn()}
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
describe('useApplyActions', () => {
|
|
15
|
-
it('calls `createCallbackHook` with `applyActions`', async () => {
|
|
16
|
-
const {useApplyActions} = await import('./useApplyActions')
|
|
17
|
-
const resourceId: ResourceId = 'project1.dataset1'
|
|
18
|
-
expect(createCallbackHook).not.toHaveBeenCalled()
|
|
19
|
-
|
|
20
|
-
expect(applyActions).not.toHaveBeenCalled()
|
|
21
|
-
const apply = useApplyActions(resourceId)
|
|
22
|
-
apply(createDocument({_type: 'author'}))
|
|
23
|
-
expect(applyActions).toHaveBeenCalledWith(createDocument({_type: 'author'}))
|
|
24
|
-
})
|
|
25
|
-
})
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type ActionsResult,
|
|
3
|
-
applyActions,
|
|
4
|
-
type ApplyActionsOptions,
|
|
5
|
-
type DocumentAction,
|
|
6
|
-
type ResourceId,
|
|
7
|
-
} from '@sanity/sdk'
|
|
8
|
-
import {type SanityDocument} from '@sanity/types'
|
|
9
|
-
|
|
10
|
-
import {createCallbackHook} from '../helpers/createCallbackHook'
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
*
|
|
14
|
-
* @beta
|
|
15
|
-
*
|
|
16
|
-
* Provides a callback for applying one or more actions to a document.
|
|
17
|
-
*
|
|
18
|
-
* @category Documents
|
|
19
|
-
* @param resourceId - The resource ID of the document to apply actions to. If not provided, the document will use the default resource.
|
|
20
|
-
* @returns A function that takes one more more {@link DocumentAction}s and returns a promise that resolves to an {@link ActionsResult}.
|
|
21
|
-
* @example Publish or unpublish a document
|
|
22
|
-
* ```
|
|
23
|
-
* import { publishDocument, unpublishDocument } from '@sanity/sdk'
|
|
24
|
-
* import { useApplyActions } from '@sanity/sdk-react'
|
|
25
|
-
*
|
|
26
|
-
* const apply = useApplyActions()
|
|
27
|
-
* const myDocument = { _id: 'my-document-id', _type: 'my-document-type' }
|
|
28
|
-
*
|
|
29
|
-
* return (
|
|
30
|
-
* <button onClick={() => apply(publishDocument(myDocument))}>Publish</button>
|
|
31
|
-
* <button onClick={() => apply(unpublishDocument(myDocument))}>Unpublish</button>
|
|
32
|
-
* )
|
|
33
|
-
* ```
|
|
34
|
-
*
|
|
35
|
-
* @example Create and publish a new document
|
|
36
|
-
* ```
|
|
37
|
-
* import { createDocument, publishDocument } from '@sanity/sdk'
|
|
38
|
-
* import { useApplyActions } from '@sanity/sdk-react'
|
|
39
|
-
*
|
|
40
|
-
* const apply = useApplyActions()
|
|
41
|
-
*
|
|
42
|
-
* const handleCreateAndPublish = () => {
|
|
43
|
-
* const handle = { _id: window.crypto.randomUUID(), _type: 'my-document-type' }
|
|
44
|
-
* apply([
|
|
45
|
-
* createDocument(handle),
|
|
46
|
-
* publishDocument(handle),
|
|
47
|
-
* ])
|
|
48
|
-
* }
|
|
49
|
-
*
|
|
50
|
-
* return (
|
|
51
|
-
* <button onClick={handleCreateAndPublish}>
|
|
52
|
-
* I’m feeling lucky
|
|
53
|
-
* </button>
|
|
54
|
-
* )
|
|
55
|
-
* ```
|
|
56
|
-
*/
|
|
57
|
-
export function useApplyActions(
|
|
58
|
-
resourceId?: ResourceId,
|
|
59
|
-
): <TDocument extends SanityDocument>(
|
|
60
|
-
action: DocumentAction<TDocument> | DocumentAction<TDocument>[],
|
|
61
|
-
options?: ApplyActionsOptions,
|
|
62
|
-
) => Promise<ActionsResult<TDocument>>
|
|
63
|
-
|
|
64
|
-
/** @beta */
|
|
65
|
-
export function useApplyActions(
|
|
66
|
-
resourceId?: ResourceId,
|
|
67
|
-
): (
|
|
68
|
-
action: DocumentAction | DocumentAction[],
|
|
69
|
-
options?: ApplyActionsOptions,
|
|
70
|
-
) => Promise<ActionsResult> {
|
|
71
|
-
return _useApplyActions(resourceId)()
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const _useApplyActions = (resourceId?: ResourceId) => createCallbackHook(applyActions, resourceId)
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
// tests/useDocument.test.ts
|
|
2
|
-
import {
|
|
3
|
-
createSanityInstance,
|
|
4
|
-
getDocumentState,
|
|
5
|
-
resolveDocument,
|
|
6
|
-
type StateSource,
|
|
7
|
-
} from '@sanity/sdk'
|
|
8
|
-
import {type SanityDocument} from '@sanity/types'
|
|
9
|
-
import {renderHook} from '@testing-library/react'
|
|
10
|
-
import {beforeEach, describe, expect, it, vi} from 'vitest'
|
|
11
|
-
|
|
12
|
-
import {useSanityInstance} from '../context/useSanityInstance'
|
|
13
|
-
import {useDocument} from './useDocument'
|
|
14
|
-
|
|
15
|
-
vi.mock('@sanity/sdk', async (importOriginal) => {
|
|
16
|
-
const original = await importOriginal<typeof import('@sanity/sdk')>()
|
|
17
|
-
return {...original, getDocumentState: vi.fn(), resolveDocument: vi.fn()}
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
vi.mock('../context/useSanityInstance', () => ({
|
|
21
|
-
useSanityInstance: vi.fn(),
|
|
22
|
-
}))
|
|
23
|
-
|
|
24
|
-
// Create a fake instance to be returned by useSanityInstance.
|
|
25
|
-
const instance = createSanityInstance({projectId: 'p', dataset: 'd'})
|
|
26
|
-
const doc: SanityDocument = {
|
|
27
|
-
_id: 'doc1',
|
|
28
|
-
foo: 'bar',
|
|
29
|
-
_type: 'book',
|
|
30
|
-
_rev: 'tx0',
|
|
31
|
-
_createdAt: '2025-02-06T00:11:00.000Z',
|
|
32
|
-
_updatedAt: '2025-02-06T00:11:00.000Z',
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
describe('useDocument hook', () => {
|
|
36
|
-
beforeEach(() => {
|
|
37
|
-
vi.resetAllMocks()
|
|
38
|
-
vi.mocked(useSanityInstance).mockReturnValue(instance)
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
it('returns the current document when ready (without a path)', () => {
|
|
42
|
-
const getCurrent = vi.fn().mockReturnValue(doc)
|
|
43
|
-
const subscribe = vi.fn().mockReturnValue(vi.fn())
|
|
44
|
-
vi.mocked(getDocumentState).mockReturnValue({
|
|
45
|
-
getCurrent,
|
|
46
|
-
subscribe,
|
|
47
|
-
} as unknown as StateSource<unknown>)
|
|
48
|
-
|
|
49
|
-
const {result} = renderHook(() => useDocument({_id: 'doc1', _type: 'book'}))
|
|
50
|
-
|
|
51
|
-
expect(result.current).toEqual(doc)
|
|
52
|
-
expect(getCurrent).toHaveBeenCalled()
|
|
53
|
-
expect(subscribe).toHaveBeenCalled()
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
it('throws a promise (suspends) when the document is not ready', () => {
|
|
57
|
-
const getCurrent = vi.fn().mockReturnValue(undefined)
|
|
58
|
-
const subscribe = vi.fn().mockReturnValue(vi.fn())
|
|
59
|
-
vi.mocked(getDocumentState).mockReturnValue({
|
|
60
|
-
getCurrent,
|
|
61
|
-
subscribe,
|
|
62
|
-
} as unknown as StateSource<unknown>)
|
|
63
|
-
|
|
64
|
-
const resolveDocPromise = Promise.resolve(doc)
|
|
65
|
-
|
|
66
|
-
// Also, simulate resolveDocument to return a known promise.
|
|
67
|
-
vi.mocked(resolveDocument).mockReturnValue(resolveDocPromise)
|
|
68
|
-
|
|
69
|
-
// Render the hook and capture the thrown promise.
|
|
70
|
-
const {result} = renderHook(() => {
|
|
71
|
-
try {
|
|
72
|
-
return useDocument({_id: 'doc1', _type: 'book'})
|
|
73
|
-
} catch (e) {
|
|
74
|
-
return e
|
|
75
|
-
}
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
// When the document is not ready, the hook throws the promise from resolveDocument.
|
|
79
|
-
expect(result.current).toBe(resolveDocPromise)
|
|
80
|
-
})
|
|
81
|
-
})
|