Skip to content

Commit

Permalink
feat(web): add float field (#1290)
Browse files Browse the repository at this point in the history
* impl

* rename

* fix: type bug

* i18n

* fix: validate

* add: e2e test

* Merge branch 'main' of https://github.com/reearth/reearth-cms into fix-web/float-field

* rename
  • Loading branch information
caichi-t authored Nov 14, 2024
1 parent c580262 commit b34b98e
Show file tree
Hide file tree
Showing 25 changed files with 225 additions and 121 deletions.
123 changes: 123 additions & 0 deletions web/e2e/project/item/fields/float.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { closeNotification } from "@reearth-cms/e2e/common/notification";
import { createModel } from "@reearth-cms/e2e/project/utils/model";
import { createProject, deleteProject } from "@reearth-cms/e2e/project/utils/project";
import { expect, test } from "@reearth-cms/e2e/utils";

test.beforeEach(async ({ reearth, page }) => {
await reearth.goto("/", { waitUntil: "domcontentloaded" });
await createProject(page);
await createModel(page);
});

test.afterEach(async ({ page }) => {
await deleteProject(page);
});

test("Float field creating and updating has succeeded", async ({ page }) => {
await page.locator("li").filter({ hasText: "Float" }).locator("div").first().click();
await page.getByLabel("Display name").click();
await page.getByLabel("Display name").fill("float1");
await page.getByLabel("Settings").locator("#key").click();
await page.getByLabel("Settings").locator("#key").fill("float1");
await page.getByLabel("Settings").locator("#description").click();
await page.getByLabel("Settings").locator("#description").fill("float1 description");

await page.getByRole("button", { name: "OK" }).click();
await closeNotification(page);

await expect(page.getByLabel("Fields").getByRole("paragraph")).toContainText("float1#float1");
await page.getByText("Content").click();
await page.getByRole("button", { name: "plus New Item" }).click();
await expect(page.locator("label")).toContainText("float1");
await expect(page.getByRole("main")).toContainText("float1 description");
await page.getByLabel("float1").click();
await page.getByLabel("float1").fill("1.1");
await page.getByRole("button", { name: "Save" }).click();
await closeNotification(page);
await page.getByLabel("Back").click();
await expect(page.getByRole("cell", { name: "1.1", exact: true })).toBeVisible();

await page.getByRole("cell").getByLabel("edit").locator("svg").click();
await expect(page.getByLabel("float1")).toHaveValue("1.1");
await page.getByLabel("float1").click();
await page.getByLabel("float1").fill("2.2");
await page.getByRole("button", { name: "Save" }).click();
await closeNotification(page);
await page.getByLabel("Back").click();
await expect(page.getByRole("cell", { name: "2.2", exact: true })).toBeVisible();
});

test("Float field editing has succeeded", async ({ page }) => {
await page.locator("li").filter({ hasText: "Float" }).locator("div").first().click();
await page.getByLabel("Display name").click();
await page.getByLabel("Display name").fill("float1");
await page.getByLabel("Settings").locator("#key").click();
await page.getByLabel("Settings").locator("#key").fill("float1");
await page.getByLabel("Settings").locator("#description").click();
await page.getByLabel("Settings").locator("#description").fill("float1 description");
await page.getByRole("tab", { name: "Default value" }).click();
await page.getByLabel("Set default value").click();
await page.getByLabel("Set default value").fill("1.1");
await page.getByRole("button", { name: "OK" }).click();
await closeNotification(page);

await page.getByText("Content").click();
await expect(page.locator("thead")).toContainText("float1");
await page.getByRole("button", { name: "plus New Item" }).click();
await page.getByRole("button", { name: "Save" }).click();
await closeNotification(page);
await page.getByLabel("Back").click();
await expect(page.getByRole("cell", { name: "1.1", exact: true })).toBeVisible();

await page.getByText("Schema").click();
await page.getByRole("img", { name: "ellipsis" }).locator("svg").click();
await page.getByRole("tab", { name: "Settings" }).click();
await page.getByLabel("Display name").click();
await page.getByLabel("Display name").fill("new float1");
await page.getByLabel("Field Key").click();
await page.getByLabel("Field Key").fill("new-float1");
await page.getByLabel("Description(optional)").click();
await page.getByLabel("Description(optional)").fill("new float1 description");
await page.getByLabel("Support multiple values").check();
await page.getByLabel("Use as title").check();
await page.getByRole("tab", { name: "Validation" }).click();
await page.getByLabel("Set minimum value").click();
await page.getByLabel("Set minimum value").fill("10.1");
await page.getByLabel("Set maximum value").click();
await page.getByLabel("Set maximum value").fill("2.1");
await expect(page.getByRole("button", { name: "OK" })).toBeDisabled();
await page.getByLabel("Set minimum value").click();
await page.getByLabel("Set minimum value").fill("2.1");
await page.getByLabel("Set maximum value").click();
await page.getByLabel("Set maximum value").fill("10.1");
await page.getByLabel("Make field required").check();
await page.getByLabel("Set field as unique").check();
await page.getByRole("tab", { name: "Default value" }).click();
await expect(page.getByLabel("Set default value")).toBeVisible();
await expect(page.getByLabel("Set default value")).toHaveValue("1.1");
await expect(page.getByRole("button", { name: "OK" })).toBeDisabled();
await page.getByLabel("Set default value").click();
await page.getByLabel("Set default value").fill("11.1");
await expect(page.getByRole("button", { name: "OK" })).toBeDisabled();
await page.getByLabel("Set default value").click();
await page.getByLabel("Set default value").fill("2.2");
await page.getByRole("button", { name: "plus New" }).click();
await page.locator("#defaultValue").nth(1).click();
await page.locator("#defaultValue").nth(1).fill("3.3");
await page.getByRole("button", { name: "OK" }).click();
await closeNotification(page);

await expect(page.getByText("new float1 *#new-float1(unique)")).toBeVisible();
await page.getByText("Content").click();
await expect(page.locator("thead")).toContainText("new float1");
await expect(page.getByRole("cell", { name: "1.1", exact: true })).toBeVisible();
await page.getByRole("button", { name: "plus New Item" }).click();
await expect(page.getByText("new float1(unique)Title")).toBeVisible();
await expect(page.getByRole("spinbutton").nth(0)).toHaveValue("2.2");
await expect(page.getByRole("spinbutton").nth(1)).toHaveValue("3.3");
await page.getByRole("button", { name: "Save" }).click();
await closeNotification(page);
await page.getByLabel("Back").click();
await page.getByRole("button", { name: "x2" }).click();
await expect(page.getByRole("tooltip")).toContainText("new float12.23.3");
});
3 changes: 3 additions & 0 deletions web/src/components/atoms/Icon/Icons/infinity.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions web/src/components/atoms/Icon/icons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ import Date from "./Icons/date.svg";
import Dot from "./Icons/dot.svg";
import EditorCopy from "./Icons/editorCopy.svg";
import Group from "./Icons/group.svg";
import InfinityIcon from "./Icons/infinity.svg";
import Key from "./Icons/key.svg";
import LineSegments from "./Icons/lineSegments.svg";
import LineString from "./Icons/lineString.svg";
Expand Down Expand Up @@ -129,6 +130,7 @@ export default {
listBullets: ListBullets,
arrowUpRight: ArrowUpRight,
arrowUpRightSlash: ArrowUpRightSlash,
infinity: InfinityIcon,
numberNine: NumberNine,
link: Link,
linkSolid: LinkSolid,
Expand Down
8 changes: 4 additions & 4 deletions web/src/components/atoms/InputNumber/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@ const InputNumber: <T extends string | number>(
props: React.PropsWithChildren<InputNumberProps<T>> & React.RefAttributes<HTMLInputElement>,
) => React.ReactElement = ({ value, ...props }) => {
const status = useMemo(() => {
if (value) {
if (props.max && Number(value) > Number(props.max)) {
if (typeof value === "number") {
if (typeof props.max === "number" && value > props.max) {
return "error";
} else if (props.min && Number(value) < Number(props.min)) {
} else if (typeof props.min === "number" && value < props.min) {
return "error";
}
}
}, [props.max, props.min, value]);

return <AntDInputNumber value={value} status={status} {...props} />;
return <AntDInputNumber value={value} status={status} style={{ width: "100%" }} {...props} />;
};

export default InputNumber;
34 changes: 7 additions & 27 deletions web/src/components/molecules/Common/Form/GroupItem/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@ import {
AssetField,
ReferenceField,
} from "@reearth-cms/components/molecules/Content/Form/fields/ComplexFieldComponents";
import { DefaultField } from "@reearth-cms/components/molecules/Content/Form/fields/FieldComponents";
import { FIELD_TYPE_COMPONENT_MAP } from "@reearth-cms/components/molecules/Content/Form/fields/FieldTypesMap";
import { FormItem, ItemAsset } from "@reearth-cms/components/molecules/Content/types";
import { Field, Group } from "@reearth-cms/components/molecules/Schema/types";
import { Field, Group, GroupField } from "@reearth-cms/components/molecules/Schema/types";

type Props = {
value?: string;
Expand Down Expand Up @@ -117,7 +116,7 @@ const GroupItem: React.FC<Props> = ({
}) => {
const { Panel } = Collapse;

const [fields, setFields] = useState<Field[]>();
const [fields, setFields] = useState<GroupField[]>();

useEffect(() => {
const handleFieldsSet = async (id: string) => {
Expand Down Expand Up @@ -176,7 +175,7 @@ const GroupItem: React.FC<Props> = ({
)
}>
<div>
{fields?.map((field: Field) => {
{fields?.map(field => {
if (field.type === "Asset") {
return (
<StyledFormItemWrapper key={field.id}>
Expand Down Expand Up @@ -232,31 +231,12 @@ const GroupItem: React.FC<Props> = ({
/>
</StyledFormItemWrapper>
);
} else if (field.type === "GeometryObject" || field.type === "GeometryEditor") {
const FieldComponent = FIELD_TYPE_COMPONENT_MAP[field.type];

return (
<StyledFormItemWrapper key={field.id} isFullWidth>
<FieldComponent field={field} itemGroupId={itemGroupId} disabled={disabled} />
</StyledFormItemWrapper>
);
} else {
const FieldComponent =
FIELD_TYPE_COMPONENT_MAP[
field.type as
| "Select"
| "Date"
| "Tag"
| "Bool"
| "Checkbox"
| "URL"
| "TextArea"
| "MarkdownText"
| "Integer"
] || DefaultField;

const FieldComponent = FIELD_TYPE_COMPONENT_MAP[field.type];
return (
<StyledFormItemWrapper key={field.id}>
<StyledFormItemWrapper
key={field.id}
isFullWidth={field.type === "GeometryObject" || field.type === "GeometryEditor"}>
<FieldComponent field={field} itemGroupId={itemGroupId} disabled={disabled} />
</StyledFormItemWrapper>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,16 @@ type DefaultFieldProps = {
disabled: boolean;
};

const IntegerField: React.FC<DefaultFieldProps> = ({ field, itemGroupId, disabled }) => {
const NumberField: React.FC<DefaultFieldProps> = ({ field, itemGroupId, disabled }) => {
const t = useT();
const min = useMemo(() => field.typeProperty?.min, [field.typeProperty?.min]);
const max = useMemo(() => field.typeProperty?.max, [field.typeProperty?.max]);
const min = useMemo(
() => field?.typeProperty?.min ?? field?.typeProperty?.numberMin,
[field?.typeProperty?.min, field?.typeProperty?.numberMin],
);
const max = useMemo(
() => field?.typeProperty?.max ?? field?.typeProperty?.numberMax,
[field?.typeProperty?.max, field?.typeProperty?.numberMax],
);
const validate = useCallback(
(value: unknown) => {
if (typeof value === "number") {
Expand Down Expand Up @@ -66,4 +72,4 @@ const IntegerField: React.FC<DefaultFieldProps> = ({ field, itemGroupId, disable
);
};

export default IntegerField;
export default NumberField;
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,4 @@ export { default as CheckboxField } from "./CheckboxField";
export { default as URLField } from "./URLField";
export { default as DateField } from "./DateField";
export { default as BoolField } from "./BoolField";
export { default as IntegerField } from "./IntegerField";
export { default as MarkdownField } from "./MarkdownField";
export { default as SelectField } from "./SelectField";
export { default as TextAreaField } from "./TextareaField";
export { default as DefaultField } from "./DefaultField";
15 changes: 12 additions & 3 deletions web/src/components/molecules/Content/Form/fields/FieldTypesMap.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
import { TagField, DateField, BoolField, CheckboxField, URLField } from "./FieldComponents";
import {
DefaultField,
TagField,
DateField,
BoolField,
CheckboxField,
URLField,
} from "./FieldComponents";
import GeometryField from "./FieldComponents/GeometryField";
import IntegerField from "./FieldComponents/IntegerField";
import MarkdownField from "./FieldComponents/MarkdownField";
import NumberField from "./FieldComponents/NumberField";
import SelectField from "./FieldComponents/SelectField";
import TextareaField from "./FieldComponents/TextareaField";

export const FIELD_TYPE_COMPONENT_MAP = {
Text: DefaultField,
Tag: TagField,
Date: DateField,
Bool: BoolField,
Checkbox: CheckboxField,
URL: URLField,
TextArea: TextareaField,
MarkdownText: MarkdownField,
Integer: IntegerField,
Integer: NumberField,
Number: NumberField,
Select: SelectField,
GeometryObject: GeometryField,
GeometryEditor: GeometryField,
Expand Down
33 changes: 5 additions & 28 deletions web/src/components/molecules/Content/Form/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ import { useT } from "@reearth-cms/i18n";
import { transformDayjsToString } from "@reearth-cms/utils/format";

import { AssetField, GroupField, ReferenceField } from "./fields/ComplexFieldComponents";
import { DefaultField } from "./fields/FieldComponents";
import { FIELD_TYPE_COMPONENT_MAP } from "./fields/FieldTypesMap";

type Props = {
Expand Down Expand Up @@ -643,31 +642,12 @@ const ContentForm: React.FC<Props> = ({
/>
</StyledFormItemWrapper>
);
} else if (field.type === "GeometryObject" || field.type === "GeometryEditor") {
const FieldComponent = FIELD_TYPE_COMPONENT_MAP[field.type];

return (
<StyledFormItemWrapper key={field.id} isFullWidth>
<FieldComponent field={field} disabled={fieldDisabled} />
</StyledFormItemWrapper>
);
} else {
const FieldComponent =
FIELD_TYPE_COMPONENT_MAP[
field.type as
| "Select"
| "Date"
| "Tag"
| "Bool"
| "Checkbox"
| "URL"
| "TextArea"
| "MarkdownText"
| "Integer"
] || DefaultField;

const FieldComponent = FIELD_TYPE_COMPONENT_MAP[field.type];
return (
<StyledFormItemWrapper key={field.id}>
<StyledFormItemWrapper
key={field.id}
isFullWidth={field.type === "GeometryObject" || field.type === "GeometryEditor"}>
<FieldComponent field={field} disabled={fieldDisabled} />
</StyledFormItemWrapper>
);
Expand All @@ -679,10 +659,7 @@ const ContentForm: React.FC<Props> = ({
<Form form={metaForm} layout="vertical" initialValues={initialMetaFormValues}>
<ContentSidebarWrapper item={item} onNavigateToRequest={onNavigateToRequest} />
{model?.metadataSchema?.fields?.map(field => {
const FieldComponent =
FIELD_TYPE_COMPONENT_MAP[
field.type as "Tag" | "Date" | "Bool" | "Checkbox" | "URL"
] || DefaultField;
const FieldComponent = FIELD_TYPE_COMPONENT_MAP[field.type];
return (
<MetaFormItemWrapper key={field.id}>
<FieldComponent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ export default (
);
break;
case "Integer":
// case "Float":
case "Number":
result.push(
{ operatorType: "basic", value: BasicOperator.Equals, label: t("is") },
{ operatorType: "basic", value: BasicOperator.NotEquals, label: t("is not") },
Expand Down Expand Up @@ -312,7 +312,7 @@ export default (
if (typeof value !== "boolean") {
value = value === "true";
}
} else if (filter.type === "Integer" /*|| filter.type === "Float"*/) {
} else if (filter.type === "Integer" || filter.type === "Number") {
value = Number(value);
} else if (filter.type === "Date") {
value = value ? new Date(value) : new Date();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,8 @@ const DropdownRender: React.FC<Props> = ({
</Option>
))}
</Select>
) : filter.type === "Integer" /*|| filter.type === "Float"*/ ? (
<InputNumber
onChange={onNumberChange}
stringMode
style={{ width: "100%" }}
placeholder="Enter the value"
/>
) : filter.type === "Integer" || filter.type === "Number" ? (
<InputNumber onChange={onNumberChange} stringMode placeholder="Enter the value" />
) : filter.type === "Date" ? (
<DatePicker
onChange={onDateChange}
Expand Down
Loading

0 comments on commit b34b98e

Please sign in to comment.