Skip to content

Commit

Permalink
Merge pull request #55 from axonivy/add-combobox-to-addDataClass
Browse files Browse the repository at this point in the history
XIVY-15045 Add Type-Combobox to Add DataClass-Dialog
  • Loading branch information
ivy-edp authored Oct 16, 2024
2 parents d08a435 + 218955b commit a78ecda
Show file tree
Hide file tree
Showing 9 changed files with 148 additions and 22 deletions.
2 changes: 1 addition & 1 deletion integrations/standalone/tests/mock/master.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ test.describe('add field', async () => {
test('empty', async () => {
await editor.add.open.locator.click();
await expect(editor.add.typeMessage.locator).toBeHidden();
await editor.add.type.locator.clear();
await editor.add.type.clear();
await editor.add.typeMessage.expectToHaveErrorMessage('Type cannot be empty.');
});
});
Expand Down
5 changes: 3 additions & 2 deletions integrations/standalone/tests/pageobjects/AddFieldDialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ import { expect, type Locator, type Page } from '@playwright/test';
import { Button } from './abstract/Button';
import { FieldMessage } from './abstract/FieldMessage';
import { TextArea } from './abstract/TextArea';
import { Combobox } from './abstract/Combobox';

export class AddFieldDialog {
readonly locator: Locator;
readonly open: Button;
readonly name: TextArea;
readonly nameMessage: FieldMessage;
readonly type: TextArea;
readonly type: Combobox;
readonly typeMessage: FieldMessage;
readonly create: Button;

Expand All @@ -17,7 +18,7 @@ export class AddFieldDialog {
this.open = new Button(page.locator('.master-content'), { name: 'Add field' });
this.name = new TextArea(this.locator, { label: 'Name' });
this.nameMessage = new FieldMessage(this.locator, { label: 'Name' });
this.type = new TextArea(this.locator, { label: 'Type' });
this.type = new Combobox(this.locator, { label: 'Type' });
this.typeMessage = new FieldMessage(this.locator, { label: 'Type' });
this.create = new Button(this.locator, { name: 'Create field' });
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export class DataClassEditor {
async addField(name: string, type: string) {
await this.add.open.locator.click();
await this.add.name.locator.fill(name);
await this.add.type.locator.fill(type);
await this.add.type.fill(type);
await this.add.create.locator.click();
}

Expand Down
27 changes: 27 additions & 0 deletions integrations/standalone/tests/pageobjects/abstract/Combobox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { expect, type Locator } from '@playwright/test';

export class Combobox {
readonly locator: Locator;

constructor(parentLocator: Locator, options?: { label?: string; nth?: number }) {
if (options?.label) {
this.locator = parentLocator.getByRole('combobox', { name: options.label, exact: true });
} else {
this.locator = parentLocator.getByRole('combobox').nth(options?.nth ?? 0);
}
}

async fill(value: string) {
await this.locator.fill(value);
await this.locator.blur();
}

async clear() {
await this.locator.fill('');
await this.locator.blur();
}

async expectToHavePlaceholder(palceholder: string) {
await expect(this.locator).toHaveAttribute('placeholder', palceholder);
}
}
24 changes: 12 additions & 12 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions packages/dataclass-editor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
"types": "lib/index.d.ts",
"main": "lib/editor.js",
"dependencies": {
"@axonivy/jsonrpc": "~12.0.0-next.340",
"@axonivy/ui-components": "~12.0.0-next.340",
"@axonivy/ui-icons": "~12.0.0-next.340",
"@axonivy/jsonrpc": "~12.0.0-next.342",
"@axonivy/ui-components": "~12.0.0-next.342",
"@axonivy/ui-icons": "~12.0.0-next.342",
"@tanstack/react-query": "5.32.1",
"@tanstack/react-query-devtools": "5.32.1",
"react": "^18.2.0"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.combobox-with-type-browser .ui-combobox {
flex: 1;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import {
BasicCheckbox,
BasicField,
Button,
Combobox,
Dialog,
DialogContent,
DialogTrigger,
Flex,
IvyIcon,
type BrowserNode,
type MessageData
} from '@axonivy/ui-components';
import { IvyIcons } from '@axonivy/ui-icons';
import { useMemo, useState } from 'react';
import { Browser } from './browser/Browser';
import { useAppContext } from '../../context/AppContext';
import { useMeta } from '../../context/useMeta';
import { typeData } from '../../data/type-data';
import './ComboboxFieldWithTypeBrowser.css';
import type { DataclassType } from '../../protocol/types';

export type InputFieldProps = {
value: string;
onChange: (value: string) => void;
message?: MessageData;
};

export const ComboboxFieldWithTypeBrowser = ({ value, onChange, message }: InputFieldProps) => {
const [open, setOpen] = useState(false);
const { context } = useAppContext();
const [typeAsList, setTypeAsList] = useState<boolean>(false);
const dataClasses = useMeta('meta/scripting/dataClasses', context, []).data;
const ivyTypes = useMeta('meta/scripting/ivyTypes', undefined, []).data;
const types = useMemo(() => typeData(dataClasses, ivyTypes, [], [], false), [dataClasses, ivyTypes]);

const typeAsListChange = (change: boolean) => {
setTypeAsList(!typeAsList);
if (change && !(value.startsWith('List<') && value.endsWith('>'))) {
onChange(`List<${value}>`);
} else if (!change && value.startsWith('List<') && value.endsWith('>')) {
const removeListFromValue = value.slice(5, -1);
onChange(removeListFromValue);
}
};

const ExtendedComboboxItem = ({ icon, value, info }: BrowserNode<DataclassType>) => (
<Flex gap={1} alignItems='center'>
<IvyIcon icon={icon} />
<span>{value}</span>
<span style={{ color: 'var(--N700)' }}>{info}</span>
</Flex>
);

return (
<Dialog open={open} onOpenChange={setOpen}>
<BasicField label='Type' message={message} aria-label='Type'>
<Flex direction='row' gap={2} alignItems='center' className='combobox-with-type-browser'>
<Combobox
onChange={value => {
const foundDataclassType = dataClasses.find(type => type.name === value);
if (foundDataclassType) {
onChange(foundDataclassType.fullQualifiedName);
} else {
onChange(value);
}
}}
value={value}
options={types}
itemRender={option => <ExtendedComboboxItem {...option} />}
/>
<DialogTrigger asChild>
<Button icon={IvyIcons.ListSearch} aria-label='Browser' />
</DialogTrigger>
</Flex>
<BasicCheckbox label='Type as List' checked={typeAsList} onCheckedChange={change => typeAsListChange(change as boolean)} />
</BasicField>
<DialogContent style={{ height: '80vh' }}>
<Browser
onChange={value => {
onChange(value);
if (value.startsWith('List<') && value.endsWith('>')) {
setTypeAsList(true);
}
}}
close={() => setOpen(false)}
/>
</DialogContent>
</Dialog>
);
};
10 changes: 7 additions & 3 deletions packages/dataclass-editor/src/master/AddFieldDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { useMemo, useRef, useState } from 'react';
import { useAppContext } from '../context/AppContext';
import type { DataClass, DataClassField } from '../data/dataclass';
import { isEntity } from '../data/dataclass-utils';
import { InputFieldWithTypeBrowser } from '../detail/field/InputFieldWithTypeBrowser';
import { ComboboxFieldWithTypeBrowser } from '../detail/field/ComboboxFieldWithTypeBrowser';

export const validateFieldName = (name: string, dataClass: DataClass): MessageData => {
if (name.trim() === '') {
Expand Down Expand Up @@ -118,13 +118,17 @@ export const AddFieldDialog = ({ table }: AddFieldDialogProps) => {
<DialogTitle>New Attribute</DialogTitle>
</DialogHeader>
<DialogDescription>Choose the name and type of the attribute you want to add.</DialogDescription>
<form onSubmit={event => event.preventDefault()}>
<form
onSubmit={event => {
event.preventDefault();
}}
>
<Flex direction='column' gap={2}>
<Flex direction='column' gap={2}>
<BasicField label='Name' message={nameValidationMessage} aria-label='Name'>
<Input value={name} onChange={event => setName(event.target.value)} />
</BasicField>
<InputFieldWithTypeBrowser value={type} message={typeValidationMessage} onChange={setType} />
<ComboboxFieldWithTypeBrowser value={type} message={typeValidationMessage} onChange={setType} />
</Flex>
<DialogFooter>
<DialogClose asChild>
Expand Down

0 comments on commit a78ecda

Please sign in to comment.