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
commit 4c48d656c082abc94d1edb8c2c812b28b4dca9a8 Author: Marat Gubaidullin <ma...@talismancloud.io> AuthorDate: Mon Apr 1 20:23:24 2024 -0400 Improvements --- .../org/apache/camel/karavan/code/CodeService.java | 46 +++++++- .../webui/src/util/ModalDeleteConfirmation.tsx | 35 ++++++ karavan-app/src/main/webui/src/util/form-util.css | 12 ++ .../src/main/webui/src/util/useFormUtil.tsx | 130 +++++++++++++++++++++ 4 files changed, 222 insertions(+), 1 deletion(-) diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/code/CodeService.java b/karavan-app/src/main/java/org/apache/camel/karavan/code/CodeService.java index 03a646d6..b4290c01 100644 --- a/karavan-app/src/main/java/org/apache/camel/karavan/code/CodeService.java +++ b/karavan-app/src/main/java/org/apache/camel/karavan/code/CodeService.java @@ -48,6 +48,13 @@ import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.constructor.SafeConstructor; import java.io.*; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.time.Instant; import java.util.*; import java.util.stream.Collectors; @@ -169,7 +176,7 @@ public class CodeService { public String getTemplateText(String fileName) { try { List<ProjectFile> files = karavanCacheService.getProjectFiles(Project.Type.templates.name()); - // replaceAll("\r\n", "\n")) has been add to eliminate the impact of editing the template files from windows machine. + // replaceAll("\r\n", "\n")) has been add to eliminate the impact of editing the template files from windows machine. return files.stream().filter(f -> f.getName().equalsIgnoreCase(fileName)) .map(file-> file.getCode().replaceAll("\r\n", "\n")).findFirst().orElse(null); } catch (Exception e) { @@ -393,6 +400,43 @@ public class CodeService { return vertx.fileSystem().readFileBlocking(fileName).toString(); } + public List<String> listResources(String resourceFolder, boolean onlyFolders) { + List<String> filePaths = new ArrayList<>(); + try { + URI uri = CodeService.class.getResource(resourceFolder).toURI(); + Path myPath; + FileSystem fileSystem = null; + if (uri.getScheme().equals("jar")) { + fileSystem = FileSystems.newFileSystem(uri, Collections.emptyMap()); + myPath = fileSystem.getPath(resourceFolder); + } else { + myPath = Paths.get(uri); + } + + if (onlyFolders) { + // Use Files.walk to list and collect directory paths + filePaths = Files.walk(myPath, 10) + .filter(Files::isDirectory) + .map(path -> path.getFileName().toString()) + .collect(Collectors.toList()); + } else { + // Use Files.walk to list and collect file paths + filePaths = Files.walk(myPath, 1) + .filter(Files::isRegularFile) + .map(path -> path.getFileName().toString()) + .collect(Collectors.toList()); + } + + // Close the file system if opened + if (fileSystem != null) { + fileSystem.close(); + } + } catch (URISyntaxException | IOException e) { + e.printStackTrace(); + } + return filePaths; + } + public String getFileString(String fullName) { return vertx.fileSystem().readFileBlocking(fullName).toString(); } diff --git a/karavan-app/src/main/webui/src/util/ModalDeleteConfirmation.tsx b/karavan-app/src/main/webui/src/util/ModalDeleteConfirmation.tsx new file mode 100644 index 00000000..ec4d0079 --- /dev/null +++ b/karavan-app/src/main/webui/src/util/ModalDeleteConfirmation.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { + Button, + Modal, + ModalVariant +} from '@patternfly/react-core'; +import '../designer/karavan.css'; + +interface Props { + content: string | React.JSX.Element + isOpen: boolean + onClose: () => void + onConfirm: () => void + title?: string +} + +export function ModalDeleteConfirmation(props: Props) { + + const {title, isOpen, onClose, onConfirm, content} = props; + return ( + <Modal + title={title ? title : 'Confirmation'} + variant={ModalVariant.small} + isOpen={isOpen} + onClose={() => onClose()} + actions={[ + <Button key="confirm" variant="danger" onClick={e => onConfirm()}>Confirm</Button>, + <Button key="cancel" variant="link" + onClick={e => onClose()}>Cancel</Button> + ]} + onEscapePress={e => onClose()}> + {content} + </Modal> + ) +} \ No newline at end of file diff --git a/karavan-app/src/main/webui/src/util/form-util.css b/karavan-app/src/main/webui/src/util/form-util.css new file mode 100644 index 00000000..47c4819f --- /dev/null +++ b/karavan-app/src/main/webui/src/util/form-util.css @@ -0,0 +1,12 @@ +.pf-v5-c-modal-box .text-field-with-prefix { + gap: 0; +} + +.pf-v5-c-modal-box .text-field-with-prefix .text-field-prefix { + margin-top: auto; + margin-bottom: auto; +} + +.pf-v5-c-modal-box .text-field-with-prefix .pf-v5-c-text-input-group__text-input { + padding-left: 0; +} \ No newline at end of file diff --git a/karavan-app/src/main/webui/src/util/useFormUtil.tsx b/karavan-app/src/main/webui/src/util/useFormUtil.tsx new file mode 100644 index 00000000..fb7903d8 --- /dev/null +++ b/karavan-app/src/main/webui/src/util/useFormUtil.tsx @@ -0,0 +1,130 @@ +import React from 'react'; +import {FieldError, UseFormReturn} from "react-hook-form"; +import { + Flex, + FormGroup, + FormHelperText, + FormSelect, + FormSelectOption, + HelperText, + HelperTextItem, Switch, Text, + TextInput, TextInputGroup, TextInputGroupMain, TextVariants +} from "@patternfly/react-core"; +import "./form-util.css" + +export function useFormUtil(formContext: UseFormReturn<any>) { + + function getHelper(error: FieldError | undefined) { + if (error) { + return ( + <FormHelperText> + <HelperText> + <HelperTextItem variant={'error'}> + {error.message} + </HelperTextItem> + </HelperText> + </FormHelperText> + ) + } + } + + 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; + 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}); + }} + /> + {getHelper((errors as any)[fieldName])} + </FormGroup> + ) + } + + function getTextFieldPrefix(fieldName: string, label: string, prefix: string, + required: boolean, + validate?: ((value: string, formValues: any) => boolean | string) | Record<string, (value: string, formValues: any) => boolean | string>) { + const {setValue, getValues, register, formState: {errors}} = formContext; + return ( + <FormGroup label={label} fieldId={fieldName} isRequired> + <TextInputGroup> + <TextInputGroupMain className="text-field-with-prefix" type="text" id={fieldName} + value={getValues()[fieldName]} + // validated={!!errors[fieldName] ? 'error' : 'default'} + {...register(fieldName, {required: (required ? "Required field" : false), validate: validate})} + onChange={(e, v) => { + setValue(fieldName, v, {shouldValidate: true}); + }} + > + <Text className='text-field-prefix' component={TextVariants.p}>{prefix}</Text> + </TextInputGroupMain> + </TextInputGroup> + {getHelper((errors as any)[fieldName])} + </FormGroup> + ) + } + + function getFormSelect(fieldName: string, label: string, options: [string, string][]) { + const {register, watch, setValue, formState: {errors}} = formContext; + return ( + <FormGroup label={label} fieldId={fieldName} isRequired> + <FormSelect + ouiaId={fieldName} + validated={!!errors[fieldName] ? 'error' : 'default'} + value={watch(fieldName)} + {...register(fieldName, {required: "Required field"})} + onChange={(e, v) => { + setValue(fieldName, v, {shouldValidate: true}); + }} + name={fieldName} + + > + <FormSelectOption key='placeholder' value={undefined} label='Select one' isDisabled/> + {options.map((option, index) => ( + <FormSelectOption key={index} value={option[0]} label={option[1]}/> + ))} + </FormSelect> + {getHelper((errors as any)[fieldName])} + </FormGroup> + ) + } + + function getSwitches(fieldName: string, label: string, options: [string, string][]) { + const {watch, register, getValues, setValue, formState: {errors}} = formContext; + 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]} + />) + })} + </Flex> + </FormGroup> + ) + } + + return {getFormSelect, getTextField, getSwitches, getTextFieldPrefix} +} +