@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.
- package/LICENSE +21 -0
- package/README.md +205 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.esm.js +2341 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +2341 -0
- package/dist/index.js.map +1 -0
- package/package.json +98 -0
- package/sanity.json +8 -0
- package/src/_lib/connector/ConnectFromRegion.tsx +24 -0
- package/src/_lib/connector/ConnectToRegion.tsx +22 -0
- package/src/_lib/connector/ConnectorRegion.tsx +23 -0
- package/src/_lib/connector/ConnectorsProvider.tsx +19 -0
- package/src/_lib/connector/ConnectorsStore.ts +122 -0
- package/src/_lib/connector/ConnectorsStoreContext.ts +4 -0
- package/src/_lib/connector/helpers.ts +5 -0
- package/src/_lib/connector/index.ts +9 -0
- package/src/_lib/connector/mapConnectorToLine.ts +83 -0
- package/src/_lib/connector/types.ts +56 -0
- package/src/_lib/connector/useConnectorsStore.ts +13 -0
- package/src/_lib/connector/useRegionRects.ts +141 -0
- package/src/_lib/fixedListenQuery.ts +101 -0
- package/src/_lib/form/DocumentForm.tsx +197 -0
- package/src/_lib/form/helpers.ts +31 -0
- package/src/_lib/form/index.ts +1 -0
- package/src/_lib/randomKey.ts +29 -0
- package/src/_lib/useListeningQuery.ts +61 -0
- package/src/_lib/usePrevious.ts +9 -0
- package/src/assistConnectors/AssistConnectorsOverlay.tsx +132 -0
- package/src/assistConnectors/ConnectorPath.tsx +62 -0
- package/src/assistConnectors/draw/arrowPath.ts +9 -0
- package/src/assistConnectors/draw/connectorPath.ts +142 -0
- package/src/assistConnectors/index.ts +1 -0
- package/src/assistDocument/AssistDocumentContext.tsx +31 -0
- package/src/assistDocument/AssistDocumentContextProvider.tsx +17 -0
- package/src/assistDocument/AssistDocumentInput.tsx +46 -0
- package/src/assistDocument/RequestRunInstructionProvider.tsx +50 -0
- package/src/assistDocument/components/AssistDocumentForm.tsx +188 -0
- package/src/assistDocument/components/FieldRefPreview.tsx +27 -0
- package/src/assistDocument/components/InstructionsArrayField.tsx +8 -0
- package/src/assistDocument/components/InstructionsArrayInput.tsx +26 -0
- package/src/assistDocument/components/SelectedFieldContext.tsx +10 -0
- package/src/assistDocument/components/generic/HiddenFieldTitle.tsx +5 -0
- package/src/assistDocument/components/helpers.ts +21 -0
- package/src/assistDocument/components/instruction/BackToInstructionsLink.tsx +31 -0
- package/src/assistDocument/components/instruction/FieldRefInput.tsx +33 -0
- package/src/assistDocument/components/instruction/InstructionInput.tsx +87 -0
- package/src/assistDocument/components/instruction/PromptInput.tsx +52 -0
- package/src/assistDocument/components/instruction/appearance/IconInput.tsx +46 -0
- package/src/assistDocument/components/instruction/appearance/InstructionVisibility.tsx +37 -0
- package/src/assistDocument/hooks/useAssistDocumentContextValue.tsx +68 -0
- package/src/assistDocument/hooks/useDocumentState.ts +6 -0
- package/src/assistDocument/hooks/useInstructionToaster.tsx +74 -0
- package/src/assistDocument/hooks/useStudioAssistDocument.ts +119 -0
- package/src/assistDocument/index.ts +1 -0
- package/src/assistFormComponents/AssistField.tsx +51 -0
- package/src/assistFormComponents/AssistFormBlock.tsx +31 -0
- package/src/assistFormComponents/AssistInlineFormBlock.tsx +14 -0
- package/src/assistFormComponents/AssistItem.tsx +20 -0
- package/src/assistFormComponents/validation/listItem.tsx +63 -0
- package/src/assistFormComponents/validation/validationList.tsx +89 -0
- package/src/assistInspector/AssistInspector.tsx +379 -0
- package/src/assistInspector/FieldAutocomplete.tsx +119 -0
- package/src/assistInspector/InstructionTaskHistoryButton.tsx +261 -0
- package/src/assistInspector/constants.ts +1 -0
- package/src/assistInspector/helpers.ts +125 -0
- package/src/assistInspector/index.ts +26 -0
- package/src/assistLayout/AiAssistanceConfigContext.tsx +81 -0
- package/src/assistLayout/AlphaMigration.tsx +311 -0
- package/src/assistLayout/AssistLayout.tsx +38 -0
- package/src/assistLayout/RunInstructionProvider.tsx +222 -0
- package/src/components/AssistFeatureBadge.tsx +9 -0
- package/src/components/Delay.tsx +25 -0
- package/src/components/HideReferenceChangedBannerInput.tsx +25 -0
- package/src/components/SafeValueInput.tsx +73 -0
- package/src/components/TimeAgo.tsx +18 -0
- package/src/constants.ts +20 -0
- package/src/fieldActions/PrivateIcon.tsx +20 -0
- package/src/fieldActions/assistFieldActions.tsx +230 -0
- package/src/globals.d.ts +4 -0
- package/src/helpers/assistSupported.ts +44 -0
- package/src/helpers/ids.ts +19 -0
- package/src/helpers/misc.ts +16 -0
- package/src/helpers/typeUtils.ts +15 -0
- package/src/helpers/useAssistSupported.ts +10 -0
- package/src/index.ts +6 -0
- package/src/legacy-types.ts +72 -0
- package/src/onboarding/FieldActionsOnboarding.tsx +90 -0
- package/src/onboarding/FirstAssistedPathProvider.tsx +29 -0
- package/src/onboarding/InspectorOnboarding.tsx +46 -0
- package/src/onboarding/onboardingStore.ts +33 -0
- package/src/plugin.tsx +80 -0
- package/src/presence/AiFieldPresence.tsx +28 -0
- package/src/presence/AssistAvatar.tsx +96 -0
- package/src/presence/AssistDocumentPresence.tsx +58 -0
- package/src/presence/useAssistPresence.ts +61 -0
- package/src/schemas/assistDocumentSchema.tsx +450 -0
- package/src/schemas/contextDocumentSchema.tsx +56 -0
- package/src/schemas/index.ts +25 -0
- package/src/schemas/serialize/SchemTypeTool.tsx +102 -0
- package/src/schemas/serialize/schemaUtils.ts +37 -0
- package/src/schemas/serialize/serializeSchema.test.ts +382 -0
- package/src/schemas/serialize/serializeSchema.ts +162 -0
- package/src/schemas/serializedSchemaTypeSchema.ts +59 -0
- package/src/schemas/typeDefExtensions.ts +30 -0
- package/src/types.ts +167 -0
- package/src/useApiClient.ts +140 -0
- package/src/vite.config.ts +9 -0
- 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,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,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
|
+
}
|