<commit_before>import {activatable} from 'widget/activation/activatable'
import {form} from 'widget/form'
import {initValues} from 'app/home/body/process/recipe'
import {selectFrom} from 'stateUtils'
import {withPanelWizardContext} from 'widget/panelWizard'
import {withRecipe} from 'app/home/body/process/recipeContext'
import FormPanel from 'widget/formPanel'
import PropTypes from 'prop-types'
import React from 'react'
import actionBuilder from 'action-builder'

const Context = React.createContext()

const defaultPolicy = ({values, wizardContext: {wizard}}) =>
    wizard || selectFrom(values, 'dirty')
        ? {_: 'disallow'}
        : {_: 'allow-then-deactivate'}

export const recipeFormPanel = ({
    id,
    fields,
    constraints,
    path,
    mapRecipeToProps = () => ({}),
    modelToValues = model => ({...model}),
    valuesToModel = values => ({...values}),
    policy = defaultPolicy,
    additionalPolicy = () => ({})
}) => {
    path = path || (() => id)
    const createMapRecipeToProps = mapRecipeToProps =>
        (recipe, props) => {
            const evaluatedPath = path(props)
            if (!evaluatedPath) {
                return null
            }
            const additionalProps = mapRecipeToProps(recipe)
            const model = selectFrom(recipe, ['model', evaluatedPath])
            const values = selectFrom(recipe, ['ui', evaluatedPath])
            return {recipeId: recipe.id, model, values, ...additionalProps}
        }

    const valuesSpec = {
        getModel: props => props.model,
        getValues: props => props.values,
        modelToValues,
        onInitialized: ({model, values, props}) => {
            const {recipeContext: {statePath}} = props
            const evaluatedPath = path(props)
            setModelAndValues({evaluatedPath, statePath, model, values})
        }
    }

    return WrappedComponent => {
        class HigherOrderComponent extends React.Component {
            constructor(props) {
                super(props)
                const {values, recipeContext: {statePath}, form} = props
                this.prevValues = values

                form.onDirtyChanged(dirty => setDirty({evaluatedPath: path(props), statePath, dirty}))
            }

            render() {
                const {form, recipeContext: {statePath}, activatable: {deactivate}} = this.props
                return (
                    <Context.Provider value={{
                        id,
                        evaluatedPath: path(this.props),
                        form,
                        statePath,
                        valuesToModel,
                        deactivate,
                        prevValues: this.prevValues
                    }}>
                        {React.createElement(WrappedComponent, this.props)}
                    </Context.Provider>
                )
            }
        }

        const policyToApply = props => ({...policy(props), ...additionalPolicy(props)})
        return (
            withPanelWizardContext()(
                activatable({id, policy: policyToApply})(
                    withRecipe(createMapRecipeToProps(mapRecipeToProps))(
                        initValues(valuesSpec)(
                            form({fields, constraints})(
                                HigherOrderComponent
                            )
                        )
                    )
                )
            )
        )
    }
}
<commit_msg>Fixed bug where recipeFormPanel didn't update policy when form got dirty.<commit_after>import {activatable} from 'widget/activation/activatable'
import {form} from 'widget/form'
import {initValues} from 'app/home/body/process/recipe'
import {selectFrom} from 'stateUtils'
import {withPanelWizardContext} from 'widget/panelWizard'
import {withRecipe} from 'app/home/body/process/recipeContext'
import FormPanel from 'widget/formPanel'
import PropTypes from 'prop-types'
import React from 'react'
import actionBuilder from 'action-builder'

const Context = React.createContext()

const defaultPolicy = ({values, wizardContext: {wizard}}) =>
    wizard || selectFrom(values, 'dirty')
        ? {_: 'disallow'}
        : {_: 'allow-then-deactivate'}

export const recipeFormPanel = ({
    id,
    fields,
    constraints,
    path,
    mapRecipeToProps = () => ({}),
    modelToValues = model => ({...model}),
    valuesToModel = values => ({...values}),
    policy = defaultPolicy,
    additionalPolicy = () => ({})
}) => {
    path = path || (() => id)
    const createMapRecipeToProps = mapRecipeToProps =>
        (recipe, props) => {
            const evaluatedPath = path(props)
            if (!evaluatedPath) {
                return null
            }
            const additionalProps = mapRecipeToProps(recipe)
            const model = selectFrom(recipe, ['model', evaluatedPath])
            const values = selectFrom(recipe, ['ui', evaluatedPath])
            return {recipeId: recipe.id, model, values, ...additionalProps}
        }

    const valuesSpec = {
        getModel: props => props.model,
        getValues: props => props.values,
        modelToValues,
        onInitialized: ({model, values, props}) => {
            const {recipeContext: {statePath}} = props
            const evaluatedPath = path(props)
            setModelAndValues({evaluatedPath, statePath, model, values})
        }
    }

    return WrappedComponent => {
        class HigherOrderComponent extends React.Component {
            constructor(props) {
                super(props)
                const {values, recipeContext: {statePath}, form} = props
                this.prevValues = values

                form.onDirtyChanged(dirty => setDirty({evaluatedPath: path(props), statePath, dirty}))
            }

            render() {
                const {form, recipeContext: {statePath}, activatable: {deactivate}} = this.props
                return (
                    <Context.Provider value={{
                        id,
                        evaluatedPath: path(this.props),
                        form,
                        statePath,
                        valuesToModel,
                        deactivate,
                        prevValues: this.prevValues
                    }}>
                        {React.createElement(WrappedComponent, this.props)}
                    </Context.Provider>
                )
            }
        }

        const policyToApply = props => ({...policy(props), ...additionalPolicy(props)})
        // [HACK] Using withRecipe() twice.
        // activatable() is dependent on recipe for its policy -> withRecipe() before activatable()
        // withRecipe() is dependent on activatable props -> activatable() before withRecipe()
        return (
            withPanelWizardContext()(
                withRecipe(createMapRecipeToProps(mapRecipeToProps))(
                    activatable({id, policy: policyToApply})(
                        withRecipe(createMapRecipeToProps(mapRecipeToProps))(
                            initValues(valuesSpec)(
                                form({fields, constraints})(
                                    HigherOrderComponent
                                )
                            )
                        )
                    )
                )
            )
        )
    }
}

{'fraction_non_alphanumeric': 0.10714771953710006, 'fraction_numerical': 0.0, 'mean_word_length': 1.7268002969561989, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 39, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6720304', 'n_tokens_mistral': 1858, 'n_tokens_neox': 1784, 'n_words': 409}