@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.
- package/LICENSE +1 -1
- package/README.md +53 -57
- package/lib/index.esm.js +1 -0
- package/lib/index.esm.js.map +1 -0
- package/lib/index.js +1 -21
- package/lib/index.js.map +1 -1
- package/lib/src/index.d.ts +548 -0
- package/package.json +64 -38
- package/sanity.json +2 -10
- package/src/actions/DuplicateToAction.tsx +20 -8
- package/src/components/CrossDatasetDuplicator.tsx +34 -49
- package/src/components/CrossDatasetDuplicatorAction.tsx +14 -0
- package/src/components/CrossDatasetDuplicatorTool.tsx +18 -0
- package/src/components/{DuplicatorTool.tsx → Duplicator.tsx} +179 -181
- 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/context/ConfigProvider.tsx +30 -0
- package/src/helpers/clientConfig.ts +1 -1
- package/src/helpers/constants.ts +10 -1
- package/src/helpers/getDocumentsInArray.ts +28 -21
- package/src/helpers/index.ts +6 -10
- package/src/index.ts +5 -0
- package/src/plugin.tsx +31 -0
- package/src/tool/index.ts +11 -10
- package/src/types/index.ts +17 -7
- package/v2-incompatible.js +11 -0
- package/.babelrc +0 -3
- package/.eslintignore +0 -1
- package/config.dist.json +0 -5
- 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 -556
- 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,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 {
|
|
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
|
-
|
|
28
|
+
CardTone,
|
|
29
|
+
useTheme,
|
|
20
30
|
} from '@sanity/ui'
|
|
21
31
|
import {ArrowRightIcon, SearchIcon, LaunchIcon} from '@sanity/icons'
|
|
22
|
-
import
|
|
23
|
-
import
|
|
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 {
|
|
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
|
|
43
|
+
export type DuplicatorProps = {
|
|
37
44
|
docs: SanityDocument[]
|
|
38
|
-
|
|
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
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
58
|
+
type WorkspaceOption = WorkspaceSummary & {
|
|
59
|
+
disabled: boolean
|
|
60
|
+
}
|
|
56
61
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
|
95
|
-
const {docs,
|
|
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
|
-
|
|
99
|
-
|
|
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
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
137
|
+
const destinationClient = originClient.withConfig({
|
|
178
138
|
...clientConfig,
|
|
179
|
-
dataset: destination.
|
|
180
|
-
projectId: destination.
|
|
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(
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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 &&
|
|
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 =
|
|
233
|
+
const destinationClient = originClient.withConfig({
|
|
266
234
|
...clientConfig,
|
|
267
|
-
dataset: destination.
|
|
268
|
-
projectId: destination.
|
|
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 (
|
|
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
|
|
280
|
-
const downloadUrl =
|
|
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(
|
|
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
|
-
|
|
376
|
-
|
|
347
|
+
function handleChange(e: React.ChangeEvent<HTMLSelectElement>) {
|
|
348
|
+
if (!workspacesOptions.length) {
|
|
349
|
+
return
|
|
350
|
+
}
|
|
377
351
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
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 && !
|
|
362
|
+
(item) => item.include && !isAssetId(item.doc._id)
|
|
390
363
|
).length
|
|
391
364
|
const selectedAssetsCount = payload.filter(
|
|
392
|
-
(item) => item.include &&
|
|
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(
|
|
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(
|
|
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
|
|
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
|
-
}, [
|
|
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
|
|
418
|
+
<Flex gap={3}>
|
|
429
419
|
<Stack style={{flex: 1}} space={3}>
|
|
430
420
|
<Label>Duplicate from</Label>
|
|
431
|
-
<Select
|
|
432
|
-
|
|
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
|
|
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
|
-
{
|
|
443
|
+
{workspacesOptions.map((space) => (
|
|
453
444
|
<option key={space.name} value={space.name} disabled={space.disabled}>
|
|
454
445
|
{space.title ?? space.name}
|
|
455
|
-
{hasMultipleProjectIds
|
|
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
|
|
477
|
+
{message && (
|
|
489
478
|
<Box paddingX={4} paddingTop={4}>
|
|
490
|
-
<Card padding={3} radius={2} shadow={1} tone={message
|
|
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
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
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
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
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
|
|
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
|
)
|