@sanity/cross-dataset-duplicator 0.3.6 → 1.0.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/LICENSE +1 -1
- package/README.md +53 -53
- package/lib/index.esm.js +2 -0
- package/lib/index.esm.js.map +1 -0
- package/lib/index.js +2 -21
- package/lib/index.js.map +1 -1
- package/lib/src/index.d.ts +20 -0
- package/package.json +64 -38
- package/sanity.json +2 -10
- package/src/actions/DuplicateToAction.tsx +33 -7
- package/src/components/CrossDatasetDuplicator.tsx +42 -46
- package/src/components/{DuplicatorTool.tsx → Duplicator.tsx} +183 -126
- package/src/components/DuplicatorQuery.tsx +36 -13
- package/src/components/DuplicatorWrapper.tsx +66 -0
- package/src/components/ResetSecret.tsx +9 -9
- package/src/components/SelectButtons.tsx +13 -9
- package/src/components/StatusBadge.tsx +13 -9
- package/src/helpers/clientConfig.ts +1 -1
- package/src/helpers/constants.ts +1 -1
- package/src/helpers/getDocumentsInArray.ts +28 -21
- package/src/helpers/index.ts +6 -10
- package/src/index.ts +36 -0
- package/src/tool/index.ts +14 -10
- package/src/types/index.ts +9 -9
- package/v2-incompatible.js +11 -0
- package/.babelrc +0 -3
- package/.eslintignore +0 -1
- package/config.dist.json +0 -4
- package/lib/actions/DuplicateToAction.js +0 -44
- package/lib/actions/DuplicateToAction.js.map +0 -1
- package/lib/actions/index.js +0 -29
- package/lib/actions/index.js.map +0 -1
- package/lib/components/CrossDatasetDuplicator.js +0 -81
- package/lib/components/CrossDatasetDuplicator.js.map +0 -1
- package/lib/components/DuplicatorQuery.js +0 -105
- package/lib/components/DuplicatorQuery.js.map +0 -1
- package/lib/components/DuplicatorTool.js +0 -503
- package/lib/components/DuplicatorTool.js.map +0 -1
- package/lib/components/Feedback.js +0 -23
- package/lib/components/Feedback.js.map +0 -1
- package/lib/components/ResetSecret.js +0 -34
- package/lib/components/ResetSecret.js.map +0 -1
- package/lib/components/SelectButtons.js +0 -84
- package/lib/components/SelectButtons.js.map +0 -1
- package/lib/components/StatusBadge.js +0 -85
- package/lib/components/StatusBadge.js.map +0 -1
- package/lib/helpers/clientConfig.js +0 -11
- package/lib/helpers/clientConfig.js.map +0 -1
- package/lib/helpers/constants.js +0 -9
- package/lib/helpers/constants.js.map +0 -1
- package/lib/helpers/getDocumentsInArray.js +0 -74
- package/lib/helpers/getDocumentsInArray.js.map +0 -1
- package/lib/helpers/index.js +0 -27
- package/lib/helpers/index.js.map +0 -1
- package/lib/tool/index.js +0 -18
- package/lib/tool/index.js.map +0 -1
- package/lib/types/index.js +0 -2
- package/lib/types/index.js.map +0 -1
- package/src/actions/index.ts +0 -22
- package/src/index.js +0 -7
|
@@ -1,8 +1,18 @@
|
|
|
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 {
|
|
15
|
+
import {extractWithPath} from '@sanity/mutator'
|
|
6
16
|
import {dset} from 'dset'
|
|
7
17
|
import {
|
|
8
18
|
Card,
|
|
@@ -15,77 +25,82 @@ import {
|
|
|
15
25
|
Select,
|
|
16
26
|
Flex,
|
|
17
27
|
Checkbox,
|
|
28
|
+
CardTone,
|
|
29
|
+
useTheme,
|
|
18
30
|
} from '@sanity/ui'
|
|
19
31
|
import {ArrowRightIcon, SearchIcon, LaunchIcon} from '@sanity/icons'
|
|
20
|
-
import
|
|
21
|
-
import
|
|
22
|
-
import schema from 'part:@sanity/base/schema'
|
|
23
|
-
import config from 'config:sanity'
|
|
32
|
+
import {SanityAssetDocument} from '@sanity/client'
|
|
33
|
+
import {isAssetId, isSanityFileAsset} from '@sanity/asset-utils'
|
|
24
34
|
|
|
25
|
-
import {
|
|
35
|
+
import {stickyStyles, createInitialMessage} from '../helpers'
|
|
26
36
|
import {getDocumentsInArray} from '../helpers/getDocumentsInArray'
|
|
27
37
|
import SelectButtons from './SelectButtons'
|
|
28
|
-
import StatusBadge from './StatusBadge'
|
|
38
|
+
import StatusBadge, {MessageTypes} from './StatusBadge'
|
|
29
39
|
import Feedback from './Feedback'
|
|
30
|
-
import {SanityDocument} from '../types'
|
|
31
40
|
import {clientConfig} from '../helpers/clientConfig'
|
|
41
|
+
import {PluginConfig} from '../types'
|
|
32
42
|
|
|
33
|
-
type
|
|
43
|
+
export type DuplicatorProps = {
|
|
34
44
|
docs: SanityDocument[]
|
|
35
|
-
|
|
45
|
+
// TODO: Find out if this is even used?
|
|
46
|
+
// draftIds: string[]
|
|
36
47
|
token: string
|
|
48
|
+
pluginConfig: PluginConfig
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export type PayloadItem = {
|
|
52
|
+
doc: SanityDocument
|
|
53
|
+
include: boolean
|
|
54
|
+
status?: keyof MessageTypes
|
|
55
|
+
hasDraft?: boolean
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
type WorkspaceOption = WorkspaceSummary & {
|
|
59
|
+
disabled: boolean
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
type Message = {
|
|
63
|
+
text: string
|
|
64
|
+
tone: CardTone
|
|
37
65
|
}
|
|
38
66
|
|
|
39
|
-
export default function
|
|
40
|
-
const {docs,
|
|
67
|
+
export default function Duplicator(props: DuplicatorProps) {
|
|
68
|
+
const {docs, token, pluginConfig} = props
|
|
69
|
+
const isDarkMode = useTheme().sanity.color.dark
|
|
41
70
|
|
|
42
71
|
// Prepare origin (this Studio) client
|
|
43
|
-
|
|
44
|
-
|
|
72
|
+
const originClient = useClient(clientConfig)
|
|
73
|
+
|
|
74
|
+
const schema = useSchema()
|
|
45
75
|
|
|
46
76
|
// Create list of dataset options
|
|
47
77
|
// and set initial value of dropdown
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
disabled:
|
|
57
|
-
space.api.dataset === originClient.config().dataset &&
|
|
58
|
-
space.api.projectId === originClient.config().projectId,
|
|
59
|
-
}))
|
|
60
|
-
: []
|
|
61
|
-
|
|
62
|
-
const [destination, setDestination] = useState(
|
|
63
|
-
spacesOptions.length ? spacesOptions.find((space) => !space.disabled) : {}
|
|
64
|
-
)
|
|
65
|
-
const [message, setMessage] = useState({})
|
|
66
|
-
const [payload, setPayload] = useState(
|
|
67
|
-
docs.length
|
|
68
|
-
? docs.map((item) => ({
|
|
69
|
-
doc: item,
|
|
70
|
-
include: true,
|
|
71
|
-
status: null,
|
|
72
|
-
hasDraft: draftIds?.length ? draftIds.includes(`drafts.${item._id}`) : false,
|
|
73
|
-
}))
|
|
74
|
-
: []
|
|
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
|
|
75
86
|
)
|
|
87
|
+
const [message, setMessage] = useState<Message | null>(null)
|
|
88
|
+
const [payload, setPayload] = useState<PayloadItem[]>([])
|
|
89
|
+
|
|
76
90
|
const [hasReferences, setHasReferences] = useState(false)
|
|
77
91
|
const [isDuplicating, setIsDuplicating] = useState(false)
|
|
78
92
|
const [isGathering, setIsGathering] = useState(false)
|
|
79
|
-
const [progress, setProgress] = useState([0, 0])
|
|
93
|
+
const [progress, setProgress] = useState<number[]>([0, 0])
|
|
80
94
|
|
|
81
95
|
// Check for References and update message
|
|
82
96
|
useEffect(() => {
|
|
83
97
|
const expr = `.._ref`
|
|
84
98
|
const initialRefs = []
|
|
85
|
-
const initialPayload = []
|
|
99
|
+
const initialPayload: PayloadItem[] = []
|
|
86
100
|
|
|
87
101
|
docs.forEach((doc) => {
|
|
88
|
-
|
|
102
|
+
const refs = extractWithPath(expr, doc).map((ref) => ref.value)
|
|
103
|
+
initialRefs.push(...refs)
|
|
89
104
|
initialPayload.push({include: true, doc})
|
|
90
105
|
})
|
|
91
106
|
|
|
@@ -108,10 +123,10 @@ export default function DuplicatorTool(props: DuplicatorToolProps) {
|
|
|
108
123
|
// (On initial render + select change)
|
|
109
124
|
useEffect(() => {
|
|
110
125
|
updatePayloadStatuses()
|
|
111
|
-
}, [destination
|
|
126
|
+
}, [destination])
|
|
112
127
|
|
|
113
128
|
// Check if payload documents exist at destination
|
|
114
|
-
async function updatePayloadStatuses(newPayload = []) {
|
|
129
|
+
async function updatePayloadStatuses(newPayload: PayloadItem[] = []) {
|
|
115
130
|
const payloadActual = newPayload.length ? newPayload : payload
|
|
116
131
|
|
|
117
132
|
if (!payloadActual.length || !destination?.name) {
|
|
@@ -119,12 +134,12 @@ export default function DuplicatorTool(props: DuplicatorToolProps) {
|
|
|
119
134
|
}
|
|
120
135
|
|
|
121
136
|
const payloadIds = payloadActual.map(({doc}) => doc._id)
|
|
122
|
-
const destinationClient =
|
|
137
|
+
const destinationClient = originClient.withConfig({
|
|
123
138
|
...clientConfig,
|
|
124
|
-
dataset: destination.
|
|
125
|
-
projectId: destination.
|
|
139
|
+
dataset: destination.dataset,
|
|
140
|
+
projectId: destination.projectId,
|
|
126
141
|
})
|
|
127
|
-
const destinationData = await destinationClient.fetch(
|
|
142
|
+
const destinationData: SanityDocument[] = await destinationClient.fetch(
|
|
128
143
|
`*[_id in $payloadIds]{ _id, _updatedAt }`,
|
|
129
144
|
{payloadIds}
|
|
130
145
|
)
|
|
@@ -155,7 +170,7 @@ export default function DuplicatorTool(props: DuplicatorToolProps) {
|
|
|
155
170
|
setPayload(updatedPayload)
|
|
156
171
|
}
|
|
157
172
|
|
|
158
|
-
function handleCheckbox(_id) {
|
|
173
|
+
function handleCheckbox(_id: string) {
|
|
159
174
|
const updatedPayload = payload.map((item) => {
|
|
160
175
|
if (item.doc._id === _id) {
|
|
161
176
|
item.include = !item.include
|
|
@@ -172,13 +187,17 @@ export default function DuplicatorTool(props: DuplicatorToolProps) {
|
|
|
172
187
|
setIsGathering(true)
|
|
173
188
|
const docIds = docs.map((doc) => doc._id)
|
|
174
189
|
|
|
175
|
-
const payloadDocs = await getDocumentsInArray(
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
+
})
|
|
182
201
|
const draftDocsIds = new Set(draftDocs.map(({_id}) => _id))
|
|
183
202
|
|
|
184
203
|
// Shape it up
|
|
@@ -187,7 +206,7 @@ export default function DuplicatorTool(props: DuplicatorToolProps) {
|
|
|
187
206
|
// Include this in the transaction?
|
|
188
207
|
include: true,
|
|
189
208
|
// Does it exist at the destination?
|
|
190
|
-
status:
|
|
209
|
+
status: undefined,
|
|
191
210
|
// Does it have any drafts?
|
|
192
211
|
hasDraft: draftDocsIds.has(`drafts.${doc._id}`),
|
|
193
212
|
}))
|
|
@@ -199,38 +218,45 @@ export default function DuplicatorTool(props: DuplicatorToolProps) {
|
|
|
199
218
|
|
|
200
219
|
// Duplicate payload to destination dataset
|
|
201
220
|
async function handleDuplicate() {
|
|
221
|
+
if (!destination) {
|
|
222
|
+
return
|
|
223
|
+
}
|
|
224
|
+
|
|
202
225
|
setIsDuplicating(true)
|
|
203
226
|
|
|
204
|
-
const assetsCount = payload.filter(({doc, include}) => include &&
|
|
227
|
+
const assetsCount = payload.filter(({doc, include}) => include && isAssetId(doc._id)).length
|
|
205
228
|
let currentProgress = 0
|
|
206
229
|
setProgress([currentProgress, assetsCount])
|
|
207
230
|
|
|
208
|
-
setMessage({text: 'Duplicating...'})
|
|
231
|
+
setMessage({text: 'Duplicating...', tone: `default`})
|
|
209
232
|
|
|
210
|
-
const destinationClient =
|
|
233
|
+
const destinationClient = originClient.withConfig({
|
|
211
234
|
...clientConfig,
|
|
212
|
-
dataset: destination.
|
|
213
|
-
projectId: destination.
|
|
235
|
+
dataset: destination.dataset,
|
|
236
|
+
projectId: destination.projectId,
|
|
214
237
|
})
|
|
215
238
|
|
|
216
|
-
const transactionDocs = []
|
|
217
|
-
const svgMaps = []
|
|
239
|
+
const transactionDocs: SanityDocument[] = []
|
|
240
|
+
const svgMaps: {old: string; new: string}[] = []
|
|
218
241
|
|
|
219
242
|
// Upload assets and then add to transaction
|
|
220
|
-
async function fetchDoc(doc) {
|
|
221
|
-
if (
|
|
243
|
+
async function fetchDoc(doc: SanityAssetDocument) {
|
|
244
|
+
if (isAssetId(doc._id)) {
|
|
222
245
|
// Download and upload asset
|
|
223
246
|
// Get the *original* image with this dlRaw param to create the same determenistic _id
|
|
224
|
-
const
|
|
225
|
-
const downloadUrl =
|
|
226
|
-
const downloadConfig =
|
|
227
|
-
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}`}}
|
|
228
250
|
|
|
229
251
|
await fetch(downloadUrl, downloadConfig).then(async (res) => {
|
|
230
252
|
const assetData = await res.blob()
|
|
231
253
|
|
|
232
254
|
const options = {filename: doc.originalFilename}
|
|
233
|
-
const assetDoc = await destinationClient.assets.upload(
|
|
255
|
+
const assetDoc = await destinationClient.assets.upload(
|
|
256
|
+
typeIsFile ? `file` : `image`,
|
|
257
|
+
assetData,
|
|
258
|
+
options
|
|
259
|
+
)
|
|
234
260
|
|
|
235
261
|
// SVG _id's need remapping before transaction
|
|
236
262
|
if (doc?.extension === 'svg') {
|
|
@@ -245,6 +271,7 @@ export default function DuplicatorTool(props: DuplicatorToolProps) {
|
|
|
245
271
|
text: `Duplicating ${currentProgress}/${assetsCount} ${
|
|
246
272
|
assetsCount === 1 ? `Assets` : `Assets`
|
|
247
273
|
}`,
|
|
274
|
+
tone: 'default',
|
|
248
275
|
})
|
|
249
276
|
|
|
250
277
|
return setProgress([currentProgress, assetsCount])
|
|
@@ -257,7 +284,7 @@ export default function DuplicatorTool(props: DuplicatorToolProps) {
|
|
|
257
284
|
const result = new Promise((resolve, reject) => {
|
|
258
285
|
const payloadIncludedDocs = payload.filter((item) => item.include).map((item) => item.doc)
|
|
259
286
|
|
|
260
|
-
mapLimit(payloadIncludedDocs, 3, asyncify(fetchDoc), (err) => {
|
|
287
|
+
mapLimit(payloadIncludedDocs, 3, asyncify(fetchDoc), (err: Error) => {
|
|
261
288
|
if (err) {
|
|
262
289
|
setIsDuplicating(false)
|
|
263
290
|
setMessage({tone: 'critical', text: `Duplication Failed`})
|
|
@@ -265,6 +292,7 @@ export default function DuplicatorTool(props: DuplicatorToolProps) {
|
|
|
265
292
|
reject(new Error('Duplication Failed'))
|
|
266
293
|
}
|
|
267
294
|
|
|
295
|
+
// @ts-ignore
|
|
268
296
|
resolve()
|
|
269
297
|
})
|
|
270
298
|
})
|
|
@@ -313,33 +341,33 @@ export default function DuplicatorTool(props: DuplicatorToolProps) {
|
|
|
313
341
|
})
|
|
314
342
|
|
|
315
343
|
setIsDuplicating(false)
|
|
316
|
-
setProgress(0)
|
|
344
|
+
setProgress([0, 0])
|
|
317
345
|
}
|
|
318
346
|
|
|
319
|
-
function handleChange(e) {
|
|
320
|
-
|
|
321
|
-
|
|
347
|
+
function handleChange(e: React.ChangeEvent<HTMLSelectElement>) {
|
|
348
|
+
if (!workspacesOptions.length) {
|
|
349
|
+
return
|
|
350
|
+
}
|
|
322
351
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
)
|
|
352
|
+
const targeted = workspacesOptions.find((space) => space.name === e.currentTarget.value)
|
|
353
|
+
|
|
354
|
+
if (targeted) {
|
|
355
|
+
setDestination(targeted)
|
|
356
|
+
}
|
|
329
357
|
}
|
|
330
358
|
|
|
331
359
|
const payloadCount = payload.length
|
|
332
360
|
const firstSvgIndex = payload.findIndex(({doc}) => doc.extension === 'svg')
|
|
333
361
|
const selectedDocumentsCount = payload.filter(
|
|
334
|
-
(item) => item.include && !
|
|
362
|
+
(item) => item.include && !isAssetId(item.doc._id)
|
|
335
363
|
).length
|
|
336
364
|
const selectedAssetsCount = payload.filter(
|
|
337
|
-
(item) => item.include &&
|
|
365
|
+
(item) => item.include && isAssetId(item.doc._id)
|
|
338
366
|
).length
|
|
339
367
|
const selectedTotal = selectedDocumentsCount + selectedAssetsCount
|
|
340
368
|
const destinationTitle = destination?.title ?? destination?.name
|
|
341
369
|
const hasMultipleProjectIds =
|
|
342
|
-
new Set(
|
|
370
|
+
new Set(workspacesOptions.map((space) => space?.projectId).filter(Boolean)).size > 1
|
|
343
371
|
|
|
344
372
|
const headingText = [selectedTotal, `/`, payloadCount, `Documents and Assets selected`].join(` `)
|
|
345
373
|
|
|
@@ -347,39 +375,59 @@ export default function DuplicatorTool(props: DuplicatorToolProps) {
|
|
|
347
375
|
const text = [`Duplicate`]
|
|
348
376
|
|
|
349
377
|
if (selectedDocumentsCount > 1) {
|
|
350
|
-
text.push(
|
|
378
|
+
text.push(
|
|
379
|
+
String(selectedDocumentsCount),
|
|
380
|
+
selectedDocumentsCount === 1 ? `Document` : `Documents`
|
|
381
|
+
)
|
|
351
382
|
}
|
|
352
383
|
|
|
353
384
|
if (selectedAssetsCount > 1) {
|
|
354
|
-
text.push(`and`, selectedAssetsCount, selectedAssetsCount === 1 ? `Asset` : `Assets`)
|
|
385
|
+
text.push(`and`, String(selectedAssetsCount), selectedAssetsCount === 1 ? `Asset` : `Assets`)
|
|
355
386
|
}
|
|
356
387
|
|
|
357
|
-
if (originClient.config().projectId !== destination
|
|
388
|
+
if (originClient.config().projectId !== destination?.projectId) {
|
|
358
389
|
text.push(`between Projects`)
|
|
359
390
|
}
|
|
360
391
|
|
|
361
|
-
text.push(`to`, destinationTitle)
|
|
392
|
+
text.push(`to`, String(destinationTitle))
|
|
362
393
|
|
|
363
394
|
return text.join(` `)
|
|
364
|
-
}, [
|
|
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
|
+
}
|
|
365
410
|
|
|
366
411
|
return (
|
|
367
412
|
<Container width={1}>
|
|
368
|
-
<Card>
|
|
413
|
+
<Card border>
|
|
369
414
|
<Stack>
|
|
370
415
|
<>
|
|
371
|
-
<Card borderBottom padding={4} style={stickyStyles}>
|
|
416
|
+
<Card borderBottom padding={4} style={stickyStyles(isDarkMode)}>
|
|
372
417
|
<Stack space={4}>
|
|
373
|
-
<Flex
|
|
418
|
+
<Flex gap={3}>
|
|
374
419
|
<Stack style={{flex: 1}} space={3}>
|
|
375
420
|
<Label>Duplicate from</Label>
|
|
376
|
-
<Select
|
|
377
|
-
|
|
421
|
+
<Select
|
|
422
|
+
readOnly
|
|
423
|
+
value={workspacesOptions.find((space) => space.disabled)?.name}
|
|
424
|
+
>
|
|
425
|
+
{workspacesOptions
|
|
378
426
|
.filter((space) => space.disabled)
|
|
379
427
|
.map((space) => (
|
|
380
428
|
<option key={space.name} value={space.name} disabled={space.disabled}>
|
|
381
429
|
{space.title ?? space.name}
|
|
382
|
-
{hasMultipleProjectIds
|
|
430
|
+
{hasMultipleProjectIds ? ` (${space.projectId})` : ``}
|
|
383
431
|
</option>
|
|
384
432
|
))}
|
|
385
433
|
</Select>
|
|
@@ -392,10 +440,10 @@ export default function DuplicatorTool(props: DuplicatorToolProps) {
|
|
|
392
440
|
<Stack style={{flex: 1}} space={3}>
|
|
393
441
|
<Label>To Destination</Label>
|
|
394
442
|
<Select onChange={handleChange}>
|
|
395
|
-
{
|
|
443
|
+
{workspacesOptions.map((space) => (
|
|
396
444
|
<option key={space.name} value={space.name} disabled={space.disabled}>
|
|
397
445
|
{space.title ?? space.name}
|
|
398
|
-
{hasMultipleProjectIds
|
|
446
|
+
{hasMultipleProjectIds ? ` (${space.projectId})` : ``}
|
|
399
447
|
{space.disabled ? ` (Current)` : ``}
|
|
400
448
|
</option>
|
|
401
449
|
))}
|
|
@@ -426,40 +474,49 @@ export default function DuplicatorTool(props: DuplicatorToolProps) {
|
|
|
426
474
|
)}
|
|
427
475
|
</Stack>
|
|
428
476
|
</Card>
|
|
429
|
-
{message
|
|
477
|
+
{message && (
|
|
430
478
|
<Box paddingX={4} paddingTop={4}>
|
|
431
|
-
<Card padding={3} radius={2} shadow={1} tone={message
|
|
479
|
+
<Card padding={3} radius={2} shadow={1} tone={message.tone}>
|
|
432
480
|
<Text size={1}>{message.text}</Text>
|
|
433
481
|
</Card>
|
|
434
482
|
</Box>
|
|
435
483
|
)}
|
|
436
484
|
{payload.length > 0 && (
|
|
437
485
|
<Stack padding={4} space={3}>
|
|
438
|
-
{payload.map(({doc, include, status, hasDraft}, index) =>
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
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>
|
|
448
504
|
</Flex>
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
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
|
+
})}
|
|
463
520
|
</Stack>
|
|
464
521
|
)}
|
|
465
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
|
|
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
|
-
|
|
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({
|
|
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
|
|
45
|
+
// const initialDraftIds = res.length
|
|
46
|
+
// ? res.filter((doc) => doc._id.startsWith(`drafts.`)).map((doc) => doc._id)
|
|
47
|
+
// : []
|
|
36
48
|
|
|
37
|
-
setInitialData({
|
|
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}
|
|
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 &&
|
|
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
|
)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import React, {useState, useEffect} from 'react'
|
|
2
|
+
import {Grid, Card, Container, Button} from '@sanity/ui'
|
|
3
|
+
import {SanityDocument, useClient} from 'sanity'
|
|
4
|
+
|
|
5
|
+
import type {DuplicatorProps} from './Duplicator'
|
|
6
|
+
import Duplicator from './Duplicator'
|
|
7
|
+
|
|
8
|
+
export default function DuplicatorWrapper(props: DuplicatorProps) {
|
|
9
|
+
const {docs, token, pluginConfig} = props
|
|
10
|
+
const [inbound, setInbound] = useState<SanityDocument[]>([])
|
|
11
|
+
const {follow = []} = pluginConfig
|
|
12
|
+
|
|
13
|
+
// Make the first mode the default if there's only one
|
|
14
|
+
const [mode, setMode] = useState<'inbound' | 'outbound'>(
|
|
15
|
+
follow.length === 1 ? follow[0] : `outbound`
|
|
16
|
+
)
|
|
17
|
+
const client = useClient()
|
|
18
|
+
|
|
19
|
+
// "Inbound" will start with all documents that reference the first one
|
|
20
|
+
// And then you can gather "Outbound" references thereafter
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
;(async () => {
|
|
23
|
+
if (follow.includes(`inbound`)) {
|
|
24
|
+
const inboundReferences = await client.fetch(`*[references($id)]`, {id: docs[0]._id})
|
|
25
|
+
setInbound([...props.docs, ...inboundReferences])
|
|
26
|
+
}
|
|
27
|
+
})()
|
|
28
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
29
|
+
}, [])
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<Container>
|
|
33
|
+
{follow.length > 1 && (follow.includes(`inbound`) || follow.includes(`outbound`)) ? (
|
|
34
|
+
<Card paddingX={4} paddingBottom={4} marginBottom={4} borderBottom>
|
|
35
|
+
<Grid columns={2} gap={4}>
|
|
36
|
+
{follow.includes(`outbound`) ? (
|
|
37
|
+
<Button
|
|
38
|
+
mode="ghost"
|
|
39
|
+
tone="primary"
|
|
40
|
+
selected={mode === 'outbound'}
|
|
41
|
+
onClick={() => setMode('outbound')}
|
|
42
|
+
text="Outbound"
|
|
43
|
+
/>
|
|
44
|
+
) : null}
|
|
45
|
+
{follow.includes(`inbound`) ? (
|
|
46
|
+
<Button
|
|
47
|
+
mode="ghost"
|
|
48
|
+
tone="primary"
|
|
49
|
+
selected={mode === 'inbound'}
|
|
50
|
+
onClick={() => setMode('inbound')}
|
|
51
|
+
disabled={inbound.length === 0}
|
|
52
|
+
text={inbound.length > 0 ? `Inbound (${inbound.length})` : 'No inbound references'}
|
|
53
|
+
/>
|
|
54
|
+
) : null}
|
|
55
|
+
</Grid>
|
|
56
|
+
</Card>
|
|
57
|
+
) : null}
|
|
58
|
+
<Duplicator
|
|
59
|
+
docs={mode === 'outbound' ? docs : inbound}
|
|
60
|
+
token={token}
|
|
61
|
+
// draftIds={[]}
|
|
62
|
+
pluginConfig={pluginConfig}
|
|
63
|
+
/>
|
|
64
|
+
</Container>
|
|
65
|
+
)
|
|
66
|
+
}
|