Skip to content

Commit

Permalink
[form-builder] Tell array/object inputs not to render change indicator
Browse files Browse the repository at this point in the history
  • Loading branch information
rexxars committed Oct 6, 2020
1 parent 04f3578 commit 0a615b7
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 42 deletions.
40 changes: 26 additions & 14 deletions packages/@sanity/form-builder/src/inputs/ArrayInput/ArrayInput.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react'
import {map} from 'rxjs/operators'
import {isPlainObject, get} from 'lodash'
import {FormFieldPresence} from '@sanity/base/presence'
import {ArraySchemaType, isObjectSchemaType, Marker, Path, SchemaType} from '@sanity/types'
import {FOCUS_TERMINATOR, startsWith} from '@sanity/util/paths'
import formBuilderConfig from 'config:@sanity/form-builder'
Expand Down Expand Up @@ -48,11 +49,13 @@ export type Props = {
readOnly: boolean
filterField: () => any
resolveUploader?: (type: SchemaType, file: File) => Uploader
presence: any
presence: FormFieldPresence[]
}

type ArrayInputState = {
isMoving: boolean
}

export default class ArrayInput extends React.Component<Props, ArrayInputState> {
_element: any
uploadSubscriptions: {
Expand Down Expand Up @@ -143,7 +146,7 @@ export default class ArrayInput extends React.Component<Props, ArrayInputState>
getMemberTypeOfItem(item: ItemValue): SchemaType {
const {type} = this.props
const itemTypeName = resolveTypeName(item)
return type.of.find(memberType => memberType.name === itemTypeName)
return type.of.find(memberType => memberType.name === itemTypeName) as SchemaType
}

renderList = () => {
Expand All @@ -160,12 +163,14 @@ export default class ArrayInput extends React.Component<Props, ArrayInputState>
filterField,
presence
} = this.props

const {isMoving} = this.state
const options = type.options || {}
const hasMissingKeys = value.some(item => !item._key)
const isSortable = options.sortable !== false && !hasMissingKeys
const isGrid = options.layout === 'grid'
const {List, Item} = resolveListComponents(isSortable, isGrid)
const listItemClassName = isMoving ? styles.listItemMute : styles.listItem
const listProps = isSortable
? {
helperClass: styles.movingItem,
Expand All @@ -175,7 +180,7 @@ export default class ArrayInput extends React.Component<Props, ArrayInputState>
useDragHandle: !isGrid
}
: {}
const listItemClassName = isMoving ? styles.listItemMute : styles.listItem

return (
<List className={readOnly ? styles.listReadOnly : styles.list} {...listProps}>
{value.map((item, index) => {
Expand Down Expand Up @@ -223,28 +228,27 @@ export default class ArrayInput extends React.Component<Props, ArrayInputState>
setElement = el => {
this._element = el
}
getUploadOptions = (file: File): Array<ResolvedUploader> => {

getUploadOptions = (file: File): ResolvedUploader[] => {
const {type, resolveUploader} = this.props
if (!resolveUploader) {
return []
}

return type.of
.map(memberType => {
const uploader = resolveUploader(memberType, file)
return (
uploader && {
type: memberType,
uploader
}
)
})
.filter(Boolean)
.map(memberType => ({
type: memberType,
uploader: resolveUploader(memberType, file)
}))
.filter(member => member.uploader)
}

handleFixMissingKeys = () => {
const {onChange, value} = this.props
const patches = value.map((val, i) => setIfMissing(randomKey(), [i, '_key']))
onChange(PatchEvent.from(...patches))
}

handleRemoveNonObjectValues = () => {
const {onChange, value} = this.props
const nonObjects = value
Expand All @@ -253,6 +257,7 @@ export default class ArrayInput extends React.Component<Props, ArrayInputState>
const patches = nonObjects.map(index => unset([index]))
onChange(PatchEvent.from(...patches))
}

handleUpload = ({file, type, uploader}) => {
const {onChange} = this.props
const item = createProtoValue(type)
Expand All @@ -266,13 +271,15 @@ export default class ArrayInput extends React.Component<Props, ArrayInputState>
[key]: events$.subscribe(onChange)
}
}

renderUnknownValueTypes = () => {
const {value, type, readOnly} = this.props
const knownTypes = (type.of || []).map(t => t.name).filter(typeName => typeName !== 'object')
const unknownValues = (value || []).filter(v => v._type && !knownTypes.includes(v._type))
if (!unknownValues || unknownValues.length < 1) {
return null
}

const message = (
<>
These are not defined in the current schema as valid types for this array. This could mean
Expand All @@ -299,6 +306,7 @@ export default class ArrayInput extends React.Component<Props, ArrayInputState>
})}
</>
)

return (
<div className={styles.unknownValueTypes}>
<Warning message={message} values={unknownValues} />
Expand Down Expand Up @@ -337,6 +345,7 @@ export default class ArrayInput extends React.Component<Props, ArrayInputState>
</Fieldset>
)
}

const hasMissingKeys = (value || []).some(item => !item._key)
if (hasMissingKeys) {
return (
Expand All @@ -348,6 +357,7 @@ export default class ArrayInput extends React.Component<Props, ArrayInputState>
onFocus={this.handleFocus}
ref={this.setElement}
markers={markers}
useChangeIndicator={false}
>
<div className={styles.missingKeysWarning}>
Some items in this list are missing their keys. We need to fix this before the list can
Expand All @@ -373,6 +383,7 @@ export default class ArrayInput extends React.Component<Props, ArrayInputState>
const uploadProps = SUPPORT_DIRECT_UPLOADS
? {getUploadOptions: this.getUploadOptions, onUpload: this.handleUpload}
: {}

return (
<FieldSetComponent
markers={markers}
Expand All @@ -385,6 +396,7 @@ export default class ArrayInput extends React.Component<Props, ArrayInputState>
type={type}
ref={this.setElement}
presence={presence.filter(item => item.path[0] === '$')}
useChangeIndicator={false}
{...uploadProps}
>
<div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ export default class ArrayOfPrimitivesInput extends React.PureComponent<Props> {
ref={this.setElement}
markers={markers}
presence={presence.filter(item => item.path[0] === '$' || item.path.length === 0)}
useChangeIndicator={false}
>
<div className={styles.root}>
{value && value.length > 0 && <div className={styles.list}>{this.renderList(value)}</div>}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,7 @@ export default class ImageInput extends React.PureComponent<Props, ImageInputSta
onFocus={this.handleFocus}
onBlur={this.handleBlur}
ref={this.setFocusArea}
useChangeIndicator={false}
{...uploadProps}
>
<div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react'
import {Marker, ObjectSchemaTypeWithOptions, Path} from '@sanity/types'
import {FormFieldPresence} from '@sanity/base/presence'
import Fieldset from 'part:@sanity/components/fieldsets/default'
import PatchEvent, {set, setIfMissing, unset} from '../../PatchEvent'
Expand All @@ -8,9 +9,11 @@ import UnknownFields from './UnknownFields'
import fieldStyles from './styles/Field.css'

import styles from './styles/ObjectInput.css'
import {Marker} from '@sanity/types'

function getCollapsedWithDefaults(options: Record<string, any> = {}, level) {
function getCollapsedWithDefaults(
options: ObjectSchemaTypeWithOptions['options'] = {},
level: number
) {
// todo: warn on "collapsable" and deprecate collapsible in favor of just "collapsed"
// --> relevant: https://github.com/sanity-io/sanity/issues/537
if (options.collapsible === true || options.collapsable === true) {
Expand All @@ -33,22 +36,24 @@ function getCollapsedWithDefaults(options: Record<string, any> = {}, level) {
collapsed: level > 2
}
}

type ObjectInputProps = {
type?: any
value?: {[key: string]: any}
compareValue?: {[key: string]: any}
type: ObjectSchemaTypeWithOptions
value?: Record<string, unknown>
compareValue?: Record<string, unknown>
onChange?: (...args: any[]) => any
onFocus: (...args: any[]) => any
focusPath?: any[]
markers?: Marker[]
onBlur: (...args: any[]) => any
focusPath?: Path
markers?: Marker[]
level?: number
readOnly?: boolean
isRoot?: boolean
filterField?: (...args: any[]) => any
presence: FormFieldPresence[]
}
export default class ObjectInput extends React.PureComponent<ObjectInputProps, {}> {

export default class ObjectInput extends React.PureComponent<ObjectInputProps> {
_firstField: any
static defaultProps = {
onChange() {},
Expand All @@ -57,12 +62,14 @@ export default class ObjectInput extends React.PureComponent<ObjectInputProps, {
isRoot: false,
filterField: () => true
}

handleBlur() {
const {onChange, value} = this.props
if (isEmpty(value)) {
onChange(PatchEvent.from(unset()))
}
}

handleFieldChange = (fieldEvent: PatchEvent, field) => {
const {onChange, type, value, isRoot} = this.props
let event = fieldEvent.prefixAll(field.name)
Expand All @@ -85,6 +92,7 @@ export default class ObjectInput extends React.PureComponent<ObjectInputProps, {
}
onChange(event)
}

renderField(field, level, index) {
const {
type,
Expand All @@ -98,9 +106,11 @@ export default class ObjectInput extends React.PureComponent<ObjectInputProps, {
filterField,
presence
} = this.props

if (!filterField(type, field) || field.type.hidden) {
return null
}

const fieldValue = value && value[field.name]
return (
<Field
Expand All @@ -121,6 +131,7 @@ export default class ObjectInput extends React.PureComponent<ObjectInputProps, {
/>
)
}

renderFieldset(fieldset, fieldsetIndex) {
const {level, focusPath, presence, onFocus} = this.props
const columns = fieldset.options && fieldset.options.columns
Expand All @@ -143,6 +154,7 @@ export default class ObjectInput extends React.PureComponent<ObjectInputProps, {
isCollapsed={isCollapsed}
presence={isCollapsed ? childPresence : []}
onFocus={onFocus}
useChangeIndicator={false}
>
{fieldset.fields.map((field, fieldIndex) => {
return this.renderField(field, level + 2, fieldsetIndex + fieldIndex)
Expand All @@ -151,6 +163,7 @@ export default class ObjectInput extends React.PureComponent<ObjectInputProps, {
</div>
)
}

getRenderedFields() {
const {type, level} = this.props
if (!type.fieldsets) {
Expand All @@ -163,18 +176,22 @@ export default class ObjectInput extends React.PureComponent<ObjectInputProps, {
: this.renderFieldset(fieldset, i)
})
}

renderUnknownFields() {
const {value, type, onChange, readOnly} = this.props
if (!type.fields) {
return null
}

const knownFieldNames = type.fields.map(field => field.name)
const unknownFields = Object.keys(value || {}).filter(
key => !key.startsWith('_') && !knownFieldNames.includes(key)
)

if (unknownFields.length === 0) {
return null
}

return (
<UnknownFields
readOnly={readOnly}
Expand All @@ -184,18 +201,22 @@ export default class ObjectInput extends React.PureComponent<ObjectInputProps, {
/>
)
}

setFirstField = el => {
this._firstField = el
}

focus() {
if (this._firstField) {
this._firstField.focus()
}
}

render() {
const {type, level, focusPath, onFocus, presence, markers} = this.props
const renderedFields = this.getRenderedFields()
const renderedUnknownFields = this.renderUnknownFields()

if (level === 0) {
return (
<div className={styles.root}>
Expand All @@ -206,6 +227,7 @@ export default class ObjectInput extends React.PureComponent<ObjectInputProps, {
</div>
)
}

const collapsibleOpts = getCollapsedWithDefaults(type.options, level)
const isExpanded = focusPath.length > 0
const columns = type.options && type.options.columns
Expand All @@ -222,6 +244,7 @@ export default class ObjectInput extends React.PureComponent<ObjectInputProps, {
markers={markers}
presence={presence.filter(item => item.path[0] === '$' || item.path.length === 0)}
onFocus={onFocus}
useChangeIndicator={false}
>
{renderedFields}
{renderedUnknownFields}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ export default class OptionsArrayInput extends React.PureComponent<OptionsArrayI
presence={presence}
level={level}
onClick={this.handleFocus}
useChangeIndicator={false}
>
<div>
<div
Expand Down

0 comments on commit 0a615b7

Please sign in to comment.