All Downloads are FREE. Search and download functionalities are using the official Maven repository.

components.buttons.withActionButton.tsx Maven / Gradle / Ivy

The newest version!
import React, { useCallback, useContext, useMemo, useRef } from 'react'
import { useDispatch, useSelector, useStore } from 'react-redux'
import isNil from 'lodash/isNil'

import { validate as validateDatasource } from '../../core/validation/validate'
import { ExpressionContext } from '../../core/Expression/Context'
import { useResolved } from '../../core/Expression/useResolver'
import { id as getID } from '../../utils/id'
import { ModelPrefix } from '../../core/datasource/const'
import { mergeMeta } from '../../ducks/api/utils/mergeMeta'
import { State as GlobalState } from '../../ducks/State'
import { Action } from '../../ducks/Action'
import { ButtonState } from '../../ducks/toolbar/Toolbar'
import { getModelByPrefixAndNameSelector } from '../../ducks/models/selectors'

import { ActionButton } from './ActionButton'
import { useReduxButton, ReduxButtonProps } from './useReduxButton'

type ButtonProps = {
    id: string
    visible?: boolean,
    disabled: boolean,
    count?: string,
    conditions: unknown
}

type ActionButtonProps = ReduxButtonProps & {
    id: string
    entityKey: string
    subMenu?: ButtonProps[]
    validate?: string[]
    action?: Action
    model: ModelPrefix
    datasource: string
}

type UseActionProps = ButtonState & {
    validate?: string[]
    action?: Action
    model: ModelPrefix
    datasource: string
}

type EventHandler = (event: MouseEvent, props: UseActionProps, state: GlobalState) => void

const emptyHandler = () => undefined

function useAction({ validate, ...rest }: UseActionProps, onClick: EventHandler) {
    const store = useStore()
    const dispatch = useDispatch()
    const props = useRef(rest)
    const elementId = useMemo(() => (getID()), [])
    const evalContext = useRef({})

    evalContext.current = useContext(ExpressionContext)
    props.current = rest

    const checkValid = useCallback(async () => {
        if (!validate?.length) {
            return true
        }

        let valid = true

        for (const dataSource of validate) {
            const isDataSourceValid = await validateDatasource(store.getState(), dataSource, ModelPrefix.active, dispatch, true)

            valid = valid && isDataSourceValid
        }

        return valid
    }, [validate, store, dispatch])

    const onClickHandler = useCallback(async (event: MouseEvent) => {
        const valid = await checkValid()

        if (!valid) {
            event.preventDefault()
            event.stopPropagation()

            return
        }

        /* необходимо для позиционирования popover */
        const { action, buttonId, key } = props.current

        const extendedAction = action ? mergeMeta(action, {
            target: elementId,
            key,
            buttonId,
            evalContext: evalContext.current,
        }) : undefined

        const state = store.getState()

        onClick(event, {
            ...props.current,
            action: extendedAction,
            // @ts-ignore FIXME убрать отсюда: нужен где-то дальше в цепочке вызовов
            dispatch,
        }, state)
    }, [props, checkValid, store, elementId, onClick, dispatch, evalContext])

    return {
        ...rest,
        id: elementId,
        onClick: onClickHandler,
        // FIXME убрать отсюда: нужен где-то дальше в цепочке вызовов
        dispatch,
    }
}

export default function withActionButton({ onClick = emptyHandler }: { onClick?: EventHandler } = {}) {
    return (WrappedComponent: Parameters[0]['Component']) => {
        function WithActionButton(props: ActionButtonProps) {
            const reduxProps = useReduxButton(props)

            const withExtendedAction = useAction({
                ...props,
                ...reduxProps,
            }, onClick)

            const { model: prefix, datasource } = props
            const model = useSelector(getModelByPrefixAndNameSelector(prefix, datasource))
            /* TODO брать label & hint для резолва из reduxProps
             * как костыль берём его из пропсов только потому что кнопки в форме уже зарезолвены на уровне филда,
             * и если брать редаксовое значение, то не будет работать обновлиние по модели
             */
            const { hint: propsHint, label: propsLabel, color: propsColor } = props
            const { hint, label, color } = useResolved>({
                hint: propsHint,
                label: propsLabel,
                color: propsColor,
            }, model)

            // FIXME проверить нужно ли это и снести, либо описать зачем
            const { visible, disabled } = props
            const { visible: visibleFromState, disabled: disabledFromState } = withExtendedAction
            const isVisible = !isNil(visible) ? visible : visibleFromState
            const isDisabled = !isNil(disabled) ? disabled : disabledFromState

            const currentMessage = isDisabled ? withExtendedAction.message || hint : hint

            const buttonProps = {
                ...withExtendedAction,
                visible: isVisible,
                disabled: isDisabled,
                hint: currentMessage,
                label,
                placement: withExtendedAction.hintPosition,
                color,
                // url,
            }

            return (
                
            )
        }

        return WithActionButton
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy