Skip to content

Commit

Permalink
chore(web): refactor beta properties (#786)
Browse files Browse the repository at this point in the history
  • Loading branch information
KaWaite authored Nov 3, 2023
1 parent 0ba17b4 commit a85ca44
Show file tree
Hide file tree
Showing 16 changed files with 612 additions and 230 deletions.
1 change: 1 addition & 0 deletions server/pkg/builtin/manifest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1996,6 +1996,7 @@ extensions:
- id: menu
title: Menu
list: true
representativeField: menuTitle
availableIf:
field: buttonType
type: string
Expand Down
4 changes: 3 additions & 1 deletion web/src/beta/components/fields/ListField/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type ListItem = {
};

export type Props = {
className?: string;
name?: string;
description?: string;
items: ListItem[];
Expand All @@ -26,6 +27,7 @@ export type Props = {
} & Pick<DragAndDropProps, "onItemDrop">;

const ListField: React.FC<Props> = ({
className,
name,
description,
items,
Expand Down Expand Up @@ -65,7 +67,7 @@ const ListField: React.FC<Props> = ({

return (
<Property name={name} description={description}>
<FieldWrapper>
<FieldWrapper className={className}>
<DragAndDropList<ListItem>
uniqueKey="ListField"
items={items}
Expand Down
164 changes: 164 additions & 0 deletions web/src/beta/components/fields/Property/PropertyField/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import { useMemo } from "react";

import { FlyTo } from "@reearth/beta/lib/core/types";
import { LatLng } from "@reearth/beta/utils/value";
import { Field, SchemaField } from "@reearth/services/api/propertyApi/utils";

import CameraField from "../../CameraField";
import { Camera } from "../../CameraField/types";
import ColorField from "../../ColorField";
import DateTimeField from "../../DateTimeField";
import LocationField from "../../LocationField";
import NumberField from "../../NumberField";
import SelectField from "../../SelectField";
import SliderField from "../../SliderField";
import SpacingInput, { SpacingValues } from "../../SpacingInput";
import TextInput from "../../TextField";
import ToggleField from "../../ToggleField";
import URLField from "../../URLField";

import useHooks from "./hooks";

type Props = {
propertyId: string;
itemId?: string;
schemaGroup: string;
schema: SchemaField;
field?: Field;
currentCamera?: Camera;
onFlyTo?: FlyTo;
};

const PropertyField: React.FC<Props> = ({
propertyId,
itemId,
field,
schemaGroup,
schema,
currentCamera,
onFlyTo,
}) => {
const { handlePropertyValueUpdate } = useHooks(propertyId, schemaGroup);

const value = useMemo(
() => field?.mergedValue ?? field?.value ?? schema.defaultValue,
[field?.mergedValue, field?.value, schema.defaultValue],
);

const handleChange = handlePropertyValueUpdate(schema.id, schema.type, itemId);
return (
<>
{schema.type === "string" ? (
schema.ui === "datetime" ? (
<DateTimeField
key={schema.id}
name={schema.name}
value={(value as string) ?? ""}
description={schema.description}
onChange={handleChange}
/>
) : schema.ui === "color" ? (
<ColorField
key={schema.id}
name={schema.name}
value={(value as string) ?? ""}
description={schema.description}
onChange={handleChange}
/>
) : schema.ui === "selection" || schema.choices ? (
<SelectField
key={schema.id}
name={schema.name}
value={(value as string) ?? ""}
description={schema.description}
options={schema.choices}
onChange={handleChange}
/>
) : schema.ui === "buttons" ? (
<p key={schema.id}>Button radio field</p>
) : (
<TextInput
key={schema.id}
name={schema.name}
value={(value as string) ?? ""}
description={schema.description}
onChange={handleChange}
/>
)
) : schema.type === "url" ? (
<URLField
key={schema.id}
name={schema.name}
entityType={schema.ui === "image" ? "image" : schema.ui === "file" ? "file" : undefined}
fileType={schema.ui === "video" || schema.ui === undefined ? "URL" : "asset"}
value={(value as string) ?? ""}
description={schema.description}
onChange={handleChange}
/>
) : schema.type === "spacing" ? (
<SpacingInput
key={schema.id}
name={schema.name}
value={(value as SpacingValues) ?? ""}
description={schema.description}
min={schema.min}
max={schema.max}
onChange={handleChange}
/>
) : schema.type === "bool" ? (
<ToggleField
key={schema.id}
name={schema.name}
checked={!!value}
description={schema.description}
onChange={handleChange}
/>
) : schema.type === "number" ? (
schema.ui === "slider" ? (
<SliderField
key={schema.id}
name={schema.name}
value={value as number}
min={schema.min}
max={schema.max}
description={schema.description}
onChange={handleChange}
/>
) : (
<NumberField
key={schema.id}
name={schema.name}
value={(value as number) ?? ""}
suffix={schema.suffix}
min={schema.min}
max={schema.max}
description={schema.description}
onChange={handleChange}
/>
)
) : schema.type === "latlng" ? (
<LocationField
key={schema.id}
name={schema.name}
value={value as LatLng}
description={schema.description}
onChange={handleChange}
/>
) : schema.type === "camera" ? (
<CameraField
key={schema.id}
name={schema.name}
value={value as Camera}
description={schema.description}
currentCamera={currentCamera}
onSave={handleChange}
onFlyTo={onFlyTo}
/>
) : (
<p key={schema.id}>{schema.name} field</p>
)}
</>
);
};

export default PropertyField;
129 changes: 129 additions & 0 deletions web/src/beta/components/fields/Property/PropertyItem/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { useMemo, useState } from "react";

import { FlyTo } from "@reearth/beta/lib/core/types";
import { Camera, ValueType, ValueTypes, zeroValues } from "@reearth/beta/utils/value";
import { Group, GroupListItem, Item } from "@reearth/services/api/propertyApi/utils";
import { useT } from "@reearth/services/i18n";

import PropertyField from "../PropertyField";
import PropertyList, { ListItem } from "../PropertyList";

type Props = {
propertyId: string;
item?: Item;
currentCamera?: Camera;
onFlyTo?: FlyTo;
};

const PropertyItem: React.FC<Props> = ({ propertyId, item, currentCamera, onFlyTo }) => {
const t = useT();
const [selected, select] = useState<string>();

const isList = item && "items" in item;
const layerMode = useMemo(() => {
if (!isList || !item?.representativeField) return false;
const sf = item.schemaFields.find(f => f.id === item.representativeField);
return sf?.type === "ref" && sf.ui === "layer";
}, [isList, item?.representativeField, item?.schemaFields]);

const groups = useMemo<(GroupListItem | Group)[]>(
() => (item && "items" in item ? item.items : item ? [item] : []),
[item],
);

const selectedItem = isList ? groups.find(g => g.id === selected) : groups[0];

const propertyListItems = useMemo(
() =>
groups
.map<ListItem | undefined>(i => {
if (!i.id) return;

const representativeField = item?.representativeField
? i.fields.find(f => f.id === item.representativeField)
: undefined;
const nameSchemaField = item?.schemaFields?.find(
sf => sf.id === item.representativeField,
);

const value = representativeField?.value || nameSchemaField?.defaultValue;

const choice = nameSchemaField?.choices
? nameSchemaField?.choices?.find(c => c.key === value)?.label
: undefined;

const title = valueToString(choice || value);

return {
id: i.id,
title: (!layerMode ? title : undefined) ?? t("Settings"),
layerId: layerMode ? title : undefined,
};
})
.filter((g): g is ListItem => !!g),
[groups, layerMode, item, t],
);
const schemaFields = useMemo(
() =>
selectedItem
? item?.schemaFields.map(f => {
const field = selectedItem?.fields.find(f2 => f2.id === f.id);
const condf = f.only && selectedItem?.fields.find(f2 => f2.id === f.only?.field);
const condsf = f.only && item.schemaFields.find(f2 => f2.id === f.only?.field);
const condv =
condf?.value ??
condf?.mergedValue ??
condsf?.defaultValue ??
(condsf?.type ? zeroValues[condsf.type] : undefined);
return {
schemaField: f,
field,
hidden: f.only && (!condv || condv !== f.only.value),
};
})
: [],
[item?.schemaFields, selectedItem],
);

return (
<>
{isList && !!item && (
<PropertyList
name={item.title || (item.id === "default" ? "defaultItemName" : "")}
items={propertyListItems}
propertyId={propertyId}
schemaGroup={item.schemaGroup}
// layers={props.layers}
// layerMode={layerMode}
selected={selected}
onSelect={select}
/>
)}
{!!item &&
schemaFields?.map(f => {
if ((layerMode && f.schemaField.id === item.representativeField) || f.hidden) return null;
return (
<PropertyField
key={f.schemaField.id}
propertyId={propertyId}
schemaGroup={item.schemaGroup}
field={f.field}
itemId={selected}
schema={f.schemaField}
currentCamera={currentCamera}
onFlyTo={onFlyTo}
/>
);
})}
</>
);
};

export default PropertyItem;

const valueToString = (v: ValueTypes[ValueType] | undefined): string | undefined => {
if (typeof v === "string" || typeof v === "number") {
return v.toString();
}
return undefined;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { useArgs } from "@storybook/preview-api";
import { Meta, StoryObj } from "@storybook/react";
import { useCallback } from "react";

import { styled } from "@reearth/services/theme";

import ListField, { Props } from ".";

const meta: Meta<typeof ListField> = {
component: ListField,
};

export default meta;

type Story = StoryObj<typeof ListField>;

export const Default: Story = (args: Props) => {
const [_, updateArgs] = useArgs();

const onSelect = useCallback((id: string) => updateArgs({ selected: id }), [updateArgs]);

return (
<Wrapper>
<div>
<ListField {...args} onSelect={onSelect} />
</div>
</Wrapper>
);
};

const Wrapper = styled.div`
display: flex;
flex-direction: column;
gap: 10%;
margin-left: 2rem;
margin-top: 2rem;
height: 300px;
width: 300px;
`;

Default.args = {
name: "List Field",
description: "List field Sample description",
items: [
{
id: "w3tlwi",
title: "Item w3tlwi",
},
{
id: "77eg5",
title: "Item 77eg5",
},
{
id: "7p218",
title: "Item 7p218",
},
{
id: "xquyo",
title: "Item xquyo",
},
{
id: "2mewj",
title: "Item 2mewj",
},
{
id: "d2gmu",
title: "Item d2gmu",
},
],
};
Loading

0 comments on commit a85ca44

Please sign in to comment.