This is an automated email from the ASF dual-hosted git repository.

marat pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-karavan.git


The following commit(s) were added to refs/heads/main by this push:
     new edb92d34 Bean Wizard
edb92d34 is described below

commit edb92d343f6cd8bf3ec121aa412451d82ea26be2
Author: Marat Gubaidullin <ma...@talismancloud.io>
AuthorDate: Thu Apr 25 11:22:46 2024 -0400

    Bean Wizard
---
 .../main/webui/src/designer/KaravanDesigner.tsx    |   9 +-
 .../main/webui/src/designer/editor/CodeEditor.tsx  |   4 +-
 .../webui/src/designer/property/DslProperties.tsx  |   2 +-
 .../webui/src/designer/route/DslConnections.tsx    |  92 ++++++++--------
 .../main/webui/src/project/beans/BeanWizard.tsx    |  64 ++++-------
 .../src/main/webui/src/util/useFormUtil.tsx        | 119 ++++++++++++++-------
 6 files changed, 161 insertions(+), 129 deletions(-)

diff --git a/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx 
b/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx
index cee6fbc4..ea97a7e1 100644
--- a/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx
+++ b/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx
@@ -128,8 +128,13 @@ export function KaravanDesigner(props: Props) {
     }
 
     function getCode(integration: Integration): string {
-        const clone = CamelUtil.cloneIntegration(integration);
-        return CamelDefinitionYaml.integrationToYaml(clone);
+        try {
+            const clone = CamelUtil.cloneIntegration(integration);
+            return CamelDefinitionYaml.integrationToYaml(clone);
+        } catch (e) {
+            EventBus.sendAlert('Error parsing Yaml', (e as Error).message, 
'danger');
+            return '';
+        }
     }
 
     function getTab(title: string, tooltip: string, icon: string, showBadge: 
boolean = false) {
diff --git a/karavan-app/src/main/webui/src/designer/editor/CodeEditor.tsx 
b/karavan-app/src/main/webui/src/designer/editor/CodeEditor.tsx
index d833148e..4bb4e3c5 100644
--- a/karavan-app/src/main/webui/src/designer/editor/CodeEditor.tsx
+++ b/karavan-app/src/main/webui/src/designer/editor/CodeEditor.tsx
@@ -20,6 +20,7 @@ import Editor from "@monaco-editor/react";
 import {shallow} from "zustand/shallow";
 import {useDesignerStore, useIntegrationStore} from "../DesignerStore";
 import {CamelDefinitionYaml} from "karavan-core/lib/api/CamelDefinitionYaml";
+import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
 
 export function CodeEditor () {
 
@@ -29,7 +30,8 @@ export function CodeEditor () {
 
     useEffect(() => {
         try {
-            const c = CamelDefinitionYaml.integrationToYaml(integration);
+            const clone = CamelUtil.cloneIntegration(integration);
+            const c = CamelDefinitionYaml.integrationToYaml(clone);
             setCode(c);
         } catch (e: any) {
             const message: string = e?.message ? e.message : e.reason;
diff --git a/karavan-app/src/main/webui/src/designer/property/DslProperties.tsx 
b/karavan-app/src/main/webui/src/designer/property/DslProperties.tsx
index 7448351c..a8ce0076 100644
--- a/karavan-app/src/main/webui/src/designer/property/DslProperties.tsx
+++ b/karavan-app/src/main/webui/src/designer/property/DslProperties.tsx
@@ -62,7 +62,7 @@ export function DslProperties(props: Props) {
 
     const [showAdvanced, setShowAdvanced] = useState<boolean>(false);
 
-    function getClonableElementHeader(): JSX.Element {
+    function getClonableElementHeader(): React.JSX.Element {
         const title = selectedStep && CamelDisplayUtil.getTitle(selectedStep);
         const description = selectedStep?.dslName ? 
CamelMetadataApi.getCamelModelMetadataByClassName(selectedStep?.dslName)?.description
 : title;
         const descriptionLines: string [] = description ? 
description?.split("\n") : [""];
diff --git a/karavan-app/src/main/webui/src/designer/route/DslConnections.tsx 
b/karavan-app/src/main/webui/src/designer/route/DslConnections.tsx
index a711780b..0b5dead4 100644
--- a/karavan-app/src/main/webui/src/designer/route/DslConnections.tsx
+++ b/karavan-app/src/main/webui/src/designer/route/DslConnections.tsx
@@ -47,7 +47,7 @@ export function DslConnections() {
         setTons(prevState => {
             const data = new Map<string, string[]>();
             TopologyUtils.findTopologyOutgoingNodes(integrations).forEach(t => 
{
-                const key = (t.step as any)?.uri + ':' + (t.step as 
any)?.parameters.name;
+                const key = (t.step as any)?.uri + ':' + (t.step as 
any)?.parameters?.name;
                 if (data.has(key)) {
                     const list = data.get(key) || [];
                     list.push(t.routeId);
@@ -253,7 +253,7 @@ export function DslConnections() {
                     {name !== undefined &&
                         <Tooltip content={`Go to ${uri}:${name}`} 
position={"left"}>
                             <Button style={{position: 'absolute', right: 27, 
top: -12, whiteSpace: 'nowrap', zIndex: 300, padding: 0}}
-                                   variant={'link'}
+                                    variant={'link'}
                                     aria-label="Goto"
                                     onClick={_ => 
InfrastructureAPI.onInternalConsumerClick(uri, name, undefined)}>
                                 {name}
@@ -306,7 +306,7 @@ export function DslConnections() {
     function getArrow(pos: DslPosition): JSX.Element[] {
         const list: JSX.Element[] = [];
 
-         if (pos.parent && pos.parent.dslName === 'TryDefinition' && 
pos.position === 0) {
+        if (pos.parent && pos.parent.dslName === 'TryDefinition' && 
pos.position === 0) {
             const parent = steps.get(pos.parent.uuid);
             list.push(...addArrowToList(list, parent, pos, true, false))
         } else if (pos.parent && ['RouteConfigurationDefinition', 
'MulticastDefinition'].includes(pos.parent.dslName)) {
@@ -320,7 +320,7 @@ export function DslConnections() {
             const parent = steps.get(pos.parent.uuid);
             list.push(...addArrowToList(list, parent, pos, true, false))
         } else if (pos.parent && ['WhenDefinition', 'OtherwiseDefinition', 
'CatchDefinition', 'FinallyDefinition', 
'TryDefinition'].includes(pos.parent.dslName)) {
-             if (pos.position === 0) {
+            if (pos.position === 0) {
                 const parent = steps.get(pos.parent.uuid);
                 list.push(...addArrowToList(list, parent, pos, true, false))
             }
@@ -395,45 +395,45 @@ export function DslConnections() {
     }
 
     function getComplexArrow(key: string, rect1: DOMRect, rect2: DOMRect, 
toHeader: boolean) {
-            const startX = rect1.x + rect1.width / 2 - left;
-            const startY = rect1.y + rect1.height - top - 2;
-            const endX = rect2.x + rect2.width / 2 - left;
-            const endTempY = rect2.y - top - 9;
-
-            const gapX = Math.abs(endX - startX);
-            const gapY = Math.abs(endTempY - startY);
-
-            const radX = gapX > 30 ? 20 : gapX/2;
-            const radY = gapY > 30 ? 20 : gapY/2;
-            const endY = rect2.y - top - radY - (toHeader ? 9 : 6);
-
-            const iRadX = startX > endX ? -1 * radX : radX;
-            const iRadY = startY > endY ? -1 * radY : radY;
-
-            const LX1 = startX;
-            const LY1 = endY - radY;
-
-            const Q1_X1 = startX;
-            const Q1_Y1 = LY1 + radY;
-            const Q1_X2 = startX + iRadX;
-            const Q1_Y2 = LY1 + radY;
-
-            const LX2 = startX + (endX - startX) - iRadX;
-            const LY2 = LY1 + radY;
-
-            const Q2_X1 = LX2 + iRadX;
-            const Q2_Y1 = endY;
-            const Q2_X2 = LX2 + iRadX;
-            const Q2_Y2 = endY + radY;
-
-            const path = `M ${startX} ${startY}`
-                + ` L ${LX1} ${LY1} `
-                + ` Q ${Q1_X1} ${Q1_Y1} ${Q1_X2} ${Q1_Y2}`
-                + ` L ${LX2} ${LY2}`
-                + ` Q ${Q2_X1} ${Q2_Y1} ${Q2_X2} ${Q2_Y2}`
-            return (
-                <path key={uuidv4()} name={key} d={path} className="path" 
markerEnd="url(#arrowhead)"/>
-            )
+        const startX = rect1.x + rect1.width / 2 - left;
+        const startY = rect1.y + rect1.height - top - 2;
+        const endX = rect2.x + rect2.width / 2 - left;
+        const endTempY = rect2.y - top - 9;
+
+        const gapX = Math.abs(endX - startX);
+        const gapY = Math.abs(endTempY - startY);
+
+        const radX = gapX > 30 ? 20 : gapX/2;
+        const radY = gapY > 30 ? 20 : gapY/2;
+        const endY = rect2.y - top - radY - (toHeader ? 9 : 6);
+
+        const iRadX = startX > endX ? -1 * radX : radX;
+        const iRadY = startY > endY ? -1 * radY : radY;
+
+        const LX1 = startX;
+        const LY1 = endY - radY;
+
+        const Q1_X1 = startX;
+        const Q1_Y1 = LY1 + radY;
+        const Q1_X2 = startX + iRadX;
+        const Q1_Y2 = LY1 + radY;
+
+        const LX2 = startX + (endX - startX) - iRadX;
+        const LY2 = LY1 + radY;
+
+        const Q2_X1 = LX2 + iRadX;
+        const Q2_Y1 = endY;
+        const Q2_X2 = LX2 + iRadX;
+        const Q2_Y2 = endY + radY;
+
+        const path = `M ${startX} ${startY}`
+            + ` L ${LX1} ${LY1} `
+            + ` Q ${Q1_X1} ${Q1_Y1} ${Q1_X2} ${Q1_Y2}`
+            + ` L ${LX2} ${LY2}`
+            + ` Q ${Q2_X1} ${Q2_Y1} ${Q2_X2} ${Q2_Y2}`
+        return (
+            <path key={uuidv4()} name={key} d={path} className="path" 
markerEnd="url(#arrowhead)"/>
+        )
     }
 
     function getSvg() {
@@ -442,8 +442,8 @@ export function DslConnections() {
         const uniqueArrows = [...new Map(arrows.map(item =>  [(item as 
any).key, item])).values()]
         return (
             <svg key={svgKey}
-                style={{width: width, height: height, position: "absolute", 
left: 0, top: 0}}
-                viewBox={"0 0 " + (width) + " " + (height)}>
+                 style={{width: width, height: height, position: "absolute", 
left: 0, top: 0}}
+                 viewBox={"0 0 " + (width) + " " + (height)}>
                 <defs>
                     <marker id="arrowhead" markerWidth="9" markerHeight="6" 
refX="0" refY="3" orient="auto" className="arrow">
                         <polygon points="0 0, 9 3, 0 6"/>
@@ -464,4 +464,4 @@ export function DslConnections() {
             {getOutgoings().map(p => getOutgoingIcons(p))}
         </div>
     )
-}
+}
\ No newline at end of file
diff --git a/karavan-app/src/main/webui/src/project/beans/BeanWizard.tsx 
b/karavan-app/src/main/webui/src/project/beans/BeanWizard.tsx
index 9c599762..8f13949d 100644
--- a/karavan-app/src/main/webui/src/project/beans/BeanWizard.tsx
+++ b/karavan-app/src/main/webui/src/project/beans/BeanWizard.tsx
@@ -16,9 +16,10 @@
  */
 import React, {useEffect, useMemo, useState} from 'react';
 import {
+    Alert,
     capitalize,
     Flex,
-    Form, FormGroup, FormHelperText, HelperText, HelperTextItem, InputGroup, 
InputGroupItem,
+    Form, FormAlert, FormGroup, FormHelperText, HelperText, HelperTextItem, 
InputGroup, InputGroupItem,
     Modal,
     ModalVariant,
     Radio, Text, TextInput,
@@ -47,21 +48,6 @@ const BEAN_TEMPLATE_SUFFIX_FILENAME = 
"-bean-template.camel.yaml";
 
 export function BeanWizard() {
 
-    const {
-        register,
-        setError,
-        handleSubmit,
-        formState: {errors},
-        reset,
-        setValue
-    } = useForm({
-        mode: "onChange",
-        defaultValues: {filename: ''}
-    });
-
-    const responseToFormErrorFields = new Map<string, string>([
-        ["filename", "filename"]
-    ]);
 
     const [project] = useProjectStore((s) => [s.project], shallow);
     const [setFile, designerTab] = useFileStore((s) => [s.setFile, 
s.designerTab], shallow);
@@ -74,11 +60,7 @@ export function BeanWizard() {
     const [bean, setBean] = useState<RegistryBeanDefinition | undefined>();
     const [filename, setFilename] = useState<string>('');
     const [beanName, setBeanName] = useState<string>('');
-
-    const [globalErrors, registerResponseErrors, resetGlobalErrors] = 
useResponseErrorHandler(
-        responseToFormErrorFields,
-        setError
-    );
+    const [backendError, setBackendError] = React.useState<string>();
 
     function handleOnFormSubmitSuccess(file: ProjectFile) {
         const message = "File successfully created.";
@@ -106,15 +88,19 @@ export function BeanWizard() {
             }
             const fullFileName = filename + CAMEL_YAML_EXT;
             const file = new ProjectFile(fullFileName, project.projectId, 
code, Date.now());
-            // return ProjectService.createFile(file)
-            //     .then(() => handleOnFormSubmitSuccess(file))
-            //     .catch((error) => registerResponseErrors(error));
+            KaravanApi.saveProjectFile(file, (result, file) => {
+                if (result) {
+                    handleOnFormSubmitSuccess(file);
+                } else {
+                    setBackendError(file?.response?.data);
+                }
+            })
         }
     }
 
     useEffect(() => {
         if (showWizard) {
-            reset({filename: ''})
+            setBackendError(undefined);
             setFilename('')
             setTemplateName('');
             setTemplateBeanName('');
@@ -210,7 +196,7 @@ export function BeanWizard() {
                     </Form>
                 </WizardStep>
                 <WizardStep name={"File"} id={"file"}
-                            footer={{nextButtonText: 'Save', onNext: 
handleSubmit(handleFormSubmit)}}
+                            footer={{nextButtonText: 'Save', onNext: event => 
handleFormSubmit()}}
                             isDisabled={(templateName.length == 0 || 
templateBeanName.length == 0) && templateName !== EMPTY_BEAN}
                 >
                     <Form autoComplete="off">
@@ -221,30 +207,24 @@ export function BeanWizard() {
                                                aria-label="filename"
                                                value={filename}
                                                
customIcon={<Text>{CAMEL_YAML_EXT}</Text>}
-                                               validated={!!errors.filename ? 
'error' : 'default'}
-                                               {...register('filename')}
                                                onChange={(e, value) => {
                                                    setFilename(value);
-                                                   
register('filename').onChange(e);
                                                }}
                                     />
                                 </InputGroupItem>
                                 {templateName !== EMPTY_BEAN && 
<InputGroupItem>
-                                    <BeanFilesDropdown 
{...register('filename')} onSelect={(fn, event) => {
-                                        setFilename(fn);
-                                        setValue('filename', fn, 
{shouldValidate: true});
-                                    }}/>
+                                    <BeanFilesDropdown
+                                        onSelect={(fn, event) => {
+                                            setFilename(fn);
+                                        }}
+                                    />
                                 </InputGroupItem>}
                             </InputGroup>
-                            {!!errors.filename && (
-                                <FormHelperText>
-                                    <HelperText>
-                                        <HelperTextItem 
icon={<ExclamationCircleIcon/>} variant={"error"}>
-                                            {errors?.filename?.message}
-                                        </HelperTextItem>
-                                    </HelperText>
-                                </FormHelperText>
-                            )}
+                            {backendError &&
+                                <FormAlert>
+                                    <Alert variant="danger" 
title={backendError} aria-live="polite" isInline/>
+                                </FormAlert>
+                            }
                         </FormGroup>
                     </Form>
                 </WizardStep>
diff --git a/karavan-app/src/main/webui/src/util/useFormUtil.tsx 
b/karavan-app/src/main/webui/src/util/useFormUtil.tsx
index 6e14da3e..7b06e843 100644
--- a/karavan-app/src/main/webui/src/util/useFormUtil.tsx
+++ b/karavan-app/src/main/webui/src/util/useFormUtil.tsx
@@ -1,5 +1,12 @@
 import React from 'react';
-import {Controller, FieldError, UseFormReturn} from "react-hook-form";
+import {
+    Controller,
+    ControllerFieldState, ControllerRenderProps,
+    FieldError,
+    FieldValues,
+    UseFormReturn,
+    UseFormStateReturn
+} from "react-hook-form";
 import {
     Flex,
     FormGroup,
@@ -7,7 +14,7 @@ import {
     FormSelect,
     FormSelectOption,
     HelperText,
-    HelperTextItem, Switch, Text,
+    HelperTextItem, Switch, Text, TextArea,
     TextInput, TextInputGroup, TextInputGroupMain, TextVariants
 } from "@patternfly/react-core";
 import "./form-util.css"
@@ -29,16 +36,48 @@ export function useFormUtil(formContext: 
UseFormReturn<any>) {
     }
 
     function getTextField(fieldName: string, label: string, validate?: 
((value: string, formValues: any) => boolean | string) | Record<string, (value: 
string, formValues: any) => boolean | string>) {
-        const {setValue, getValues, register, formState: {errors}} = 
formContext;
+        const {control, setValue, getValues, formState: {errors}} = 
formContext;
+        return (
+            <FormGroup label={label} fieldId={fieldName} isRequired>
+                <Controller
+                    rules={{required: "Required field", validate: validate}}
+                    control={control}
+                    name={fieldName}
+                    render={() => (
+                        <TextInput className="text-field" type="text" 
id={fieldName}
+                                   value={getValues(fieldName)}
+                                   validated={!!errors[fieldName] ? 'error' : 
'default'}
+                                   onChange={(_, v) => {
+                                       setValue(fieldName, v, {shouldValidate: 
true});
+                                   }}
+                        />
+                    )}
+                />
+                {getHelper((errors as any)[fieldName])}
+            </FormGroup>
+        )
+    }
+
+    function getTextArea(fieldName: string, label: string, validate?: ((value: 
string, formValues: any) => boolean | string) | Record<string, (value: string, 
formValues: any) => boolean | string>) {
+        const {setValue, getValues, control, formState: {errors}} = 
formContext;
         return (
             <FormGroup label={label} fieldId={fieldName} isRequired>
-                <TextInput className="text-field" type="text" id={fieldName}
-                           value={getValues()[fieldName]}
-                           validated={!!errors[fieldName] ? 'error' : 
'default'}
-                           {...register(fieldName, {required: "Required 
field", validate: validate})}
-                           onChange={(e, v) => {
-                               setValue(fieldName, v, {shouldValidate: true});
-                           }}
+                <Controller
+                    rules={{required: "Required field", validate: validate}}
+                    control={control}
+                    name={fieldName}
+                    render={() => (
+                        <TextArea type="text"
+                                  id={fieldName}
+                                  value={getValues(fieldName)}
+                                  validated={!!errors[fieldName] ? 'error' : 
'default'}
+                            // ref={ref}
+                                  onChange={(e, v) => {
+                                      setValue(fieldName, v, {shouldValidate: 
true});
+                                  }}
+                                  autoResize
+                        />
+                    )}
                 />
                 {getHelper((errors as any)[fieldName])}
             </FormGroup>
@@ -53,8 +92,11 @@ export function useFormUtil(formContext: UseFormReturn<any>) 
{
             <FormGroup label={label} fieldId={fieldName} isRequired>
                 <TextInputGroup>
                     <TextInputGroupMain className="text-field-with-prefix" 
type="text" id={fieldName}
-                                        value={getValues()[fieldName]}
-                                        {...register(fieldName, {required: 
(required ? "Required field" : false), validate: validate})}
+                                        value={getValues(fieldName)}
+                                        {...register(fieldName, {
+                                            required: (required ? "Required 
field" : false),
+                                            validate: validate
+                                        })}
                                         onChange={(e, v) => {
                                             setValue(fieldName, v, 
{shouldValidate: true});
                                         }}
@@ -75,8 +117,11 @@ export function useFormUtil(formContext: 
UseFormReturn<any>) {
             <FormGroup label={label} fieldId={fieldName} isRequired>
                 <TextInputGroup className="text-field-with-suffix">
                     <TextInputGroupMain type="text" id={fieldName}
-                                        value={getValues()[fieldName]}
-                                        {...register(fieldName, {required: 
(required ? "Required field" : false), validate: validate})}
+                                        value={getValues(fieldName)}
+                                        {...register(fieldName, {
+                                            required: (required ? "Required 
field" : false),
+                                            validate: validate
+                                        })}
                                         onChange={(e, v) => {
                                             setValue(fieldName, v, 
{shouldValidate: true});
                                         }}
@@ -119,33 +164,33 @@ export function useFormUtil(formContext: 
UseFormReturn<any>) {
         return (
             <FormGroup label={label} fieldId={fieldName} isRequired 
{...register(fieldName)}>
                 <Flex direction={{default: 'column'}}>
-                {options.map((option, index) => {
-                    const key = option[0];
-                    const label = option[0];
-                    return (<Switch
-                        id={key}
-                        label={label}
-                        labelOff={label}
-                        isChecked={watch(fieldName) !== undefined && 
watch(fieldName).includes(key)}
-                        onChange={(e, v) => {
-                            const vals: string[] = watch(fieldName);
-                            const idx = vals.findIndex(x => x === key);
-                            if (idx > -1 && !v) {
-                                vals.splice(idx, 1);
-                                setValue(fieldName, [...vals]);
-                            } else if (idx === -1 && v) {
-                                vals.push(key);
-                                setValue(fieldName, [...vals]);
-                            }
-                        }}
-                        ouiaId={option[0]}
-                    />)
-                })}
+                    {options.map((option, index) => {
+                        const key = option[0];
+                        const label = option[0];
+                        return (<Switch
+                            id={key}
+                            label={label}
+                            labelOff={label}
+                            isChecked={watch(fieldName) !== undefined && 
watch(fieldName).includes(key)}
+                            onChange={(e, v) => {
+                                const vals: string[] = watch(fieldName);
+                                const idx = vals.findIndex(x => x === key);
+                                if (idx > -1 && !v) {
+                                    vals.splice(idx, 1);
+                                    setValue(fieldName, [...vals]);
+                                } else if (idx === -1 && v) {
+                                    vals.push(key);
+                                    setValue(fieldName, [...vals]);
+                                }
+                            }}
+                            ouiaId={option[0]}
+                        />)
+                    })}
                 </Flex>
             </FormGroup>
         )
     }
 
-    return {getFormSelect, getTextField, getSwitches, getTextFieldPrefix, 
getTextFieldSuffix}
+    return {getFormSelect, getTextField, getSwitches, getTextFieldPrefix, 
getTextFieldSuffix, getTextArea}
 }
 

Reply via email to