import get from 'lodash/get' ; import { ControlElement , JsonSchema , UISchemaElement } from '../models' ; import find from 'lodash/find' ; import type { JsonFormsCellRendererRegistryEntry , JsonFormsRendererRegistryEntry , } from '../reducers' ; import { findUISchema , getAjv , getCells , getConfig , getData , getErrorAt , getErrorTranslator , getRenderers , getSchema , getSubErrorsAt , getTranslator , getUiSchema , JsonFormsUISchemaRegistryEntry , } from '../reducers' ; import { RankedTester } from '../testers' ; import { hasShowRule , isInherentlyEnabled , isVisible } from './runtime' ; import { createLabelDescriptionFrom } from './label' ; import { CombinatorKeyword , resolveSubSchemas } from './combinators' ; import { moveDown , moveUp } from './array' ; import { AnyAction , Dispatch } from './type' ; import { Resolve } from './util' ; import { composePaths , composeWithUi } from './path' ; import { CoreActions , update } from '../actions' ; import { ErrorObject } from 'ajv' ; import { JsonFormsState } from '../store' ; import { getCombinedErrorMessage , getI18nKey , getI18nKeyPrefix , Translator } from '../i18n' ; const isRequired = ( schema , schemaPath , rootSchema ) : boolean => { const pathSegments = schemaPath . split ( '/' ) ; const lastSegment = pathSegments [ pathSegments . length - 1 ] ; const nextHigherSchemaSegments = pathSegments . slice ( 0 , pathSegments . length - 2 ) ; const nextHigherSchemaPath = nextHigherSchemaSegments . join ( '/' ) ; const nextHigherSchema = Resolve . schema ( schema , nextHigherSchemaPath , rootSchema ) ; return ( nextHigherSchema !== undefined && nextHigherSchema . required !== undefined && nextHigherSchema . required . indexOf ( lastSegment ) !== - 1 ) ; } ; param { string | undefined } label param { boolean } required param { boolean } hideRequiredAsterisk returns { string } export const computeLabel = ( label , required , hideRequiredAsterisk ) : string => { return `${ label ?? '' }${ required && ! hideRequiredAsterisk ? '*' : '' }` ; } ; param { boolean } required param { boolean } hideRequiredAsterisk returns { boolean } export const showAsRequired = ( required , hideRequiredAsterisk ) : boolean => { return required && ! hideRequiredAsterisk ; } ; param schema returns { any } export const createDefaultValue = ( schema ) => { switch ( schema . type ) { case 'string' : if ( schema . format === 'date-time' || schema . format === 'date' || schema . format === 'time' ) { return new Date ( ) ; } return '' ; case 'integer' : case 'number' : return 0 ; case 'boolean' : return false ; case 'array' : return [ ] ; case 'null' : return null ; default : return { } ; } } ; param visible param description param isFocused returns { boolean } export const isDescriptionHidden = ( visible , description , isFocused , showUnfocusedDescription ) : boolean => { return ( description === undefined || ( description !== undefined && ! visible ) || ( ! showUnfocusedDescription && ! isFocused ) ) ; } ; export interface WithClassname { className ? : string ; } export interface EnumOption { label : string ; value : any ; } export const enumToEnumOptionMapper = ( e , t ? , i18nKey ? ) : EnumOption => { let label = typeof e === 'string' ? e : JSON . stringify ( e ) ; if ( t ) { if ( i18nKey ) { label = t ( `${ i18nKey }.${ label }` , label ) ; } else { label = t ( label , label ) ; } } return { label , value : e } ; } ; export const oneOfToEnumOptionMapper = ( e , t ? , fallbackI18nKey ? ) : EnumOption => { let label = e . title ?? ( typeof e . const === 'string' ? e . const : JSON . stringify ( e . const ) ) ; if ( t ) { if ( e . i18n ) { label = t ( e . i18n , label ) ; } else if ( fallbackI18nKey ) { label = t ( `${ fallbackI18nKey }.${ label }` , label ) ; } else { label = t ( label , label ) ; } } return { label , value : e . const , } ; } ; export interface OwnPropsOfRenderer { /**
     * The UI schema to be rendered.
     */ uischema ? : UISchemaElement ; /**
     * The JSON schema that describes the data.
     */ schema ? : JsonSchema ; /**
     * Whether the rendered element should be enabled.
     */ enabled ? : boolean ; /**
     * Whether the rendered element should be visible.
     */ visible ? : boolean ; /**
     * Optional instance path. Necessary when the actual data
     * path can not be inferred via the UI schema element as
     * it is the case with nested controls.
     */ path ? : string ; renderers ? : JsonFormsRendererRegistryEntry [ ] ; cells ? : JsonFormsCellRendererRegistryEntry [ ] ; uischemas ? : JsonFormsUISchemaRegistryEntry [ ] ; } export interface OwnPropsOfControl extends OwnPropsOfRenderer { id ? : string ; uischema ? : ControlElement ; } export interface OwnPropsOfEnum { options ? : EnumOption [ ] ; } export interface OwnPropsOfLayout extends OwnPropsOfRenderer { direction ? : 'row' | 'column' ; } /**
 * State-based props of a  Renderer .
  export interface StatePropsOfRenderer { /**
     * Any configuration options for the element.
     */ config ? : any ; /**
     * The UI schema to be rendered.
     */ uischema : UISchemaElement ; /**
     * The JSON schema that describes the data.
     */ schema : JsonSchema ; /**
     * The data to be rendered.
     */ data ? : any ; /**
     * Whether the rendered element should be enabled.
     */ enabled : boolean ; /**
     * Whether the rendered element should be visible.
     */ visible : boolean ; /**
     * Instance path the data is written to, in case of a control.
     */ path : string ; /**
     * All available renderers.
     */ renderers ? : JsonFormsRendererRegistryEntry [ ] ; /**
     * All available cell renderers.
     */ cells ? : JsonFormsCellRendererRegistryEntry [ ] ; } /**
 * State-based properties for UI schema elements that have a scope.
 */ export interface StatePropsOfScopedRenderer extends StatePropsOfRenderer { uischema : ControlElement ; /**
     * Any validation errors that are caused by the data to be rendered.
     */ errors : string ; /**
     * The data to be rendered.
     */ data : any ; /**
     * The root schema as returned by the store.
     */ rootSchema : JsonSchema ; /**
     * A unique ID that should be used for rendering the scoped UI schema element.
     */ id : string ; } /**
 * Props of a  Renderer .
  export interface RendererProps extends StatePropsOfRenderer { } /**
 * State-based props of a Control
 */ export interface StatePropsOfControl extends StatePropsOfScopedRenderer { cells ? : { tester : RankedTester ; cell : any ; } [ ] ; /**
     * The label for the rendered element.
     */ label : string ; /**
     * Description of input cell
     */ description ? : string ; /**
     * Whether the rendered data is required.
     */ required ? : boolean ; } /**
 * Dispatch-based props of a Control.
 */ export interface DispatchPropsOfControl { param { string } path param { any } value handleChange ( path , value ) : void ; } /**
 * Props of a Control.
 */ export interface ControlProps extends StatePropsOfControl , DispatchPropsOfControl { } /**
 * State props of a layout;
 */ export interface StatePropsOfLayout extends StatePropsOfRenderer { /**
     * Direction for the layout to flow
     */ direction : 'row' | 'column' ; } export interface LayoutProps extends StatePropsOfLayout { } /**
 * The state of a control.
 */ export interface ControlState { /**
     * The current value.
     */ value : any ; /**
     * Whether the control is focused.
     */ isFocused : boolean ; } param state param ownProps returns { StatePropsOfControl } export const mapStateToControlProps = ( state , ownProps ) : StatePropsOfControl => { const { uischema } = ownProps ; const rootData = getData ( state ) ; const path = composeWithUi ( uischema , ownProps . path ) ; const visible = ownProps . visible === undefined || hasShowRule ( uischema ) ? isVisible ( uischema , rootData , ownProps . path , getAjv ( state ) ) : ownProps . visible ; const controlElement = uischema as ControlElement ; const id = ownProps . id ; const rootSchema = getSchema ( state ) ; const required = controlElement . scope !== undefined && isRequired ( ownProps . schema , controlElement . scope , rootSchema ) ; const resolvedSchema = Resolve . schema ( ownProps . schema || rootSchema , controlElement . scope , rootSchema ) ; const errors = getErrorAt ( path , resolvedSchema ) ( state ) ; const description = resolvedSchema !== undefined ? resolvedSchema . description : '' ; const data = Resolve . data ( rootData , path ) ; const labelDesc = createLabelDescriptionFrom ( uischema , resolvedSchema ) ; const label = labelDesc . show ? labelDesc . text : '' ; const config = getConfig ( state ) ; const enabled = isInherentlyEnabled ( state , ownProps , uischema , resolvedSchema || rootSchema , rootData , config ) ; const schema = resolvedSchema ?? rootSchema ; const t = getTranslator ( ) ( state ) ; const te = getErrorTranslator ( ) ( state ) ; const i18nLabel = t ( getI18nKey ( schema , uischema , path , 'label' ) , label ) ; const i18nDescription = t ( getI18nKey ( schema , uischema , path , 'description' ) , description ) ; const i18nErrorMessage = getCombinedErrorMessage ( errors , te , t , schema , uischema , path ) ; return { data , description : i18nDescription , errors : i18nErrorMessage , label : i18nLabel , visible , enabled , id , path , required , uischema , schema , config : getConfig ( state ) , cells : ownProps . cells || state . jsonforms . cells , rootSchema } ; } ; param dispatch returns { DispatchPropsOfControl } export const mapDispatchToControlProps = ( dispatch ) : DispatchPropsOfControl => ( { handleChange ( path , value ) { dispatch ( update ( path , ( ) => value ) ) ; } } ) ; param state param ownProps returns { StatePropsOfControl & OwnPropsOfEnum } export const mapStateToEnumControlProps = ( state , ownProps ) : StatePropsOfControl & OwnPropsOfEnum => { const props = mapStateToControlProps ( state , ownProps ) ; const options = ownProps . options || props . schema . enum ?. map ( e => enumToEnumOptionMapper ( e , getTranslator ( ) ( state ) , getI18nKeyPrefix ( props . schema , props . uischema , props . path ) ) ) || ( props . schema . const && [ enumToEnumOptionMapper ( props . schema . const , getTranslator ( ) ( state ) , getI18nKeyPrefix ( props . schema , props . uischema , props . path ) ) ] ) ; return { ... props , options } ; } ; param state param ownProps returns { StatePropsOfControl & OwnPropsOfEnum } export const mapStateToOneOfEnumControlProps = ( state , ownProps ) : StatePropsOfControl & OwnPropsOfEnum => { const props = mapStateToControlProps ( state , ownProps ) ; const options = ownProps . options || ( props . schema . oneOf as JsonSchema [ ] ) ?. map ( oneOfSubSchema => oneOfToEnumOptionMapper ( oneOfSubSchema , getTranslator ( ) ( state ) , getI18nKeyPrefix ( props . schema , props . uischema , props . path ) ) ) ; return { ... props , options } ; } ; param state param ownProps returns { StatePropsOfControl & OwnPropsOfEnum } export const mapStateToMultiEnumControlProps = ( state , ownProps ) : StatePropsOfControl & OwnPropsOfEnum => { const props = mapStateToControlProps ( state , ownProps ) ; const items = props . schema . items as JsonSchema ; const options = ownProps . options || ( items ?. oneOf && ( items . oneOf as JsonSchema [ ] ) . map ( oneOfSubSchema => oneOfToEnumOptionMapper ( oneOfSubSchema , state . jsonforms . i18n ?. translate , getI18nKeyPrefix ( props . schema , props . uischema , props . path ) ) ) ) || items ?. enum ?. map ( e => enumToEnumOptionMapper ( e , state . jsonforms . i18n ?. translate , getI18nKeyPrefix ( props . schema , props . uischema , props . path ) ) ) ; return { ... props , options } ; } ; param state param ownProps returns { StatePropsOfControl } export const mapStateToMasterListItemProps = ( state , ownProps ) : StatePropsOfMasterItem => { const { schema , path , index } = ownProps ; const firstPrimitiveProp = schema . properties ? find ( Object . keys ( schema . properties ) , propName => { const prop = schema . properties [ propName ] ; return ( prop . type === 'string' || prop . type === 'number' || prop . type === 'integer' ) ; } ) : undefined ; const childPath = composePaths ( path , `${ index }` ) ; const childData = Resolve . data ( getData ( state ) , childPath ) ; const childLabel = firstPrimitiveProp ? childData [ firstPrimitiveProp ] : '' ; return { ... ownProps , childLabel } ; } ; /**
 * State-based props of a table control.
 */ export interface StatePropsOfControlWithDetail extends StatePropsOfControl { uischemas ? : JsonFormsUISchemaRegistryEntry [ ] ; renderers ? : JsonFormsRendererRegistryEntry [ ] ; cells ? : JsonFormsCellRendererRegistryEntry [ ] ; } export interface OwnPropsOfMasterListItem { index : number ; selected : boolean ; path : string ; schema : JsonSchema ; handleSelect ( index ) : ( ) => void ; removeItem ( path , value ) : ( ) => void ; } export interface StatePropsOfMasterItem extends OwnPropsOfMasterListItem { childLabel : string ; } param state param ownProps returns { StatePropsOfArrayControl } export const mapStateToControlWithDetailProps = ( state , ownProps ) : StatePropsOfControlWithDetail => { const { ... props } = mapStateToControlProps ( state , ownProps ) ; return { ... props , uischemas : state . jsonforms . uischemas } ; } ; export interface ControlWithDetailProps extends StatePropsOfControlWithDetail , DispatchPropsOfControl { } /**
 * State-based props of a table control.
 */ export interface StatePropsOfArrayControl extends StatePropsOfControlWithDetail { childErrors ? : ErrorObject [ ] ; } param state param ownProps returns { StatePropsOfArrayControl } export const mapStateToArrayControlProps = ( state , ownProps ) : StatePropsOfArrayControl => { const { path , schema , uischema , ... props } = mapStateToControlWithDetailProps ( state , ownProps ) ; const resolvedSchema = Resolve . schema ( schema , 'items' , props . rootSchema ) ; const childErrors = getSubErrorsAt ( path , resolvedSchema ) ( state ) ; return { ... props , path , uischema , schema : resolvedSchema , childErrors , renderers : ownProps . renderers || getRenderers ( state ) , cells : ownProps . cells || getCells ( state ) } ; } ; /**
 * Dispatch props of a table control
 */ export interface DispatchPropsOfArrayControl { addItem ( path , value ) : ( ) => void ; removeItems ? ( path , toDelete ) : ( ) => void ; moveUp ? ( path , toMove ) : ( ) => void ; moveDown ? ( path , toMove ) : ( ) => void ; } param dispatch returns { DispatchPropsOfArrayControl } export const mapDispatchToArrayControlProps = ( dispatch ) : DispatchPropsOfArrayControl => ( { addItem : ( path , value ) => ( ) => { dispatch ( update ( path , array => { if ( array === undefined || array === null ) { return [ value ] ; } array . push ( value ) ; return array ; } ) ) ; } , removeItems : ( path , toDelete ) => ( ) => { dispatch ( update ( path , array => { toDelete . sort ( ) . reverse ( ) . forEach ( s => array . splice ( s , 1 ) ) ; return array ; } ) ) ; } , moveUp : ( path , toMove ) => ( ) => { dispatch ( update ( path , array => { moveUp ( array , toMove ) ; return array ; } ) ) ; } , moveDown : ( path , toMove ) => ( ) => { dispatch ( update ( path , array => { moveDown ( array , toMove ) ; return array ; } ) ) ; } } ) ; export interface DispatchPropsOfMultiEnumControl { addItem : ( path : string , value : any ) => void ; removeItem ? : ( path : string , toDelete : any ) => void ; } export const mapDispatchToMultiEnumProps = ( dispatch ) : DispatchPropsOfMultiEnumControl => ( { addItem : ( path , value ) => { dispatch ( update ( path , data => { if ( data === undefined || data === null ) { return [ value ] ; } data . push ( value ) ; return data ; } ) ) ; } , removeItem : ( path , toDelete ) => { dispatch ( update ( path , data => { const indexInData = data . indexOf ( toDelete ) ; data . splice ( indexInData , 1 ) ; return data ; } ) ) ; } } ) ; /**
 * Props of an array control.
 */ export interface ArrayControlProps extends StatePropsOfArrayControl , DispatchPropsOfArrayControl { } export const layoutDefaultProps = { visible : true , enabled : true , path : '' , direction : 'column' } ; const getDirection = ( uischema ) => { if ( uischema . type === 'HorizontalLayout' ) { return 'row' ; } if ( uischema . type === 'VerticalLayout' ) { return 'column' ; } return layoutDefaultProps . direction ; } ; param state param ownProps returns { StatePropsOfLayout } export const mapStateToLayoutProps = ( state , ownProps ) : LayoutProps => { const rootData = getData ( state ) ; const { uischema } = ownProps ; const visible = ownProps . visible === undefined || hasShowRule ( uischema ) ? isVisible ( ownProps . uischema , rootData , ownProps . path , getAjv ( state ) ) : ownProps . visible ; const data = Resolve . data ( rootData , ownProps . path ) ; const config = getConfig ( state ) ; const enabled = isInherentlyEnabled ( state , ownProps , uischema , undefined , rootData , config ) ; return { ... layoutDefaultProps , renderers : ownProps . renderers || getRenderers ( state ) , cells : ownProps . cells || getCells ( state ) , visible , enabled , path : ownProps . path , data , uischema : ownProps . uischema , schema : ownProps . schema , direction : ownProps . direction ?? getDirection ( uischema ) } ; } ; export type RefResolver = ( schema : JsonSchema ) => Promise < JsonSchema > ; export interface OwnPropsOfJsonFormsRenderer extends OwnPropsOfRenderer { } export interface StatePropsOfJsonFormsRenderer extends OwnPropsOfJsonFormsRenderer { rootSchema : JsonSchema ; } export interface JsonFormsProps extends StatePropsOfJsonFormsRenderer { } export const mapStateToJsonFormsRendererProps = ( state , ownProps ) : StatePropsOfJsonFormsRenderer => { let uischema = ownProps . uischema ; if ( uischema === undefined ) { if ( ownProps . schema ) { uischema = findUISchema ( state . jsonforms . uischemas , ownProps . schema , undefined , ownProps . path ) ; } else { uischema = getUiSchema ( state ) ; } } return { renderers : ownProps . renderers || get ( state . jsonforms , 'renderers' ) || [ ] , cells : ownProps . cells || get ( state . jsonforms , 'cells' ) || [ ] , schema : ownProps . schema || getSchema ( state ) , rootSchema : getSchema ( state ) , uischema : uischema , path : ownProps . path } ; } ; export const controlDefaultProps = { ... layoutDefaultProps , errors : [ ] as string [ ] } ; export interface StatePropsOfCombinator extends OwnPropsOfControl { rootSchema : JsonSchema ; path : string ; id : string ; indexOfFittingSchema : number ; uischemas : JsonFormsUISchemaRegistryEntry [ ] ; data : any ; } export const mapStateToCombinatorRendererProps = ( state , ownProps , keyword ) : StatePropsOfCombinator => { const { uischema } = ownProps ; const path = composeWithUi ( uischema , ownProps . path ) ; const rootSchema = getSchema ( state ) ; const resolvedSchema = Resolve . schema ( ownProps . schema || rootSchema , uischema . scope , rootSchema ) ; const visible = ownProps . visible === undefined || hasShowRule ( uischema ) ? isVisible ( uischema , getData ( state ) , ownProps . path , getAjv ( state ) ) : ownProps . visible ; const id = ownProps . id ; const data = Resolve . data ( getData ( state ) , path ) ; const ajv = state . jsonforms . core . ajv ; const schema = resolvedSchema || rootSchema ; const _schema = resolveSubSchemas ( schema , rootSchema , keyword ) ; const structuralKeywords = [ 'required' , 'additionalProperties' , 'type' , 'enum' , 'const' ] ; const dataIsValid = ( errors ) : boolean => { return ( ! errors || errors . length === 0 || ! errors . find ( e => structuralKeywords . indexOf ( e . keyword ) !== - 1 ) ) ; } ; let indexOfFittingSchema ; for ( let i = 0 ; i < _schema [ keyword ] . length ; i ++ ) { try { const valFn = ajv . compile ( _schema [ keyword ] [ i ] ) ; valFn ( data ) ; if ( dataIsValid ( valFn . errors ) ) { indexOfFittingSchema = i ; break ; } } catch ( error ) { console . debug ( "Combinator subschema is not self contained, can't hand it over to AJV" ) ; } } return { data , path , schema , rootSchema , visible , id , indexOfFittingSchema , uischemas : state . jsonforms . uischemas , uischema } ; } ; export interface CombinatorRendererProps extends StatePropsOfCombinator , DispatchPropsOfControl { } param state param ownProps returns { StatePropsOfCombinator } export const mapStateToAllOfProps = ( state , ownProps ) : StatePropsOfCombinator => mapStateToCombinatorRendererProps ( state , ownProps , 'allOf' ) ; export const mapStateToAnyOfProps = ( state , ownProps ) : StatePropsOfCombinator => { return mapStateToCombinatorRendererProps ( state , ownProps , 'anyOf' ) ; } ; export const mapStateToOneOfProps = ( state , ownProps ) : StatePropsOfCombinator => { return mapStateToCombinatorRendererProps ( state , ownProps , 'oneOf' ) ; } ; export interface StatePropsOfArrayLayout extends StatePropsOfControlWithDetail { data : number ; minItems ? : number ; } param state param ownProps returns { StatePropsOfArrayControl } export const mapStateToArrayLayoutProps = ( state , ownProps ) : StatePropsOfArrayLayout => { const { path , schema , uischema , errors , ... props } = mapStateToControlWithDetailProps ( state , ownProps ) ; const resolvedSchema = Resolve . schema ( schema , 'items' , props . rootSchema ) ; const childErrors = getCombinedErrorMessage ( getSubErrorsAt ( path , resolvedSchema ) ( state ) , getErrorTranslator ( ) ( state ) , getTranslator ( ) ( state ) , undefined , undefined , undefined ) ; const allErrors = errors + ( errors . length > 0 && childErrors . length > 0 ? '\n' : '' ) + childErrors ; return { ... props , path , uischema , schema : resolvedSchema , data : props . data ? props . data . length : 0 , errors : allErrors , minItems : schema . minItems } ; } ; /**
 * Props of an array control.
 */ export interface ArrayLayoutProps extends StatePropsOfArrayLayout , DispatchPropsOfArrayControl { }