-
Notifications
You must be signed in to change notification settings - Fork 394
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(form-builder): migrate reference input to use Sanity UI
Co-Authored-By: Marius Lundgård <studio@mariuslundgard.com
- Loading branch information
Showing
9 changed files
with
493 additions
and
334 deletions.
There are no files selected for viewing
559 changes: 266 additions & 293 deletions
559
packages/@sanity/form-builder/src/inputs/ReferenceInput/ReferenceInput.tsx
Large diffs are not rendered by default.
Oops, something went wrong.
1 change: 0 additions & 1 deletion
1
packages/@sanity/form-builder/src/inputs/ReferenceInput/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1 @@ | ||
export * from './ReferenceInput' | ||
export {default} from './ReferenceInput' |
17 changes: 0 additions & 17 deletions
17
packages/@sanity/form-builder/src/inputs/ReferenceInput/styles/ReferenceInput.css
This file was deleted.
Oops, something went wrong.
3 changes: 3 additions & 0 deletions
3
packages/@sanity/form-builder/src/inputs/ReferenceInput/tsconfig.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
// this conditionally enables strict mode for everything in this folder | ||
// (Note: only applies to the typescript compiler service, not when using tsc for build) | ||
{"extends": "../../../tsconfig.strict"} |
48 changes: 48 additions & 0 deletions
48
packages/@sanity/form-builder/src/inputs/ReferenceInput/usePreviewSnapshot.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import {useEffect, useState} from 'react' | ||
import {Reference} from '@sanity/types' | ||
import {tap} from 'rxjs/operators' | ||
import {Observable} from 'rxjs' | ||
|
||
type SnapshotState = { | ||
isLoading: boolean | ||
snapshot: null | PreviewSnapshot | ||
} | ||
|
||
const LOADING_SNAPSHOT: SnapshotState = { | ||
isLoading: true, | ||
snapshot: null, | ||
} | ||
|
||
const NULL_SNAPSHOT: SnapshotState = { | ||
isLoading: false, | ||
snapshot: null, | ||
} | ||
|
||
type PreviewSnapshot = { | ||
_id: string | ||
_type: string | ||
title: string | ||
description: string | ||
} | ||
|
||
export function usePreviewSnapshot( | ||
value: Reference | undefined, | ||
getPreviewSnapshot: (reference: Reference) => Observable<PreviewSnapshot | null> | ||
): SnapshotState { | ||
const [state, setState] = useState<SnapshotState>(LOADING_SNAPSHOT) | ||
|
||
// eslint-disable-next-line consistent-return | ||
useEffect(() => { | ||
if (value?._ref) { | ||
setState(LOADING_SNAPSHOT) | ||
const sub = getPreviewSnapshot(value) | ||
.pipe(tap((snapshot) => setState({isLoading: false, snapshot}))) | ||
.subscribe() | ||
return () => { | ||
sub.unsubscribe() | ||
} | ||
} | ||
setState(NULL_SNAPSHOT) | ||
}, [getPreviewSnapshot, value]) | ||
return state | ||
} |
129 changes: 111 additions & 18 deletions
129
packages/@sanity/form-builder/src/sanity/inputs/SanityReferenceInput.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,117 @@ | ||
import React from 'react' | ||
import React, {ForwardedRef, forwardRef, useCallback, useRef} from 'react' | ||
|
||
import {search, getPreviewSnapshot} from './client-adapters/reference' | ||
import ReferenceInput, {Props} from '../../inputs/ReferenceInput' | ||
import { | ||
Marker, | ||
Path, | ||
Reference, | ||
ReferenceFilterSearchOptions, | ||
ReferenceOptions, | ||
ReferenceSchemaType, | ||
SanityDocument, | ||
} from '@sanity/types' | ||
import {get} from '@sanity/util/paths' | ||
import {FormFieldPresence} from '@sanity/base/lib/presence' | ||
import {from, throwError} from 'rxjs' | ||
import {catchError, mergeMap} from 'rxjs/operators' | ||
import {ReferenceInput} from '../../inputs/ReferenceInput' | ||
import PatchEvent from '../../PatchEvent' | ||
import withValuePath from '../../utils/withValuePath' | ||
import withDocument from '../../utils/withDocument' | ||
import * as adapter from './client-adapters/reference' | ||
|
||
export default class SanityReference extends React.Component<Props> { | ||
_input: any | ||
setInput = (input) => { | ||
this._input = input | ||
// eslint-disable-next-line require-await | ||
async function resolveUserDefinedFilter( | ||
options: ReferenceOptions | undefined, | ||
document: SanityDocument, | ||
valuePath: Path | ||
): Promise<ReferenceFilterSearchOptions> { | ||
if (!options) { | ||
return {} | ||
} | ||
focus() { | ||
this._input.focus() | ||
|
||
if (typeof options.filter === 'function') { | ||
const parentPath = valuePath.slice(0, -1) | ||
const parent = get(document, parentPath) as Record<string, unknown> | ||
return options.filter({document, parentPath, parent}) | ||
} | ||
render() { | ||
return ( | ||
<ReferenceInput | ||
{...this.props} | ||
onSearch={search} | ||
getPreviewSnapshot={getPreviewSnapshot} | ||
ref={this.setInput} | ||
/> | ||
) | ||
|
||
return { | ||
filter: options.filter, | ||
params: 'filterParams' in options ? options.filterParams : undefined, | ||
} | ||
} | ||
|
||
export type Props = { | ||
value?: Reference | ||
compareValue?: Reference | ||
type: ReferenceSchemaType | ||
markers: Marker[] | ||
focusPath: Path | ||
readOnly?: boolean | ||
onFocus: (path: Path) => void | ||
onChange: (event: PatchEvent) => void | ||
level: number | ||
presence: FormFieldPresence[] | ||
|
||
// From withDocument | ||
document: SanityDocument | ||
|
||
// From withValuePath | ||
getValuePath: () => Path | ||
} | ||
|
||
function useValueRef<T>(value: T): {current: T} { | ||
const ref = useRef(value) | ||
ref.current = value | ||
return ref | ||
} | ||
|
||
type SearchError = { | ||
message: string | ||
details?: { | ||
type: string | ||
description: string | ||
} | ||
} | ||
|
||
const SanityReferenceInput = forwardRef(function SanityReferenceInput( | ||
props: Props, | ||
ref: ForwardedRef<HTMLInputElement> | ||
) { | ||
const {getValuePath, type, document} = props | ||
|
||
const documentRef = useValueRef(document) | ||
|
||
const handleSearch = useCallback( | ||
(searchString: string) => | ||
from(resolveUserDefinedFilter(type.options, documentRef.current, getValuePath())).pipe( | ||
mergeMap(({filter, params}) => | ||
adapter.search(searchString, type, {...type.options, filter, params}) | ||
), | ||
catchError((err: SearchError) => { | ||
const isQueryError = err.details && err.details.type === 'queryParseError' | ||
if (type.options?.filter && isQueryError) { | ||
err.message = `Invalid reference filter, please check the custom "filter" option` | ||
} | ||
return throwError(err) | ||
}) | ||
), | ||
[documentRef, getValuePath, type] | ||
) | ||
|
||
const getPreviewSnapshot = useCallback( | ||
(value: Reference) => adapter.getPreviewSnapshot(value, type), | ||
[type] | ||
) | ||
|
||
return ( | ||
<ReferenceInput | ||
{...props} | ||
onSearch={handleSearch} | ||
getPreviewSnapshot={getPreviewSnapshot} | ||
ref={ref} | ||
/> | ||
) | ||
}) | ||
|
||
export default withValuePath(withDocument(SanityReferenceInput)) |
25 changes: 20 additions & 5 deletions
25
packages/@sanity/form-builder/src/sanity/inputs/client-adapters/reference.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,29 @@ | ||
import {map} from 'rxjs/operators' | ||
import {ReferenceFilterSearchOptions, ReferenceSchemaType} from '@sanity/types' | ||
import {Observable} from 'rxjs' | ||
import {createWeightedSearch, observeForPreview} from '../../../legacyParts' | ||
import {versionedClient} from '../../versionedClient' | ||
|
||
export function getPreviewSnapshot(value, referenceType) { | ||
export function getPreviewSnapshot(value: {_ref: string}, referenceType: ReferenceSchemaType) { | ||
return observeForPreview(value, referenceType).pipe(map((result: any) => result.snapshot)) | ||
} | ||
|
||
export function search(textTerm, referenceType, options) { | ||
const doSearch = createWeightedSearch(referenceType.to, versionedClient, options) | ||
return doSearch(textTerm, {includeDrafts: false}).pipe( | ||
map((results: any[]) => results.map((res) => res.hit)) | ||
type SearchHit = { | ||
_id: string | ||
_type: string | ||
} | ||
|
||
type SearchResult = {hit: SearchHit}[] | ||
|
||
export function search( | ||
textTerm: string, | ||
type: ReferenceSchemaType, | ||
options: ReferenceFilterSearchOptions | ||
): Observable<SearchHit[]> { | ||
const searchWeighted = createWeightedSearch(type.to, versionedClient, options) | ||
return searchWeighted(textTerm, {includeDrafts: false}).pipe( | ||
map((results: SearchResult): SearchHit[] => | ||
results.map(({hit}) => ({_type: hit._type, _id: hit._id})) | ||
) | ||
) | ||
} |
3 changes: 3 additions & 0 deletions
3
packages/@sanity/form-builder/src/sanity/inputs/tsconfig.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
// this conditionally enables strict mode for everything in this folder | ||
// (Note: only applies to the typescript compiler service, not when using tsc for build) | ||
{"extends": "../../../tsconfig.strict"} |
42 changes: 42 additions & 0 deletions
42
packages/@sanity/form-builder/src/utils/useObservableCallback.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import React, {DependencyList, useCallback} from 'react' | ||
import {observableCallback} from 'observable-callback' | ||
import {Observable} from 'rxjs' | ||
|
||
export type AsyncCompleteState<T> = { | ||
status: 'complete' | ||
result: T | ||
} | ||
export type AsyncPendingState = { | ||
status: 'pending' | ||
} | ||
export type AsyncErrorState = { | ||
status: 'error' | ||
error: Error | ||
} | ||
|
||
export type AsyncState<T> = AsyncPendingState | AsyncCompleteState<T> | AsyncErrorState | ||
|
||
const EMPTY_DEPS: DependencyList = [] | ||
|
||
export function useObservableCallback<T, U>( | ||
fn: (arg: Observable<T>) => Observable<U>, | ||
dependencies: DependencyList = EMPTY_DEPS | ||
): (arg: T) => void { | ||
const callbackRef = React.useRef(null) | ||
if (callbackRef.current === null) { | ||
callbackRef.current = observableCallback<U>() | ||
} | ||
const [calls$, call] = callbackRef.current | ||
|
||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
const callback = useCallback(fn, dependencies) | ||
|
||
React.useEffect(() => { | ||
const subscription = calls$.pipe(callback).subscribe() | ||
return () => { | ||
subscription.unsubscribe() | ||
} | ||
}, [calls$, call, callback]) | ||
|
||
return call | ||
} |