@sanity/cross-dataset-duplicator 0.4.0 → 1.1.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.
Files changed (64) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +53 -57
  3. package/lib/index.esm.js +1 -0
  4. package/lib/index.esm.js.map +1 -0
  5. package/lib/index.js +1 -21
  6. package/lib/index.js.map +1 -1
  7. package/lib/src/index.d.ts +548 -0
  8. package/package.json +64 -38
  9. package/sanity.json +2 -10
  10. package/src/actions/DuplicateToAction.tsx +20 -8
  11. package/src/components/CrossDatasetDuplicator.tsx +34 -49
  12. package/src/components/CrossDatasetDuplicatorAction.tsx +14 -0
  13. package/src/components/CrossDatasetDuplicatorTool.tsx +18 -0
  14. package/src/components/{DuplicatorTool.tsx → Duplicator.tsx} +179 -181
  15. package/src/components/DuplicatorQuery.tsx +36 -13
  16. package/src/components/DuplicatorWrapper.tsx +66 -0
  17. package/src/components/ResetSecret.tsx +9 -9
  18. package/src/components/SelectButtons.tsx +13 -9
  19. package/src/components/StatusBadge.tsx +13 -9
  20. package/src/context/ConfigProvider.tsx +30 -0
  21. package/src/helpers/clientConfig.ts +1 -1
  22. package/src/helpers/constants.ts +10 -1
  23. package/src/helpers/getDocumentsInArray.ts +28 -21
  24. package/src/helpers/index.ts +6 -10
  25. package/src/index.ts +5 -0
  26. package/src/plugin.tsx +31 -0
  27. package/src/tool/index.ts +11 -10
  28. package/src/types/index.ts +17 -7
  29. package/v2-incompatible.js +11 -0
  30. package/.babelrc +0 -3
  31. package/.eslintignore +0 -1
  32. package/config.dist.json +0 -5
  33. package/lib/actions/DuplicateToAction.js +0 -44
  34. package/lib/actions/DuplicateToAction.js.map +0 -1
  35. package/lib/actions/index.js +0 -29
  36. package/lib/actions/index.js.map +0 -1
  37. package/lib/components/CrossDatasetDuplicator.js +0 -81
  38. package/lib/components/CrossDatasetDuplicator.js.map +0 -1
  39. package/lib/components/DuplicatorQuery.js +0 -105
  40. package/lib/components/DuplicatorQuery.js.map +0 -1
  41. package/lib/components/DuplicatorTool.js +0 -556
  42. package/lib/components/DuplicatorTool.js.map +0 -1
  43. package/lib/components/Feedback.js +0 -23
  44. package/lib/components/Feedback.js.map +0 -1
  45. package/lib/components/ResetSecret.js +0 -34
  46. package/lib/components/ResetSecret.js.map +0 -1
  47. package/lib/components/SelectButtons.js +0 -84
  48. package/lib/components/SelectButtons.js.map +0 -1
  49. package/lib/components/StatusBadge.js +0 -85
  50. package/lib/components/StatusBadge.js.map +0 -1
  51. package/lib/helpers/clientConfig.js +0 -11
  52. package/lib/helpers/clientConfig.js.map +0 -1
  53. package/lib/helpers/constants.js +0 -9
  54. package/lib/helpers/constants.js.map +0 -1
  55. package/lib/helpers/getDocumentsInArray.js +0 -74
  56. package/lib/helpers/getDocumentsInArray.js.map +0 -1
  57. package/lib/helpers/index.js +0 -27
  58. package/lib/helpers/index.js.map +0 -1
  59. package/lib/tool/index.js +0 -18
  60. package/lib/tool/index.js.map +0 -1
  61. package/lib/types/index.js +0 -2
  62. package/lib/types/index.js.map +0 -1
  63. package/src/actions/index.ts +0 -22
  64. package/src/index.js +0 -7
@@ -1,11 +1,20 @@
1
1
  /* eslint-disable react/jsx-no-bind */
2
2
  import React, {useState, useEffect} from 'react'
3
+ import {
4
+ useClient,
5
+ Preview,
6
+ useSchema,
7
+ useWorkspaces,
8
+ WorkspaceSummary,
9
+ SanityDocument,
10
+ } from 'sanity'
11
+ // @ts-ignore
3
12
  import mapLimit from 'async/mapLimit'
13
+ // @ts-ignore
4
14
  import asyncify from 'async/asyncify'
5
- import {extract, extractWithPath} from '@sanity/mutator'
15
+ import {extractWithPath} from '@sanity/mutator'
6
16
  import {dset} from 'dset'
7
17
  import {
8
- Grid,
9
18
  Card,
10
19
  Container,
11
20
  Text,
@@ -16,131 +25,82 @@ import {
16
25
  Select,
17
26
  Flex,
18
27
  Checkbox,
19
- Tab,
28
+ CardTone,
29
+ useTheme,
20
30
  } from '@sanity/ui'
21
31
  import {ArrowRightIcon, SearchIcon, LaunchIcon} from '@sanity/icons'
22
- import sanityClient from 'part:@sanity/base/client'
23
- import Preview from 'part:@sanity/base/preview'
24
- import schema from 'part:@sanity/base/schema'
25
- import config from 'config:sanity'
26
- import duplicatorConfig from 'config:@sanity/cross-dataset-duplicator'
32
+ import {SanityAssetDocument} from '@sanity/client'
33
+ import {isAssetId, isSanityFileAsset} from '@sanity/asset-utils'
27
34
 
28
- import {typeIsAsset, stickyStyles, createInitialMessage} from '../helpers'
35
+ import {stickyStyles, createInitialMessage} from '../helpers'
29
36
  import {getDocumentsInArray} from '../helpers/getDocumentsInArray'
30
37
  import SelectButtons from './SelectButtons'
31
- import StatusBadge from './StatusBadge'
38
+ import StatusBadge, {MessageTypes} from './StatusBadge'
32
39
  import Feedback from './Feedback'
33
- import {SanityDocument} from '../types'
34
40
  import {clientConfig} from '../helpers/clientConfig'
41
+ import {PluginConfig} from '../types'
35
42
 
36
- type DuplicatorToolProps = {
43
+ export type DuplicatorProps = {
37
44
  docs: SanityDocument[]
38
- draftIds: string[]
45
+ // TODO: Find out if this is even used?
46
+ // draftIds: string[]
39
47
  token: string
48
+ pluginConfig: PluginConfig
40
49
  }
41
50
 
42
- export function DuplicatorToolWrapper(props: DuplicatorToolProps) {
43
- const {docs, token} = props
44
- const [mode, setMode] = useState('outbound')
45
- const [inbound, setInbound] = useState([])
46
- const {follow = []} = duplicatorConfig
47
-
48
- useEffect(() => {
49
- ;(async () => {
50
- const inboundReferences = await sanityClient.fetch('*[references($id)]', {id: docs[0]._id})
51
- setInbound([...props.docs, ...inboundReferences])
52
- })()
53
- }, [])
51
+ export type PayloadItem = {
52
+ doc: SanityDocument
53
+ include: boolean
54
+ status?: keyof MessageTypes
55
+ hasDraft?: boolean
56
+ }
54
57
 
55
- console.log(docs, inbound)
58
+ type WorkspaceOption = WorkspaceSummary & {
59
+ disabled: boolean
60
+ }
56
61
 
57
- return (
58
- <Container>
59
- {follow.includes(`inbound`) || follow.includes(`outbound`) ? (
60
- <Card paddingX={4} paddingBottom={4} marginBottom={4} borderBottom>
61
- <Grid columns={2} gap={4}>
62
- {follow.includes(`outbound`) ? (
63
- <Button
64
- mode="ghost"
65
- tone="primary"
66
- selected={mode === 'outbound'}
67
- onClick={() => setMode('outbound')}
68
- text={`Outbound`}
69
- />
70
- ) : null}
71
- {follow.includes(`inbound`) ? (
72
- <Button
73
- mode="ghost"
74
- tone="primary"
75
- selected={mode === 'inbound'}
76
- onClick={() => setMode('inbound')}
77
- disabled={inbound.length === 0}
78
- text={inbound.length > 0 ? `Inbound (${inbound.length})` : 'No inbound references'}
79
- />
80
- ) : null}
81
- </Grid>
82
- </Card>
83
- ) : null}
84
- <DuplicatorTool
85
- key={mode}
86
- docs={mode === 'outbound' ? docs : inbound}
87
- token={token}
88
- draftIds={[]}
89
- />
90
- </Container>
91
- )
62
+ type Message = {
63
+ text: string
64
+ tone: CardTone
92
65
  }
93
66
 
94
- export default function DuplicatorTool(props: DuplicatorToolProps) {
95
- const {docs, draftIds, token} = props
67
+ export default function Duplicator(props: DuplicatorProps) {
68
+ const {docs, token, pluginConfig} = props
69
+ const isDarkMode = useTheme().sanity.color.dark
96
70
 
97
71
  // Prepare origin (this Studio) client
98
- // In function-scope so it is up to date on every render
99
- const originClient = sanityClient.withConfig(clientConfig)
72
+ const originClient = useClient(clientConfig)
73
+
74
+ const schema = useSchema()
100
75
 
101
76
  // Create list of dataset options
102
77
  // and set initial value of dropdown
103
- const spacesOptions = config?.__experimental_spaces?.length
104
- ? config.__experimental_spaces.map((space) => ({
105
- ...space,
106
- api: {
107
- ...space.api,
108
- projectId: space.api.projectId || process.env.SANITY_STUDIO_API_PROJECT_ID,
109
- },
110
- usingEnvForProjectId: !space.api.projectId && process.env.SANITY_STUDIO_API_PROJECT_ID,
111
- disabled:
112
- space.api.dataset === originClient.config().dataset &&
113
- space.api.projectId === originClient.config().projectId,
114
- }))
115
- : []
116
-
117
- const [destination, setDestination] = useState(
118
- spacesOptions.length ? spacesOptions.find((space) => !space.disabled) : {}
119
- )
120
- const [message, setMessage] = useState({})
121
- const [payload, setPayload] = useState(
122
- docs.length
123
- ? docs.map((item) => ({
124
- doc: item,
125
- include: true,
126
- status: null,
127
- hasDraft: draftIds?.length ? draftIds.includes(`drafts.${item._id}`) : false,
128
- }))
129
- : []
78
+ const workspaces = useWorkspaces()
79
+ const workspacesOptions: WorkspaceOption[] = workspaces.map((workspace) => ({
80
+ ...workspace,
81
+ disabled: workspace.dataset === originClient.config().dataset,
82
+ }))
83
+
84
+ const [destination, setDestination] = useState<WorkspaceOption | null>(
85
+ workspaces.length ? workspacesOptions.find((space) => !space.disabled) ?? null : null
130
86
  )
87
+ const [message, setMessage] = useState<Message | null>(null)
88
+ const [payload, setPayload] = useState<PayloadItem[]>([])
89
+
131
90
  const [hasReferences, setHasReferences] = useState(false)
132
91
  const [isDuplicating, setIsDuplicating] = useState(false)
133
92
  const [isGathering, setIsGathering] = useState(false)
134
- const [progress, setProgress] = useState([0, 0])
93
+ const [progress, setProgress] = useState<number[]>([0, 0])
135
94
 
136
95
  // Check for References and update message
137
96
  useEffect(() => {
138
97
  const expr = `.._ref`
139
98
  const initialRefs = []
140
- const initialPayload = []
99
+ const initialPayload: PayloadItem[] = []
141
100
 
142
101
  docs.forEach((doc) => {
143
- initialRefs.push(...extract(expr, doc))
102
+ const refs = extractWithPath(expr, doc).map((ref) => ref.value)
103
+ initialRefs.push(...refs)
144
104
  initialPayload.push({include: true, doc})
145
105
  })
146
106
 
@@ -163,10 +123,10 @@ export default function DuplicatorTool(props: DuplicatorToolProps) {
163
123
  // (On initial render + select change)
164
124
  useEffect(() => {
165
125
  updatePayloadStatuses()
166
- }, [destination, docs])
126
+ }, [destination])
167
127
 
168
128
  // Check if payload documents exist at destination
169
- async function updatePayloadStatuses(newPayload = []) {
129
+ async function updatePayloadStatuses(newPayload: PayloadItem[] = []) {
170
130
  const payloadActual = newPayload.length ? newPayload : payload
171
131
 
172
132
  if (!payloadActual.length || !destination?.name) {
@@ -174,12 +134,12 @@ export default function DuplicatorTool(props: DuplicatorToolProps) {
174
134
  }
175
135
 
176
136
  const payloadIds = payloadActual.map(({doc}) => doc._id)
177
- const destinationClient = sanityClient.withConfig({
137
+ const destinationClient = originClient.withConfig({
178
138
  ...clientConfig,
179
- dataset: destination.api.dataset,
180
- projectId: destination.api.projectId,
139
+ dataset: destination.dataset,
140
+ projectId: destination.projectId,
181
141
  })
182
- const destinationData = await destinationClient.fetch(
142
+ const destinationData: SanityDocument[] = await destinationClient.fetch(
183
143
  `*[_id in $payloadIds]{ _id, _updatedAt }`,
184
144
  {payloadIds}
185
145
  )
@@ -210,7 +170,7 @@ export default function DuplicatorTool(props: DuplicatorToolProps) {
210
170
  setPayload(updatedPayload)
211
171
  }
212
172
 
213
- function handleCheckbox(_id) {
173
+ function handleCheckbox(_id: string) {
214
174
  const updatedPayload = payload.map((item) => {
215
175
  if (item.doc._id === _id) {
216
176
  item.include = !item.include
@@ -227,13 +187,17 @@ export default function DuplicatorTool(props: DuplicatorToolProps) {
227
187
  setIsGathering(true)
228
188
  const docIds = docs.map((doc) => doc._id)
229
189
 
230
- const payloadDocs = await getDocumentsInArray(docIds, originClient, null)
231
- const draftDocs = await getDocumentsInArray(
232
- docIds.map((id) => `drafts.${id}`),
233
- originClient,
234
- null,
235
- `{_id}`
236
- )
190
+ const payloadDocs = await getDocumentsInArray({
191
+ fetchIds: docIds,
192
+ client: originClient,
193
+ pluginConfig,
194
+ })
195
+ const draftDocs = await getDocumentsInArray({
196
+ fetchIds: docIds.map((id) => `drafts.${id}`),
197
+ client: originClient,
198
+ projection: `{_id}`,
199
+ pluginConfig,
200
+ })
237
201
  const draftDocsIds = new Set(draftDocs.map(({_id}) => _id))
238
202
 
239
203
  // Shape it up
@@ -242,7 +206,7 @@ export default function DuplicatorTool(props: DuplicatorToolProps) {
242
206
  // Include this in the transaction?
243
207
  include: true,
244
208
  // Does it exist at the destination?
245
- status: '',
209
+ status: undefined,
246
210
  // Does it have any drafts?
247
211
  hasDraft: draftDocsIds.has(`drafts.${doc._id}`),
248
212
  }))
@@ -254,38 +218,45 @@ export default function DuplicatorTool(props: DuplicatorToolProps) {
254
218
 
255
219
  // Duplicate payload to destination dataset
256
220
  async function handleDuplicate() {
221
+ if (!destination) {
222
+ return
223
+ }
224
+
257
225
  setIsDuplicating(true)
258
226
 
259
- const assetsCount = payload.filter(({doc, include}) => include && typeIsAsset(doc._type)).length
227
+ const assetsCount = payload.filter(({doc, include}) => include && isAssetId(doc._id)).length
260
228
  let currentProgress = 0
261
229
  setProgress([currentProgress, assetsCount])
262
230
 
263
- setMessage({text: 'Duplicating...'})
231
+ setMessage({text: 'Duplicating...', tone: `default`})
264
232
 
265
- const destinationClient = sanityClient.withConfig({
233
+ const destinationClient = originClient.withConfig({
266
234
  ...clientConfig,
267
- dataset: destination.api.dataset,
268
- projectId: destination.api.projectId,
235
+ dataset: destination.dataset,
236
+ projectId: destination.projectId,
269
237
  })
270
238
 
271
- const transactionDocs = []
272
- const svgMaps = []
239
+ const transactionDocs: SanityDocument[] = []
240
+ const svgMaps: {old: string; new: string}[] = []
273
241
 
274
242
  // Upload assets and then add to transaction
275
- async function fetchDoc(doc) {
276
- if (typeIsAsset(doc._type)) {
243
+ async function fetchDoc(doc: SanityAssetDocument) {
244
+ if (isAssetId(doc._id)) {
277
245
  // Download and upload asset
278
246
  // Get the *original* image with this dlRaw param to create the same determenistic _id
279
- const uploadType = doc._type.split('.').pop().replace('Asset', '')
280
- const downloadUrl = uploadType === 'image' ? `${doc.url}?dlRaw=true` : doc.url
281
- const downloadConfig =
282
- uploadType === 'image' ? {headers: {Authorization: `Bearer ${token}`}} : {}
247
+ const typeIsFile = isSanityFileAsset(doc)
248
+ const downloadUrl = typeIsFile ? doc.url : `${doc.url}?dlRaw=true`
249
+ const downloadConfig = typeIsFile ? {} : {headers: {Authorization: `Bearer ${token}`}}
283
250
 
284
251
  await fetch(downloadUrl, downloadConfig).then(async (res) => {
285
252
  const assetData = await res.blob()
286
253
 
287
254
  const options = {filename: doc.originalFilename}
288
- const assetDoc = await destinationClient.assets.upload(uploadType, assetData, options)
255
+ const assetDoc = await destinationClient.assets.upload(
256
+ typeIsFile ? `file` : `image`,
257
+ assetData,
258
+ options
259
+ )
289
260
 
290
261
  // SVG _id's need remapping before transaction
291
262
  if (doc?.extension === 'svg') {
@@ -300,6 +271,7 @@ export default function DuplicatorTool(props: DuplicatorToolProps) {
300
271
  text: `Duplicating ${currentProgress}/${assetsCount} ${
301
272
  assetsCount === 1 ? `Assets` : `Assets`
302
273
  }`,
274
+ tone: 'default',
303
275
  })
304
276
 
305
277
  return setProgress([currentProgress, assetsCount])
@@ -312,7 +284,7 @@ export default function DuplicatorTool(props: DuplicatorToolProps) {
312
284
  const result = new Promise((resolve, reject) => {
313
285
  const payloadIncludedDocs = payload.filter((item) => item.include).map((item) => item.doc)
314
286
 
315
- mapLimit(payloadIncludedDocs, 3, asyncify(fetchDoc), (err) => {
287
+ mapLimit(payloadIncludedDocs, 3, asyncify(fetchDoc), (err: Error) => {
316
288
  if (err) {
317
289
  setIsDuplicating(false)
318
290
  setMessage({tone: 'critical', text: `Duplication Failed`})
@@ -320,6 +292,7 @@ export default function DuplicatorTool(props: DuplicatorToolProps) {
320
292
  reject(new Error('Duplication Failed'))
321
293
  }
322
294
 
295
+ // @ts-ignore
323
296
  resolve()
324
297
  })
325
298
  })
@@ -368,33 +341,33 @@ export default function DuplicatorTool(props: DuplicatorToolProps) {
368
341
  })
369
342
 
370
343
  setIsDuplicating(false)
371
- setProgress(0)
344
+ setProgress([0, 0])
372
345
  }
373
346
 
374
- function handleChange(e) {
375
- setDestination(spacesOptions.find((space) => space.name === e.currentTarget.value))
376
- }
347
+ function handleChange(e: React.ChangeEvent<HTMLSelectElement>) {
348
+ if (!workspacesOptions.length) {
349
+ return
350
+ }
377
351
 
378
- if (!spacesOptions.length) {
379
- return (
380
- <Feedback tone="critical">
381
- <code>__experimental_spaces</code> not found in <code>sanity.json</code>
382
- </Feedback>
383
- )
352
+ const targeted = workspacesOptions.find((space) => space.name === e.currentTarget.value)
353
+
354
+ if (targeted) {
355
+ setDestination(targeted)
356
+ }
384
357
  }
385
358
 
386
359
  const payloadCount = payload.length
387
360
  const firstSvgIndex = payload.findIndex(({doc}) => doc.extension === 'svg')
388
361
  const selectedDocumentsCount = payload.filter(
389
- (item) => item.include && !typeIsAsset(item.doc._type)
362
+ (item) => item.include && !isAssetId(item.doc._id)
390
363
  ).length
391
364
  const selectedAssetsCount = payload.filter(
392
- (item) => item.include && typeIsAsset(item.doc._type)
365
+ (item) => item.include && isAssetId(item.doc._id)
393
366
  ).length
394
367
  const selectedTotal = selectedDocumentsCount + selectedAssetsCount
395
368
  const destinationTitle = destination?.title ?? destination?.name
396
369
  const hasMultipleProjectIds =
397
- new Set(spacesOptions.map((space) => space?.api?.projectId).filter(Boolean)).size > 1
370
+ new Set(workspacesOptions.map((space) => space?.projectId).filter(Boolean)).size > 1
398
371
 
399
372
  const headingText = [selectedTotal, `/`, payloadCount, `Documents and Assets selected`].join(` `)
400
373
 
@@ -402,41 +375,59 @@ export default function DuplicatorTool(props: DuplicatorToolProps) {
402
375
  const text = [`Duplicate`]
403
376
 
404
377
  if (selectedDocumentsCount > 1) {
405
- text.push(selectedDocumentsCount, selectedDocumentsCount === 1 ? `Document` : `Documents`)
378
+ text.push(
379
+ String(selectedDocumentsCount),
380
+ selectedDocumentsCount === 1 ? `Document` : `Documents`
381
+ )
406
382
  }
407
383
 
408
384
  if (selectedAssetsCount > 1) {
409
- text.push(`and`, selectedAssetsCount, selectedAssetsCount === 1 ? `Asset` : `Assets`)
385
+ text.push(`and`, String(selectedAssetsCount), selectedAssetsCount === 1 ? `Asset` : `Assets`)
410
386
  }
411
387
 
412
- if (originClient.config().projectId !== destination.api.projectId) {
388
+ if (originClient.config().projectId !== destination?.projectId) {
413
389
  text.push(`between Projects`)
414
390
  }
415
391
 
416
- text.push(`to`, destinationTitle)
392
+ text.push(`to`, String(destinationTitle))
417
393
 
418
394
  return text.join(` `)
419
- }, [selectedDocumentsCount, selectedAssetsCount, destinationTitle])
395
+ }, [
396
+ selectedDocumentsCount,
397
+ selectedAssetsCount,
398
+ originClient,
399
+ destination?.projectId,
400
+ destinationTitle,
401
+ ])
402
+
403
+ if (workspacesOptions.length < 2) {
404
+ return (
405
+ <Feedback tone="critical">
406
+ <code>sanity.config.ts</code> must contain at least two Workspaces to use this plugin.
407
+ </Feedback>
408
+ )
409
+ }
420
410
 
421
411
  return (
422
412
  <Container width={1}>
423
- <Card>
413
+ <Card border>
424
414
  <Stack>
425
415
  <>
426
- <Card borderBottom padding={4} style={stickyStyles}>
416
+ <Card borderBottom padding={4} style={stickyStyles(isDarkMode)}>
427
417
  <Stack space={4}>
428
- <Flex space={3}>
418
+ <Flex gap={3}>
429
419
  <Stack style={{flex: 1}} space={3}>
430
420
  <Label>Duplicate from</Label>
431
- <Select readOnly value={spacesOptions.find((space) => space.disabled)?.name}>
432
- {spacesOptions
421
+ <Select
422
+ readOnly
423
+ value={workspacesOptions.find((space) => space.disabled)?.name}
424
+ >
425
+ {workspacesOptions
433
426
  .filter((space) => space.disabled)
434
427
  .map((space) => (
435
428
  <option key={space.name} value={space.name} disabled={space.disabled}>
436
429
  {space.title ?? space.name}
437
- {hasMultipleProjectIds || space.usingEnvForProjectId
438
- ? ` (${space.api.projectId})`
439
- : ``}
430
+ {hasMultipleProjectIds ? ` (${space.projectId})` : ``}
440
431
  </option>
441
432
  ))}
442
433
  </Select>
@@ -449,12 +440,10 @@ export default function DuplicatorTool(props: DuplicatorToolProps) {
449
440
  <Stack style={{flex: 1}} space={3}>
450
441
  <Label>To Destination</Label>
451
442
  <Select onChange={handleChange}>
452
- {spacesOptions.map((space) => (
443
+ {workspacesOptions.map((space) => (
453
444
  <option key={space.name} value={space.name} disabled={space.disabled}>
454
445
  {space.title ?? space.name}
455
- {hasMultipleProjectIds || space.usingEnvForProjectId
456
- ? ` (${space.api.projectId})`
457
- : ``}
446
+ {hasMultipleProjectIds ? ` (${space.projectId})` : ``}
458
447
  {space.disabled ? ` (Current)` : ``}
459
448
  </option>
460
449
  ))}
@@ -485,40 +474,49 @@ export default function DuplicatorTool(props: DuplicatorToolProps) {
485
474
  )}
486
475
  </Stack>
487
476
  </Card>
488
- {message?.text && (
477
+ {message && (
489
478
  <Box paddingX={4} paddingTop={4}>
490
- <Card padding={3} radius={2} shadow={1} tone={message?.tone ?? 'transparent'}>
479
+ <Card padding={3} radius={2} shadow={1} tone={message.tone}>
491
480
  <Text size={1}>{message.text}</Text>
492
481
  </Card>
493
482
  </Box>
494
483
  )}
495
484
  {payload.length > 0 && (
496
485
  <Stack padding={4} space={3}>
497
- {payload.map(({doc, include, status, hasDraft}, index) => (
498
- <React.Fragment key={doc._id}>
499
- <Flex align="center">
500
- <Checkbox checked={include} onChange={() => handleCheckbox(doc._id)} />
501
- <Box flex={1} paddingX={3}>
502
- <Preview value={doc} type={schema.get(doc._type)} />
503
- </Box>
504
- <Flex items="center" gap={2}>
505
- {hasDraft ? <StatusBadge status="UNPUBLISHED" isAsset={false} /> : null}
506
- <StatusBadge status={status} isAsset={typeIsAsset(doc._type)} />
486
+ {payload.map(({doc, include, status, hasDraft}, index) => {
487
+ const schemaType = schema.get(doc._type)
488
+
489
+ return (
490
+ <React.Fragment key={doc._id}>
491
+ <Flex align="center">
492
+ <Checkbox checked={include} onChange={() => handleCheckbox(doc._id)} />
493
+ <Box flex={1} paddingX={3}>
494
+ {schemaType ? (
495
+ <Preview value={doc} schemaType={schemaType} />
496
+ ) : (
497
+ <Card tone="caution">Invalid schema type</Card>
498
+ )}
499
+ </Box>
500
+ <Flex align="center" gap={2}>
501
+ {hasDraft ? <StatusBadge status="UNPUBLISHED" isAsset={false} /> : null}
502
+ <StatusBadge status={status} isAsset={isAssetId(doc._id)} />
503
+ </Flex>
507
504
  </Flex>
508
- </Flex>
509
- {doc?.extension === 'svg' && index === firstSvgIndex && (
510
- <Card padding={3} radius={2} shadow={1} tone="caution">
511
- <Text size={1}>
512
- Due to how SVGs are sanitized after first uploaded, duplicated SVG assets
513
- may have new <code>_id</code>'s at the destination. The newly generated{' '}
514
- <code>_id</code> will be the same in each duplication, but it will never
515
- be the same <code>_id</code> as the first time this Asset was uploaded.
516
- References to the asset will be updated to use the new <code>_id</code>.
517
- </Text>
518
- </Card>
519
- )}
520
- </React.Fragment>
521
- ))}
505
+ {doc?.extension === 'svg' && index === firstSvgIndex && (
506
+ <Card padding={3} radius={2} shadow={1} tone="caution">
507
+ <Text size={1}>
508
+ Due to how SVGs are sanitized after first uploaded, duplicated SVG
509
+ assets may have new <code>_id</code>'s at the destination. The newly
510
+ generated <code>_id</code> will be the same in each duplication, but it
511
+ will never be the same <code>_id</code> as the first time this Asset was
512
+ uploaded. References to the asset will be updated to use the new{' '}
513
+ <code>_id</code>.
514
+ </Text>
515
+ </Card>
516
+ )}
517
+ </React.Fragment>
518
+ )
519
+ })}
522
520
  </Stack>
523
521
  )}
524
522
  <Stack space={2} padding={4} paddingTop={0}>
@@ -1,40 +1,55 @@
1
1
  import React, {useEffect, useState} from 'react'
2
- import sanityClient from 'part:@sanity/base/client'
3
- import schema from 'part:@sanity/base/schema'
4
2
  import {Button, Stack, Box, Label, Text, Card, Flex, Grid, Container, TextInput} from '@sanity/ui'
3
+ import {useSchema, useClient, SanityDocument} from 'sanity'
5
4
 
6
- import DuplicatorTool from './DuplicatorTool'
5
+ import Duplicator from './Duplicator'
7
6
  import {clientConfig} from '../helpers/clientConfig'
8
-
9
- const originClient = sanityClient.withConfig(clientConfig)
7
+ import {PluginConfig} from '../types'
10
8
 
11
9
  type DuplicatorQueryProps = {
12
10
  token: string
11
+ pluginConfig: PluginConfig
13
12
  }
14
13
 
15
- const schemaTypes = schema.getTypeNames()
14
+ type InitialData = {
15
+ docs: SanityDocument[]
16
+ // draftIds: string[]
17
+ }
16
18
 
17
19
  export default function DuplicatorQuery(props: DuplicatorQueryProps) {
18
- const {token} = props
20
+ const {token, pluginConfig} = props
21
+
22
+ const originClient = useClient(clientConfig)
23
+
24
+ const schema = useSchema()
25
+ const schemaTypes = schema.getTypeNames()
19
26
 
20
27
  const [value, setValue] = useState(``)
21
- const [initialData, setInitialData] = useState({docs: [], draftIds: []});
28
+ const [initialData, setInitialData] = useState<InitialData>({
29
+ docs: [],
30
+ // draftIds: []
31
+ })
22
32
 
23
33
  function handleSubmit(e?: any) {
24
34
  if (e) e.preventDefault()
25
35
 
26
36
  originClient
27
37
  .fetch(value)
28
- .then((res) => {
38
+ .then((res: SanityDocument[]) => {
29
39
  // Ensure queried docs are registered to the schema
30
40
  const registeredAndPublishedDocs = res.length
31
41
  ? res
32
42
  .filter((doc) => schemaTypes.includes(doc._type))
33
43
  .filter((doc) => !doc._id.startsWith(`drafts.`))
34
44
  : []
35
- const initialDraftIds = res.length ? res.filter(doc => doc._id.startsWith(`drafts.`)).map(doc => doc._id) : []
45
+ // const initialDraftIds = res.length
46
+ // ? res.filter((doc) => doc._id.startsWith(`drafts.`)).map((doc) => doc._id)
47
+ // : []
36
48
 
37
- setInitialData({docs: registeredAndPublishedDocs, draftIds: initialDraftIds})
49
+ setInitialData({
50
+ docs: registeredAndPublishedDocs,
51
+ // draftIds: initialDraftIds
52
+ })
38
53
  })
39
54
  .catch((err) => console.error(err))
40
55
  }
@@ -44,13 +59,14 @@ export default function DuplicatorQuery(props: DuplicatorQueryProps) {
44
59
  if (!initialData.docs?.length && value) {
45
60
  handleSubmit()
46
61
  }
62
+ // eslint-disable-next-line react-hooks/exhaustive-deps
47
63
  }, [])
48
64
 
49
65
  return (
50
66
  <Container width={[1, 1, 1, 3]} padding={[0, 0, 0, 5]}>
51
67
  <Grid columns={[1, 1, 1, 2]} gap={[1, 1, 1, 4]}>
52
68
  <Box padding={[2, 2, 2, 0]}>
53
- <Card padding={4} scheme="dark" radius={3}>
69
+ <Card padding={4} radius={3} border>
54
70
  <Stack space={4}>
55
71
  <Box>
56
72
  <Label>Initial Documents Query</Label>
@@ -97,7 +113,14 @@ export default function DuplicatorQuery(props: DuplicatorQueryProps) {
97
113
  </Card>
98
114
  </Container>
99
115
  ))}
100
- {initialData.docs?.length > 0 && <DuplicatorTool docs={initialData.docs} draftIds={initialData.draftIds} token={token} />}
116
+ {initialData.docs?.length > 0 && (
117
+ <Duplicator
118
+ docs={initialData.docs}
119
+ // draftIds={initialData.draftIds}
120
+ token={token}
121
+ pluginConfig={pluginConfig}
122
+ />
123
+ )}
101
124
  </Grid>
102
125
  </Container>
103
126
  )