Skip to content

Commit

Permalink
Temp: Random : {} field
Browse files Browse the repository at this point in the history
  • Loading branch information
lordrip committed Nov 27, 2024
1 parent 2e0b640 commit a503b81
Show file tree
Hide file tree
Showing 6 changed files with 292 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ void testGetProcessors() throws Exception {

var marshal = processorMap.get("org.apache.camel.model.MarshalDefinition");
assertFalse(marshal.has("anyOf"));
assertEquals("dataformat", marshal.get("$comment").asText());
assertTrue(marshal.has("oneOf"));

var toD = processorMap.get("org.apache.camel.model.ToDynamicDefinition");
assertTrue(toD.withObject("/properties").has("uri"));
Expand Down
19 changes: 15 additions & 4 deletions packages/ui/src/components/Form/OneOf/OneOfField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,22 +53,31 @@ export const OneOfField = connectField(({ name: propsName, oneOf, onChange }: On
const onSchemaChanged = useCallback(
(schemaName: string | undefined) => {
if (schemaName === selectedSchemaName) return;
/** Remove existing properties */
const path = propsName === '' ? ROOT_PATH : `${propsName}.${selectedSchemaName}`;
onChange(undefined, path);

if (!isDefined(schemaName)) {
setSelectedSchema(undefined);
setSelectedSchemaName(undefined);
return;
}

/** Remove existing properties */
const currentSchema = oneOfSchemas.find((schema) => schema.name === selectedSchemaName);
if (isDefined(currentSchema) && isDefined(appliedSchema)) {
const cleanModel = Object.keys(currentSchema.schema.properties ?? []).reduce((acc, key) => {
acc[key] = undefined;
return acc;
}, appliedSchema.model);

const path = propsName === '' ? ROOT_PATH : `${propsName}.${selectedSchemaName}`;
onChange(cleanModel, path);
}

const selectedSchema = oneOfSchemas.find((schema) => schema.name === schemaName);
const schemaWithDefinitions = applyDefinitionsToSchema(selectedSchema?.schema, schemaBridge?.schema);
setSelectedSchema(schemaWithDefinitions);
setSelectedSchemaName(schemaName);
},
[onChange, oneOfSchemas, propsName, schemaBridge?.schema, selectedSchemaName],
[appliedSchema, onChange, oneOfSchemas, propsName, schemaBridge?.schema, selectedSchemaName],
);

useEffect(() => {
Expand All @@ -78,6 +87,8 @@ export const OneOfField = connectField(({ name: propsName, oneOf, onChange }: On
const handleOnChangeIndividualProp = useCallback(
(path: string, value: unknown) => {
const updatedPath = propsName === '' ? path : `${propsName}.${path}`;
console.log(path, updatedPath, value);

onChange(value, updatedPath);
},
[onChange, propsName],
Expand Down
39 changes: 24 additions & 15 deletions packages/ui/src/components/Form/OneOf/OneOfSchemaList.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,10 @@
import {
Dropdown,
DropdownItem,
DropdownList,
MenuToggle,
MenuToggleElement,
Text,
TextContent,
TextVariants,
} from '@patternfly/react-core';
import { MenuToggle, MenuToggleElement, Text, TextContent, TextVariants } from '@patternfly/react-core';
import { FunctionComponent, PropsWithChildren, Ref, useCallback, useEffect, useMemo, useState } from 'react';
import { OneOfSchemas } from '../../../utils/get-oneof-schema-list';
import { isDefined } from '../../../utils/is-defined';
import { Typeahead, TypeaheadOptions } from '../../Typeahead';
import { SchemaService } from '../schema.service';
import './OneOfSchemaList.scss';
import { TypeaheadField } from '../customField/TypeaheadField';

interface OneOfComponentProps extends PropsWithChildren {
name: string;
Expand All @@ -32,10 +23,10 @@ export const OneOfSchemaList: FunctionComponent<OneOfComponentProps> = ({
const [isOpen, setIsOpen] = useState(false);

const onSelect = useCallback(
(_event: unknown, value: string | number | undefined) => {
(value: string) => {
setIsOpen(false);
onSchemaChanged(undefined);
onSchemaChanged(value as string);
onSchemaChanged(value);
},
[onSchemaChanged],
);
Expand Down Expand Up @@ -65,6 +56,16 @@ export const OneOfSchemaList: FunctionComponent<OneOfComponentProps> = ({
[isOpen, name, onToggleClick, selectedSchemaName],
);

const options = useMemo(() => {
return oneOfSchemas.map((schemaDef): TypeaheadOptions => {
return {
value: schemaDef.name,
description: schemaDef.description,
isSelected: schemaDef.name === selectedSchemaName,
};
});
}, [oneOfSchemas, selectedSchemaName]);

useEffect(() => {
if (oneOfSchemas.length === 1 && isDefined(oneOfSchemas[0]) && !selectedSchemaName) {
onSchemaChanged(oneOfSchemas[0].name);
Expand All @@ -77,7 +78,7 @@ export const OneOfSchemaList: FunctionComponent<OneOfComponentProps> = ({

return (
<>
<Dropdown
{/* <Dropdown
id={`${name}-oneof-select`}
data-testid={`${name}-oneof-select`}
isOpen={isOpen}
Expand All @@ -101,7 +102,15 @@ export const OneOfSchemaList: FunctionComponent<OneOfComponentProps> = ({
);
})}
</DropdownList>
</Dropdown>
</Dropdown> */}

<Typeahead
value={selectedSchemaName}
options={options}
canCreateNewOption={false}
data-testid={`${name}-oneof-select`}
onChange={onSelect}
/>

{children}
</>
Expand Down
250 changes: 250 additions & 0 deletions packages/ui/src/components/Typeahead/Typeahead.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
import {
Button,
MenuToggle,
MenuToggleElement,
Select,
SelectList,
SelectOption,
SelectOptionProps,
TextInputGroup,
TextInputGroupMain,
TextInputGroupUtilities,
} from '@patternfly/react-core';
import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon';
import React, { FunctionComponent, useEffect, useMemo, useRef, useState } from 'react';
import { IDataTestID } from '../../models';

export interface TypeaheadOptions extends IDataTestID {
value: string;
description?: string;
className?: string;
isDisabled?: boolean;
isSelected?: boolean;
}

interface TypeaheadProps extends IDataTestID {
value?: string;
options: Array<TypeaheadOptions>;
onChange?: (value: string) => void;
canCreateNewOption?: boolean;
}

export const Typeahead: FunctionComponent<TypeaheadProps> = ({
value,
options = [],
onChange,
canCreateNewOption = false,
'data-testid': dataTestId = 'typeahead',
}) => {
const selectedReference = value ?? '';

const listOptions: TypeaheadOptions[] = useMemo(() => {
const localOptions: TypeaheadOptions[] = [];
const hasSelectedReference = options.some((option) => option.value === selectedReference);

if (!hasSelectedReference && selectedReference !== '') {
const newOption: TypeaheadOptions = {
value: selectedReference,
isSelected: true,
};
localOptions.push(newOption);
}

return localOptions.concat(options);
}, [options, selectedReference]);

const [isOpen, setIsOpen] = useState(false);
const [selected, setSelected] = useState<string>('');
const [inputValue, setInputValue] = useState<string>(selectedReference);
const [filterValue, setFilterValue] = useState<string>('');
const [selectOptions, setSelectOptions] = useState<SelectOptionProps[]>(listOptions);
const [focusedItemIndex, setFocusedItemIndex] = useState<number | null>(null);
const [activeItem, setActiveItem] = useState<string | null>(null);
const [onCreation, setOnCreation] = useState<boolean>(false); // Boolean to refresh filter state after new option is created
const textInputRef = useRef<HTMLInputElement>();

useEffect(() => {
let newSelectOptions: SelectOptionProps[] = [...listOptions];
const lowerCaseFilterValue = filterValue.toLowerCase();

// Filter menu items based on the text input value when one exists
if (filterValue) {
newSelectOptions = listOptions.filter(
(menuItem) =>
menuItem.value.toLocaleLowerCase().includes(lowerCaseFilterValue) ||
menuItem.description?.toLocaleLowerCase().includes(lowerCaseFilterValue),
);

// When no options are found after filtering, display creation option
if (canCreateNewOption && !newSelectOptions.length) {
newSelectOptions = [{ isDisabled: false, children: `Insert custom value "${filterValue}"`, value: 'create' }];
}

// Open the menu when the input value changes and the new value is not empty
if (!isOpen) {
setIsOpen(true);
}
}
setSelectOptions(newSelectOptions);
setActiveItem(null);
setFocusedItemIndex(null);
}, [canCreateNewOption, filterValue, isOpen, listOptions]);

const onToggleClick = () => {
setIsOpen(!isOpen);
};

const onSelect = (_event: unknown, value: string | number | undefined) => {
// if (value === 'create') {
// if (!listOptions.some((item) => item.value === filterValue)) {
// listOptions = [...listOptions, { value: filterValue }];
// }
// setSelected(filterValue);
// setOnCreation(!onCreation);
// onChange?.(filterValue);
// setFilterValue('');
// } else {
setInputValue(value as string);
setFilterValue('');
setSelected(value as string);
onChange?.(value as string);
// }
setIsOpen(false);
setFocusedItemIndex(null);
setActiveItem(null);
};

const onTextInputChange = (_event: unknown, value: string) => {
setInputValue(value);
setFilterValue(value);
};
const handleMenuArrowKeys = (key: string) => {
let indexToFocus;

if (isOpen) {
if (key === 'ArrowUp') {
// When no index is set or at the first index, focus to the last, otherwise decrement focus index
if (focusedItemIndex === null || focusedItemIndex === 0) {
indexToFocus = selectOptions.length - 1;
} else {
indexToFocus = focusedItemIndex - 1;
}
}

if (key === 'ArrowDown') {
// When no index is set or at the last index, focus to the first, otherwise increment focus index
if (focusedItemIndex === null || focusedItemIndex === selectOptions.length - 1) {
indexToFocus = 0;
} else {
indexToFocus = focusedItemIndex + 1;
}
}

setFocusedItemIndex(indexToFocus ?? null);
const focusedItem = selectOptions.filter((option) => !option.isDisabled)[indexToFocus!];
setActiveItem(`select-create-typeahead-${focusedItem.value.replace(' ', '-')}`);
}
};

const onInputKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
const enabledMenuItems = selectOptions.filter((option) => !option.isDisabled);
const [firstMenuItem] = enabledMenuItems;
const focusedItem = focusedItemIndex ? enabledMenuItems[focusedItemIndex] : firstMenuItem;

switch (event.key) {
// Select the first available option
case 'Enter':
if (isOpen) {
onSelect(undefined, focusedItem.value as string);
setIsOpen((prevIsOpen) => !prevIsOpen);
setFocusedItemIndex(null);
setActiveItem(null);
}

setIsOpen((prevIsOpen) => !prevIsOpen);
setFocusedItemIndex(null);
setActiveItem(null);

break;
case 'Tab':
case 'Escape':
setIsOpen(false);
setActiveItem(null);
break;
case 'ArrowUp':
case 'ArrowDown':
event.preventDefault();
handleMenuArrowKeys(event.key);
break;
}
};

const toggle = (toggleRef: React.Ref<MenuToggleElement>) => (
<MenuToggle ref={toggleRef} variant="typeahead" onClick={onToggleClick} isExpanded={isOpen} isFullWidth>
<TextInputGroup isPlain>
<TextInputGroupMain
value={inputValue}
onClick={onToggleClick}
onChange={onTextInputChange}
onKeyDown={onInputKeyDown}
id={`${dataTestId}-input`}
data-testid={`${dataTestId}-input`}
innerRef={textInputRef}
isExpanded={isOpen}
autoComplete="off"
placeholder="Type or select an option"
{...(activeItem && { 'aria-activedescendant': activeItem })}
role="combobox"
aria-controls="select-create-typeahead-listbox"
/>

<TextInputGroupUtilities>
{!!inputValue && (
<Button
variant="plain"
onClick={() => {
setSelected('');
setInputValue('');
setFilterValue('');
onChange?.('');
textInputRef?.current?.focus();
}}
aria-label="Clear input value"
>
<TimesIcon aria-hidden />
</Button>
)}
</TextInputGroupUtilities>
</TextInputGroup>
</MenuToggle>
);

return (
<Select
data-testdid={`${dataTestId}-select`}
isOpen={isOpen}
selected={selected}
onSelect={onSelect}
onOpenChange={() => {
setIsOpen(false);
}}
toggle={toggle}
>
<SelectList id="select-create-typeahead-listbox">
{selectOptions.map((option, index) => (
<SelectOption
key={option.value}
itemId={option.value}
data-testid={`select-typeahead-${option.value}`}
description={option.description}
isFocused={focusedItemIndex === index}
className={option.className}
onClick={() => setSelected(option.value)}
>
{option.value}
</SelectOption>
))}
</SelectList>
</Select>
);
};
1 change: 1 addition & 0 deletions packages/ui/src/components/Typeahead/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './Typeahead';
1 change: 1 addition & 0 deletions packages/ui/src/hooks/applied-schema.hook.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export const useAppliedSchema = (fieldName: string, oneOfSchemas: OneOfSchemas[]
}

const foundSchema = oneOfSchemas[index];
console.log(JSON.stringify(form.model, null, 2));

return {
index,
Expand Down

0 comments on commit a503b81

Please sign in to comment.