@sanity/assist 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.
Files changed (109) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +205 -0
  3. package/dist/index.d.ts +52 -0
  4. package/dist/index.esm.js +2341 -0
  5. package/dist/index.esm.js.map +1 -0
  6. package/dist/index.js +2341 -0
  7. package/dist/index.js.map +1 -0
  8. package/package.json +98 -0
  9. package/sanity.json +8 -0
  10. package/src/_lib/connector/ConnectFromRegion.tsx +24 -0
  11. package/src/_lib/connector/ConnectToRegion.tsx +22 -0
  12. package/src/_lib/connector/ConnectorRegion.tsx +23 -0
  13. package/src/_lib/connector/ConnectorsProvider.tsx +19 -0
  14. package/src/_lib/connector/ConnectorsStore.ts +122 -0
  15. package/src/_lib/connector/ConnectorsStoreContext.ts +4 -0
  16. package/src/_lib/connector/helpers.ts +5 -0
  17. package/src/_lib/connector/index.ts +9 -0
  18. package/src/_lib/connector/mapConnectorToLine.ts +83 -0
  19. package/src/_lib/connector/types.ts +56 -0
  20. package/src/_lib/connector/useConnectorsStore.ts +13 -0
  21. package/src/_lib/connector/useRegionRects.ts +141 -0
  22. package/src/_lib/fixedListenQuery.ts +101 -0
  23. package/src/_lib/form/DocumentForm.tsx +197 -0
  24. package/src/_lib/form/helpers.ts +31 -0
  25. package/src/_lib/form/index.ts +1 -0
  26. package/src/_lib/randomKey.ts +29 -0
  27. package/src/_lib/useListeningQuery.ts +61 -0
  28. package/src/_lib/usePrevious.ts +9 -0
  29. package/src/assistConnectors/AssistConnectorsOverlay.tsx +132 -0
  30. package/src/assistConnectors/ConnectorPath.tsx +62 -0
  31. package/src/assistConnectors/draw/arrowPath.ts +9 -0
  32. package/src/assistConnectors/draw/connectorPath.ts +142 -0
  33. package/src/assistConnectors/index.ts +1 -0
  34. package/src/assistDocument/AssistDocumentContext.tsx +31 -0
  35. package/src/assistDocument/AssistDocumentContextProvider.tsx +17 -0
  36. package/src/assistDocument/AssistDocumentInput.tsx +46 -0
  37. package/src/assistDocument/RequestRunInstructionProvider.tsx +50 -0
  38. package/src/assistDocument/components/AssistDocumentForm.tsx +188 -0
  39. package/src/assistDocument/components/FieldRefPreview.tsx +27 -0
  40. package/src/assistDocument/components/InstructionsArrayField.tsx +8 -0
  41. package/src/assistDocument/components/InstructionsArrayInput.tsx +26 -0
  42. package/src/assistDocument/components/SelectedFieldContext.tsx +10 -0
  43. package/src/assistDocument/components/generic/HiddenFieldTitle.tsx +5 -0
  44. package/src/assistDocument/components/helpers.ts +21 -0
  45. package/src/assistDocument/components/instruction/BackToInstructionsLink.tsx +31 -0
  46. package/src/assistDocument/components/instruction/FieldRefInput.tsx +33 -0
  47. package/src/assistDocument/components/instruction/InstructionInput.tsx +87 -0
  48. package/src/assistDocument/components/instruction/PromptInput.tsx +52 -0
  49. package/src/assistDocument/components/instruction/appearance/IconInput.tsx +46 -0
  50. package/src/assistDocument/components/instruction/appearance/InstructionVisibility.tsx +37 -0
  51. package/src/assistDocument/hooks/useAssistDocumentContextValue.tsx +68 -0
  52. package/src/assistDocument/hooks/useDocumentState.ts +6 -0
  53. package/src/assistDocument/hooks/useInstructionToaster.tsx +74 -0
  54. package/src/assistDocument/hooks/useStudioAssistDocument.ts +119 -0
  55. package/src/assistDocument/index.ts +1 -0
  56. package/src/assistFormComponents/AssistField.tsx +51 -0
  57. package/src/assistFormComponents/AssistFormBlock.tsx +31 -0
  58. package/src/assistFormComponents/AssistInlineFormBlock.tsx +14 -0
  59. package/src/assistFormComponents/AssistItem.tsx +20 -0
  60. package/src/assistFormComponents/validation/listItem.tsx +63 -0
  61. package/src/assistFormComponents/validation/validationList.tsx +89 -0
  62. package/src/assistInspector/AssistInspector.tsx +379 -0
  63. package/src/assistInspector/FieldAutocomplete.tsx +119 -0
  64. package/src/assistInspector/InstructionTaskHistoryButton.tsx +261 -0
  65. package/src/assistInspector/constants.ts +1 -0
  66. package/src/assistInspector/helpers.ts +125 -0
  67. package/src/assistInspector/index.ts +26 -0
  68. package/src/assistLayout/AiAssistanceConfigContext.tsx +81 -0
  69. package/src/assistLayout/AlphaMigration.tsx +311 -0
  70. package/src/assistLayout/AssistLayout.tsx +38 -0
  71. package/src/assistLayout/RunInstructionProvider.tsx +222 -0
  72. package/src/components/AssistFeatureBadge.tsx +9 -0
  73. package/src/components/Delay.tsx +25 -0
  74. package/src/components/HideReferenceChangedBannerInput.tsx +25 -0
  75. package/src/components/SafeValueInput.tsx +73 -0
  76. package/src/components/TimeAgo.tsx +18 -0
  77. package/src/constants.ts +20 -0
  78. package/src/fieldActions/PrivateIcon.tsx +20 -0
  79. package/src/fieldActions/assistFieldActions.tsx +230 -0
  80. package/src/globals.d.ts +4 -0
  81. package/src/helpers/assistSupported.ts +44 -0
  82. package/src/helpers/ids.ts +19 -0
  83. package/src/helpers/misc.ts +16 -0
  84. package/src/helpers/typeUtils.ts +15 -0
  85. package/src/helpers/useAssistSupported.ts +10 -0
  86. package/src/index.ts +6 -0
  87. package/src/legacy-types.ts +72 -0
  88. package/src/onboarding/FieldActionsOnboarding.tsx +90 -0
  89. package/src/onboarding/FirstAssistedPathProvider.tsx +29 -0
  90. package/src/onboarding/InspectorOnboarding.tsx +46 -0
  91. package/src/onboarding/onboardingStore.ts +33 -0
  92. package/src/plugin.tsx +80 -0
  93. package/src/presence/AiFieldPresence.tsx +28 -0
  94. package/src/presence/AssistAvatar.tsx +96 -0
  95. package/src/presence/AssistDocumentPresence.tsx +58 -0
  96. package/src/presence/useAssistPresence.ts +61 -0
  97. package/src/schemas/assistDocumentSchema.tsx +450 -0
  98. package/src/schemas/contextDocumentSchema.tsx +56 -0
  99. package/src/schemas/index.ts +25 -0
  100. package/src/schemas/serialize/SchemTypeTool.tsx +102 -0
  101. package/src/schemas/serialize/schemaUtils.ts +37 -0
  102. package/src/schemas/serialize/serializeSchema.test.ts +382 -0
  103. package/src/schemas/serialize/serializeSchema.ts +162 -0
  104. package/src/schemas/serializedSchemaTypeSchema.ts +59 -0
  105. package/src/schemas/typeDefExtensions.ts +30 -0
  106. package/src/types.ts +167 -0
  107. package/src/useApiClient.ts +140 -0
  108. package/src/vite.config.ts +9 -0
  109. package/v2-incompatible.js +11 -0
@@ -0,0 +1,142 @@
1
+ import {ConnectorLine, ConnectorOptions} from '../../_lib/connector'
2
+
3
+ export function drawArrowPath(
4
+ options: ConnectorOptions,
5
+ x: number,
6
+ y: number,
7
+ dir: number
8
+ ): string {
9
+ return [
10
+ `M ${x - options.arrow.size} ${y - options.arrow.size * dir} `,
11
+ `L ${x} ${y}`,
12
+ `L ${x + options.arrow.size} ${y - options.arrow.size * dir}`,
13
+ ].join('')
14
+ }
15
+
16
+ function moveTo(x: number, y: number) {
17
+ return `M${x} ${y}`
18
+ }
19
+
20
+ function lineTo(x: number, y: number) {
21
+ return `L${x} ${y}`
22
+ }
23
+
24
+ function join(strings: string[], delim = '') {
25
+ return strings.join(delim)
26
+ }
27
+
28
+ function quadCurve(x1: number, y1: number, x: number, y: number) {
29
+ return `Q${x1} ${y1} ${x} ${y}`
30
+ }
31
+
32
+ export function drawConnectorPath(options: ConnectorOptions, line: ConnectorLine): string {
33
+ const {cornerRadius} = options.path
34
+ const {from, to} = line
35
+ const {x: fromX, y: fromY} = from
36
+ const {x: _toX, y: toY} = to
37
+
38
+ const toX = _toX - 1
39
+
40
+ // Calculate divider position
41
+ const dividerX = to.bounds.x + options.divider.offsetX
42
+
43
+ // Calculate connector FROM path X position
44
+ const fromPathX = from.isAbove || from.isBelow ? fromX + options.arrow.marginX : fromX
45
+
46
+ // Calculate maximum corner radius
47
+ const r0 = Math.min(cornerRadius, Math.abs(fromPathX - dividerX) / 2)
48
+ const r1 = Math.min(cornerRadius, Math.abs(fromY - toY) / 2)
49
+
50
+ const cmds: string[] = []
51
+
52
+ // FROM
53
+ if (from.isAbove) {
54
+ cmds.push(
55
+ moveTo(
56
+ fromX + options.arrow.marginX,
57
+ fromY - options.arrow.threshold + options.arrow.marginY
58
+ ),
59
+ lineTo(fromX + options.arrow.marginX, fromY - r0),
60
+ quadCurve(fromX + options.arrow.marginX, fromY, fromX + options.arrow.marginX + r0, fromY)
61
+ )
62
+ } else if (from.isBelow) {
63
+ cmds.push(
64
+ moveTo(
65
+ fromX + options.arrow.marginX,
66
+ fromY + options.arrow.threshold - options.arrow.marginY
67
+ ),
68
+ lineTo(fromX + options.arrow.marginX, fromY + r0),
69
+ quadCurve(fromX + options.arrow.marginX, fromY, fromX + options.arrow.marginX + r0, fromY)
70
+ )
71
+ } else {
72
+ cmds.push(moveTo(fromX, fromY))
73
+ }
74
+
75
+ // TO
76
+ if (to.isAbove) {
77
+ if (fromY < to.bounds.y) {
78
+ cmds.push(
79
+ lineTo(dividerX - r1, fromY),
80
+ quadCurve(dividerX, fromY, dividerX, fromY + r1),
81
+ lineTo(dividerX, toY - r1),
82
+ quadCurve(dividerX, toY, dividerX + r1, toY),
83
+ lineTo(dividerX - cornerRadius, toY),
84
+ quadCurve(dividerX, toY, dividerX, toY - cornerRadius),
85
+ lineTo(dividerX, toY - options.arrow.threshold + options.arrow.marginY)
86
+ )
87
+ } else {
88
+ cmds.push(
89
+ lineTo(dividerX - cornerRadius, fromY),
90
+ quadCurve(dividerX, fromY, dividerX, fromY - cornerRadius),
91
+ lineTo(dividerX, toY - options.arrow.threshold + options.arrow.marginY)
92
+ )
93
+ }
94
+ } else if (to.isBelow) {
95
+ if (fromY > to.bounds.y + to.bounds.h) {
96
+ // curl around
97
+ cmds.push(
98
+ lineTo(dividerX - options.arrow.marginX - r1, fromY),
99
+ quadCurve(
100
+ dividerX - options.arrow.marginX,
101
+ fromY,
102
+ dividerX - options.arrow.marginX,
103
+ fromY - r1
104
+ ),
105
+ lineTo(dividerX - options.arrow.marginX, toY + r1),
106
+ quadCurve(
107
+ dividerX - options.arrow.marginX,
108
+ toY,
109
+ dividerX - options.arrow.marginX + r1,
110
+ toY
111
+ ),
112
+ lineTo(dividerX - cornerRadius, toY),
113
+ quadCurve(dividerX, toY, dividerX, toY + cornerRadius),
114
+ lineTo(dividerX, toY + options.arrow.threshold - options.arrow.marginY)
115
+ )
116
+ } else {
117
+ cmds.push(
118
+ lineTo(dividerX - cornerRadius, fromY),
119
+ quadCurve(dividerX, fromY, dividerX, fromY + cornerRadius),
120
+ lineTo(dividerX, toY + options.arrow.threshold - options.arrow.marginY)
121
+ )
122
+ }
123
+ } else if (fromY < toY) {
124
+ cmds.push(
125
+ lineTo(dividerX - r0, fromY),
126
+ quadCurve(dividerX, fromY, dividerX, fromY + r1),
127
+ lineTo(dividerX, toY - r1),
128
+ quadCurve(dividerX, toY, dividerX + r1, toY),
129
+ lineTo(toX, toY)
130
+ )
131
+ } else {
132
+ cmds.push(
133
+ lineTo(dividerX - Math.min(r0, r1), fromY),
134
+ quadCurve(dividerX, fromY, dividerX, fromY - Math.min(r0, r1)),
135
+ lineTo(dividerX, toY + r1),
136
+ quadCurve(dividerX, toY, dividerX + r1, toY),
137
+ lineTo(toX, toY)
138
+ )
139
+ }
140
+
141
+ return join(cmds)
142
+ }
@@ -0,0 +1 @@
1
+ export * from './AssistConnectorsOverlay'
@@ -0,0 +1,31 @@
1
+ import {DocumentInspector, ObjectSchemaType, PatchEvent} from 'sanity'
2
+ import {createContext, useContext} from 'react'
3
+ import {StudioAssistDocument} from '../types'
4
+
5
+ export type AssistDocumentContextValue = (
6
+ | {assistDocument: StudioAssistDocument; loading: false}
7
+ | {assistDocument: undefined; loading: true}
8
+ ) & {
9
+ documentIsNew: boolean
10
+ assistableDocumentId: string
11
+ documentIsAssistable: boolean
12
+ documentId: string
13
+ documentSchemaType: ObjectSchemaType
14
+ openInspector: (inspectorName: string, paneParams?: Record<string, string>) => void
15
+ closeInspector: (inspectorName?: string) => void
16
+ inspector: DocumentInspector | null
17
+ selectedPath?: string
18
+ documentOnChange: (event: PatchEvent) => void
19
+ }
20
+
21
+ export const AssistDocumentContext = createContext<AssistDocumentContextValue | undefined>(
22
+ undefined
23
+ )
24
+
25
+ export function useAssistDocumentContext(): AssistDocumentContextValue {
26
+ const context = useContext(AssistDocumentContext)
27
+ if (!context) {
28
+ throw new Error('AssistDocumentContext value is missing')
29
+ }
30
+ return context
31
+ }
@@ -0,0 +1,17 @@
1
+ import {ObjectSchemaType} from 'sanity'
2
+ import {AssistDocumentContext} from './AssistDocumentContext'
3
+ import {useAssistDocumentContextValue} from './hooks/useAssistDocumentContextValue'
4
+ import {PropsWithChildren} from 'react'
5
+
6
+ export interface AIDocumentInputProps {
7
+ documentId: string
8
+ schemaType: ObjectSchemaType
9
+ }
10
+
11
+ export function AssistDocumentContextProvider(props: PropsWithChildren<AIDocumentInputProps>) {
12
+ const {documentId, schemaType} = props
13
+ const value = useAssistDocumentContextValue(documentId, schemaType)
14
+ return (
15
+ <AssistDocumentContext.Provider value={value}>{props.children}</AssistDocumentContext.Provider>
16
+ )
17
+ }
@@ -0,0 +1,46 @@
1
+ import {InputProps, ObjectInputProps} from 'sanity'
2
+ import {AssistDocumentContextProvider} from './AssistDocumentContextProvider'
3
+ import {FirstAssistedPathProvider} from '../onboarding/FirstAssistedPathProvider'
4
+ import {useInstructionToaster} from './hooks/useInstructionToaster'
5
+ import {isType} from '../helpers/typeUtils'
6
+ import {useLayer} from '@sanity/ui'
7
+ import {useDocumentPane} from 'sanity/desk'
8
+ import {usePathKey} from '../helpers/misc'
9
+ import {ConnectFromRegion} from '../_lib/connector'
10
+
11
+ export function AssistDocumentInputWrapper(props: InputProps) {
12
+ if (!isType(props.schemaType, 'document') && props.id !== 'root') {
13
+ return <AssistInput {...props} />
14
+ }
15
+
16
+ const documentId = (props.value as any)?._id as string | undefined
17
+ if (!documentId) {
18
+ return props.renderDefault(props)
19
+ }
20
+
21
+ return <AssistDocumentInput {...(props as ObjectInputProps)} documentId={documentId} />
22
+ }
23
+
24
+ function AssistDocumentInput({documentId, ...props}: ObjectInputProps & {documentId: string}) {
25
+ useInstructionToaster(documentId, props.schemaType)
26
+
27
+ return (
28
+ <FirstAssistedPathProvider members={props.members}>
29
+ <AssistDocumentContextProvider schemaType={props.schemaType} documentId={documentId}>
30
+ {props.renderDefault(props)}
31
+ </AssistDocumentContextProvider>
32
+ </FirstAssistedPathProvider>
33
+ )
34
+ }
35
+
36
+ function AssistInput(props: InputProps) {
37
+ const {zIndex} = useLayer()
38
+ const {paneKey} = useDocumentPane()
39
+ const pathKey = usePathKey(props.path)
40
+
41
+ return (
42
+ <ConnectFromRegion _key={`${paneKey}_${pathKey}`} zIndex={zIndex} style={{minWidth: 0}}>
43
+ {props.renderDefault(props)}
44
+ </ConnectFromRegion>
45
+ )
46
+ }
@@ -0,0 +1,50 @@
1
+ import {useRunInstruction} from '../assistLayout/RunInstructionProvider'
2
+ import {useCallback, useEffect, useState} from 'react'
3
+ import {ObjectSchemaType, PatchEvent, SanityDocument, unset} from 'sanity'
4
+ import {RunInstructionArgs} from '../assistLayout/AssistLayout'
5
+ import {publicId} from '../helpers/ids'
6
+
7
+ export interface DocumentArgs {
8
+ documentOnChange: (event: PatchEvent) => void
9
+ // indicates if the document is a draft or liveEditable currently
10
+ isDocAssistable: boolean
11
+ }
12
+
13
+ export function isDocAssistable(
14
+ documentSchemaType: ObjectSchemaType,
15
+ published?: SanityDocument | null,
16
+ draft?: SanityDocument | null
17
+ ) {
18
+ return !!(documentSchemaType.liveEdit ? published : draft)
19
+ }
20
+
21
+ export function getAssistableDocId(documentSchemaType: ObjectSchemaType, documentId: string) {
22
+ const baseId = publicId(documentId)
23
+ return documentSchemaType.liveEdit ? baseId : `drafts.${baseId}`
24
+ }
25
+
26
+ export function useRequestRunInstruction(args: DocumentArgs) {
27
+ const {documentOnChange, isDocAssistable} = args
28
+
29
+ const {runInstruction, instructionLoading} = useRunInstruction()
30
+ const [queuedTask, setQueuedTask] = useState<RunInstructionArgs | undefined>(undefined)
31
+
32
+ useEffect(() => {
33
+ if (queuedTask && isDocAssistable) {
34
+ runInstruction(queuedTask)
35
+ setQueuedTask(undefined)
36
+ }
37
+ }, [queuedTask, isDocAssistable, runInstruction])
38
+
39
+ return {
40
+ instructionLoading,
41
+ requestRunInstruction: useCallback(
42
+ (task: RunInstructionArgs) => {
43
+ // make a dummy edit: this will trigger the document/draft to be created
44
+ documentOnChange(PatchEvent.from([unset(['_force_document_creation'])]))
45
+ setQueuedTask(task)
46
+ },
47
+ [setQueuedTask, documentOnChange]
48
+ ),
49
+ }
50
+ }
@@ -0,0 +1,188 @@
1
+ import {
2
+ assistFieldTypeName,
3
+ AssistInspectorRouteParams,
4
+ AssistDocument,
5
+ documentRootKey,
6
+ fieldPathParam,
7
+ AssistField,
8
+ instructionParam,
9
+ } from '../../types'
10
+ import {useEffect, useMemo} from 'react'
11
+ import {
12
+ FormCallbacksProvider,
13
+ FormCallbacksValue,
14
+ FormInput,
15
+ insert,
16
+ KeyedSegment,
17
+ ObjectInputProps,
18
+ ObjectSchemaType,
19
+ Path,
20
+ SchemaType,
21
+ set,
22
+ setIfMissing,
23
+ stringToPath,
24
+ typed,
25
+ useFormCallbacks,
26
+ useSchema,
27
+ } from 'sanity'
28
+ import {BackToInstructionListLink} from './instruction/BackToInstructionsLink'
29
+ import {useAiPaneRouter} from '../../assistInspector/helpers'
30
+ import {SelectedFieldContextProvider, SelectedFieldContextValue} from './SelectedFieldContext'
31
+ import {Stack} from '@sanity/ui'
32
+ import {documentTypeFromAiDocumentId} from '../../helpers/ids'
33
+
34
+ const EMPTY_FIELDS: AssistField[] = []
35
+
36
+ export function AssistDocumentForm(props: ObjectInputProps) {
37
+ const {onChange} = props
38
+ const value = props.value as AssistDocument | undefined
39
+ const id = value?._id
40
+ const fields = value?.fields
41
+
42
+ const targetDocumentType = useMemo(() => {
43
+ if (!id) {
44
+ return undefined
45
+ }
46
+ return documentTypeFromAiDocumentId(id)
47
+ }, [id])
48
+
49
+ const {params, setParams} = useAiPaneRouter()
50
+ const pathKey = params[fieldPathParam]
51
+ const instruction = params[instructionParam]
52
+
53
+ const activeKey = useMemo(
54
+ () => (fields ?? EMPTY_FIELDS).find((f) => f.path === pathKey)?._key,
55
+ [fields, pathKey]
56
+ )
57
+
58
+ const activePath: Path | undefined = useMemo(() => {
59
+ if (!activeKey) {
60
+ return undefined
61
+ }
62
+ const base = ['fields', {_key: activeKey}]
63
+ return instruction ? [...base, 'instructions', {_key: instruction}] : base
64
+ }, [activeKey, instruction])
65
+
66
+ const schema = useSchema()
67
+ const documentSchema: ObjectSchemaType | undefined = useMemo(() => {
68
+ if (!targetDocumentType) {
69
+ return undefined
70
+ }
71
+ return schema.get(targetDocumentType) as ObjectSchemaType
72
+ }, [schema, targetDocumentType])
73
+
74
+ const fieldSchema = useSelectedSchema(pathKey, documentSchema)
75
+ const context: SelectedFieldContextValue = useMemo(
76
+ () => ({
77
+ documentSchema,
78
+ fieldSchema: fieldSchema ?? documentSchema,
79
+ }),
80
+ [fieldSchema, documentSchema]
81
+ )
82
+
83
+ const title = value?.title
84
+
85
+ useEffect(() => {
86
+ if (!title && documentSchema && !id?.startsWith('drafts.')) {
87
+ onChange(set(documentSchema.title ?? documentSchema.name, ['title']))
88
+ }
89
+ }, [title, documentSchema, onChange, id])
90
+
91
+ useEffect(() => {
92
+ if (activePath || !pathKey) {
93
+ return
94
+ }
95
+ onChange([
96
+ setIfMissing([], ['fields']),
97
+ insert(
98
+ [
99
+ typed<AssistField>({
100
+ _key: pathKey,
101
+ _type: assistFieldTypeName,
102
+ path: pathKey,
103
+ }),
104
+ ],
105
+ 'after',
106
+ ['fields', -1]
107
+ ),
108
+ ])
109
+ }, [activePath, onChange, pathKey])
110
+
111
+ const {onPathOpen, ...formCallbacks} = useFormCallbacks()
112
+
113
+ const newCallbacks: FormCallbacksValue = useMemo(
114
+ () => ({
115
+ ...formCallbacks,
116
+ onPathOpen: (path) => {
117
+ if (!instruction && path.length === 4 && path[2] === 'instructions') {
118
+ setParams(
119
+ typed<AssistInspectorRouteParams>({
120
+ ...params,
121
+ [instructionParam]: (path[3] as KeyedSegment)?._key,
122
+ }) as Record<keyof AssistInspectorRouteParams, string | undefined>
123
+ )
124
+ onPathOpen([])
125
+ } else {
126
+ setTimeout(() => onPathOpen(path), 0)
127
+ }
128
+ },
129
+ }),
130
+ [formCallbacks, onPathOpen, params, setParams, instruction]
131
+ )
132
+
133
+ useEffect(() => {
134
+ if (activePath && !instruction) {
135
+ onPathOpen([])
136
+ }
137
+ }, [activePath, instruction, onPathOpen])
138
+
139
+ return (
140
+ <SelectedFieldContextProvider value={context}>
141
+ <Stack space={5}>
142
+ {instruction && <BackToInstructionListLink />}
143
+
144
+ {activePath && (
145
+ <FormCallbacksProvider {...newCallbacks}>
146
+ <div style={{lineHeight: '1.25em'}}>
147
+ <FormInput {...props} absolutePath={activePath} />
148
+ </div>
149
+ </FormCallbacksProvider>
150
+ )}
151
+
152
+ {!activePath && props.renderDefault(props)}
153
+ </Stack>
154
+ </SelectedFieldContextProvider>
155
+ )
156
+ }
157
+
158
+ function useSelectedSchema(
159
+ fieldPath: string | undefined,
160
+ documentSchema: ObjectSchemaType | undefined
161
+ ): SchemaType | undefined {
162
+ return useMemo(() => {
163
+ if (!fieldPath) {
164
+ return undefined
165
+ }
166
+ if (fieldPath === documentRootKey) {
167
+ return documentSchema
168
+ }
169
+
170
+ const path = stringToPath(fieldPath)
171
+ let currentSchema: ObjectSchemaType | undefined = documentSchema
172
+ for (let i = 0; i < path.length; i++) {
173
+ const segment = path[i]
174
+ const field = currentSchema?.fields.find((f) => f.name === segment)
175
+ if (!field) {
176
+ return undefined
177
+ }
178
+ if (i === path.length - 1) {
179
+ return field.type
180
+ }
181
+ if (field.type.jsonType !== 'object') {
182
+ return undefined
183
+ }
184
+ currentSchema = field.type
185
+ }
186
+ return currentSchema
187
+ }, [documentSchema, fieldPath])
188
+ }
@@ -0,0 +1,27 @@
1
+ import {useContext} from 'react'
2
+ import {useSelectedField} from '../../assistInspector/helpers'
3
+ import {Box, Flex, Text} from '@sanity/ui'
4
+ import {PreviewProps} from 'sanity'
5
+ import {SelectedFieldContext} from './SelectedFieldContext'
6
+ import {InlineBlockValueContext} from '../../assistFormComponents/AssistInlineFormBlock'
7
+
8
+ export function FieldRefPreview(props: PreviewProps & {path?: string}) {
9
+ const documentSchema = useContext(SelectedFieldContext)?.documentSchema
10
+ const path = (useContext(InlineBlockValueContext) as {path?: string})?.path ?? props.path
11
+ const selectedField = useSelectedField(documentSchema, path)
12
+ return (
13
+ <Flex gap={2} align="center" style={{width: '100%'}}>
14
+ <Flex flex={1} gap={2} align="center" paddingY={3} paddingX={1}>
15
+ {/*<Box>
16
+ <Text>{selectedField?.icon ? createElement(selectedField?.icon) : <CodeIcon />}</Text>
17
+ </Box>*/}
18
+ <Box>
19
+ <Text size={1} textOverflow="ellipsis">
20
+ {selectedField?.title ?? 'Select field'}
21
+ </Text>
22
+ </Box>
23
+ </Flex>
24
+ <>{props.actions}</>
25
+ </Flex>
26
+ )
27
+ }
@@ -0,0 +1,8 @@
1
+ import {ArrayFieldProps} from 'sanity'
2
+
3
+ export function InstructionsArrayField(props: ArrayFieldProps) {
4
+ return props.renderDefault({
5
+ ...props,
6
+ title: undefined,
7
+ })
8
+ }
@@ -0,0 +1,26 @@
1
+ import {ArrayOfObjectsInputProps, useCurrentUser} from 'sanity'
2
+ import {useMemo} from 'react'
3
+ import {StudioInstruction} from '../../types'
4
+
5
+ export function InstructionsArrayInput(props: ArrayOfObjectsInputProps) {
6
+ const user = useCurrentUser()
7
+
8
+ const originalValue = props.value as StudioInstruction[] | undefined
9
+ const originalMembers = props.members
10
+ const value = useMemo(
11
+ () => (originalValue ?? []).filter((v) => !v.userId || v.userId === user?.id),
12
+ [originalValue, user]
13
+ )
14
+ const members = useMemo(
15
+ () =>
16
+ (originalMembers ?? []).filter((v) => {
17
+ if (v.kind === 'error') {
18
+ return true
19
+ }
20
+ const value = v?.item?.value as any
21
+ return !value.userId || value.userId === user?.id
22
+ }),
23
+ [originalMembers, user]
24
+ )
25
+ return props.renderDefault({...props, value, members})
26
+ }
@@ -0,0 +1,10 @@
1
+ import {ObjectSchemaType, SchemaType} from 'sanity'
2
+ import {createContext} from 'react'
3
+
4
+ export interface SelectedFieldContextValue {
5
+ documentSchema?: ObjectSchemaType
6
+ fieldSchema?: SchemaType
7
+ }
8
+
9
+ export const SelectedFieldContext = createContext<SelectedFieldContextValue | undefined>(undefined)
10
+ export const SelectedFieldContextProvider = SelectedFieldContext.Provider
@@ -0,0 +1,5 @@
1
+ import {FieldProps} from 'sanity'
2
+
3
+ export function HiddenFieldTitle(props: FieldProps) {
4
+ return props.renderDefault({...props, title: '', level: props.level - 2, changed: false})
5
+ }
@@ -0,0 +1,21 @@
1
+ import {FieldError, FieldMember, FieldSetMember, ObjectMember} from 'sanity'
2
+
3
+ export function findFieldMember(
4
+ members: ObjectMember[],
5
+ fieldName: string
6
+ ): FieldMember | FieldError | undefined {
7
+ return members.find(
8
+ (m): m is FieldMember | FieldError =>
9
+ (m.kind === 'field' && m.name === fieldName) ||
10
+ (m.kind === 'error' && m.fieldName === fieldName)
11
+ )
12
+ }
13
+
14
+ export function findFieldsetMember(
15
+ members: ObjectMember[],
16
+ fieldsetName: string
17
+ ): FieldSetMember | undefined {
18
+ return members.find(
19
+ (m): m is FieldSetMember => m.kind === 'fieldSet' && m.fieldSet.name === fieldsetName
20
+ )
21
+ }
@@ -0,0 +1,31 @@
1
+ import {ArrowLeftIcon} from '@sanity/icons'
2
+ import {Button} from '@sanity/ui'
3
+ import {useCallback} from 'react'
4
+ import {instructionParam} from '../../../types'
5
+ import {useDocumentPane} from 'sanity/desk'
6
+ import {aiInspectorId} from '../../../assistInspector/constants'
7
+
8
+ export function BackToInstructionListLink() {
9
+ const {openInspector} = useDocumentPane()
10
+
11
+ const goBack = useCallback(
12
+ () => openInspector(aiInspectorId, {[instructionParam]: undefined as any}),
13
+ [openInspector]
14
+ )
15
+
16
+ return (
17
+ <div>
18
+ <Button
19
+ as="a"
20
+ fontSize={1}
21
+ icon={ArrowLeftIcon}
22
+ mode="bleed"
23
+ padding={1}
24
+ space={2}
25
+ onClick={goBack}
26
+ text=" Instructions"
27
+ textAlign="left"
28
+ />
29
+ </div>
30
+ )
31
+ }
@@ -0,0 +1,33 @@
1
+ import {set, StringInputProps} from 'sanity'
2
+ import {useCallback, useContext, useEffect, useId, useRef} from 'react'
3
+ import {Box} from '@sanity/ui'
4
+ import {SelectedFieldContext} from '../SelectedFieldContext'
5
+ import {FieldAutocomplete} from '../../../assistInspector/FieldAutocomplete'
6
+
7
+ export function FieldRefPathInput(props: StringInputProps) {
8
+ const documentSchema = useContext(SelectedFieldContext)?.documentSchema
9
+ const ref = useRef<HTMLDivElement>(null)
10
+ const id = useId()
11
+ const {onChange} = props
12
+
13
+ useEffect(() => {
14
+ ref.current?.querySelector('input')?.focus()
15
+ }, [])
16
+
17
+ const onSelect = useCallback((path: string) => onChange(set(path)), [onChange])
18
+
19
+ if (!documentSchema) {
20
+ return props.renderDefault(props)
21
+ }
22
+
23
+ return (
24
+ <Box flex={1} style={{minWidth: 200}} ref={ref}>
25
+ <FieldAutocomplete
26
+ id={id}
27
+ schemaType={documentSchema}
28
+ onSelect={onSelect}
29
+ fieldPath={props.value}
30
+ />
31
+ </Box>
32
+ )
33
+ }