All files / blong-browser/src/widgets ComponentWidget.tsx

40.25% Statements 31/77
100% Branches 1/1
0% Functions 0/1
40.25% Lines 31/77

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 781x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x                                                                                           1x 1x 1x 1x 1x 1x    
/**
 * ComponentWidget — dynamically loads and renders a portal component.
 *
 * Used as a form field widget with:
 *   `widget: {type: 'component', component: 'portal.page.key', params: {...}}`
 *
 * Dispatches `component/${page}` with the configured page key to obtain a
 * React component, then renders it. Shows a centred spinner while loading.
 *
 * Both `component` and `params` values support `${field}` template expressions
 * resolved against the current form values at mount time.  Uses the
 * blong-template browser engine (Function-constructor based, same syntax as the
 * server-side trusted engine).
 */
import type {IWidgetProps} from '@feasibleone/blong';
import {renderAll} from '@feasibleone/blong-template';
import React from 'react';
import {useBlongForm} from '../components/Form/FormContext.js';
import {useBlong} from '../context/BlongContext.js';
import {ProgressSpinner} from '../primereact/index.js';
 
export function ComponentWidget(widgetProps: IWidgetProps) {
    const {schema, loading} = widgetProps;
    const {handler} = useBlong();
    const formCtx = useBlongForm();
    const page = schema.widget?.component ?? '';
    const params = schema.widget?.params;
    const [errorRender, setErrorRender] = React.useState<Error | null>(null);

    const [LoadedComponent, setLoadedComponent] = React.useState<React.ComponentType<
        IWidgetProps
    > | null>(null);

    const vars = formCtx?.getValues();
    React.useEffect(() => {
        if (!page || loading) return;

        let resolvedPage: string;
        let resolvedParams: Record<string, unknown> = {};
        try {
            resolvedPage = renderAll(page, vars ?? {});
            if (!resolvedPage) return;
            resolvedParams = params
                ? (renderAll(params, vars ?? {}) as Record<string, unknown>)
                : {};
        } catch (error) {
            // eslint-disable-next-line @eslint-react/set-state-in-effect
            setErrorRender(error as Error);
            return;
        }
        void (handler[`component/${resolvedPage}`]({
            ...resolvedParams,
        }, {}) as Promise<React.ComponentType | null>)?.then(component => {
            if (component) {
                setErrorRender(null);
                setLoadedComponent(() => component as React.ComponentType<IWidgetProps>);
            }
        });
    }, [page, params, formCtx, handler, loading, vars]);

    if (!LoadedComponent) {
        return (
            <div
                style={{
                    display: 'flex',
                    alignItems: 'center',
                    justifyContent: 'center',
                    height: '100%',
                }}
            >
                <ProgressSpinner title={errorRender ? errorRender.message : 'Loading...'} />
            </div>
        );
    }
 
    return <LoadedComponent {...widgetProps} />;
}