diff --git a/public/reearth.yml b/public/reearth.yml index 9cf9700..020ca86 100644 --- a/public/reearth.yml +++ b/public/reearth.yml @@ -129,6 +129,15 @@ extensions: description: "Specify the fields to display in the infobox, please use field keys separated by commas. Left empty to show all fields." type: string ui: multiline + - id: title_hidden_fields + title: Title Hidden Fields + description: "Specify the fields to hide their titles from the infobox, please use field keys separated by commas." + type: string + ui: multiline + - id: title_field + title: Title Field + description: "Specify the field to use as the title in the infobox." + type: string - id: inspector_block type: infoboxBlock name: CMS Data Inspector Block diff --git a/src/extensions/inspector_block/inspector_block.ts b/src/extensions/inspector_block/inspector_block.ts index 0ed5e82..76da538 100644 --- a/src/extensions/inspector_block/inspector_block.ts +++ b/src/extensions/inspector_block/inspector_block.ts @@ -3,27 +3,26 @@ import html_main from "@distui/inspector_block/main/index.html?raw"; import { GlobalThis } from "@/shared/reearthTypes"; type UIMessage = { - action: "getProperties"; + action: "getInspector"; }; const reearth = (globalThis as unknown as GlobalThis).reearth; reearth.ui.show(html_main); -const sendProperties = () => { +const sendInspector = () => { reearth.ui.postMessage({ - action: "getProperties", + action: "getInspector", payload: - reearth.layers.selectedFeature?.properties?.__inspector_fields || - undefined, + reearth.layers.selectedFeature?.properties?.__inspector || undefined, }); }; -reearth.layers.on("select", sendProperties); +reearth.layers.on("select", sendInspector); reearth.extension.on("message", (message: unknown) => { const msg = message as UIMessage; - if (msg.action === "getProperties") { - sendProperties(); + if (msg.action === "getInspector") { + sendInspector(); } }); diff --git a/src/extensions/inspector_block/main/App.tsx b/src/extensions/inspector_block/main/App.tsx index 1164b23..95e9ece 100644 --- a/src/extensions/inspector_block/main/App.tsx +++ b/src/extensions/inspector_block/main/App.tsx @@ -1,32 +1,36 @@ import useHooks from "./hooks"; -import PropertyValue, { SingleValueProperty } from "./PropertyValue"; +import PropertyItem from "./PropertyItem"; function App() { - const { properties } = useHooks(); + const { inspector } = useHooks(); - if (!properties) { + if (!inspector) { return null; } return (
- {properties.map((prop) => ( -
-
{prop.name ?? prop.key}
- {Array.isArray(prop.value) ? ( -
- {prop.value.map((item, index) => ( - - ))} + {inspector.title && ( +
{inspector.title}
+ )} + {inspector.properties.map((property) => + property.type === "group" ? ( +
+ {!property.hideTitle && ( +
+ {property.name ?? property.key} +
+ )} +
+ {property.children?.map((child) => ( + + )) ?? null}
- ) : ( - - )} -
- ))} +
+ ) : ( + + ) + )}
); } diff --git a/src/extensions/inspector_block/main/PropertyItem.tsx b/src/extensions/inspector_block/main/PropertyItem.tsx new file mode 100644 index 0000000..6366c04 --- /dev/null +++ b/src/extensions/inspector_block/main/PropertyItem.tsx @@ -0,0 +1,43 @@ +import { FC } from "react"; + +import { Property } from "./hooks"; +import PropertyValue, { SingleValueProperty } from "./PropertyValue"; + +type PropertyItemProps = { + property: Property; +}; + +const PropertyItem: FC = ({ property }) => { + if ( + property.hideTitle && + (property.value === undefined || + property.value === "" || + property.value === null) + ) { + return null; + } + + return ( +
+ {!property.hideTitle && ( +
+ {property.name ?? property.key} +
+ )} + {Array.isArray(property.value) ? ( +
+ {property.value.map((item, index) => ( + + ))} +
+ ) : ( + + )} +
+ ); +}; + +export default PropertyItem; diff --git a/src/extensions/inspector_block/main/PropertyValue.tsx b/src/extensions/inspector_block/main/PropertyValue.tsx index b04dd95..5ab6d43 100644 --- a/src/extensions/inspector_block/main/PropertyValue.tsx +++ b/src/extensions/inspector_block/main/PropertyValue.tsx @@ -73,5 +73,5 @@ const PropertyValue: FC = ({ property }) => { export default PropertyValue; const isImageUrl = (url: string) => { - return /\.(jpeg|jpg|gif|png|svg)$/.test(url); + return /\.(jpeg|jpg|gif|png|svg)$/.test(url.toLowerCase()); }; diff --git a/src/extensions/inspector_block/main/hooks.ts b/src/extensions/inspector_block/main/hooks.ts index e317bb2..bc9f1e7 100644 --- a/src/extensions/inspector_block/main/hooks.ts +++ b/src/extensions/inspector_block/main/hooks.ts @@ -2,7 +2,7 @@ import { useEffect, useState } from "react"; import { postMsg } from "@/shared/utils"; -export type Field = { +export type Property = { id: string; key: string; type: string; @@ -15,18 +15,25 @@ export type Field = { | number[] | boolean[]; name: string; + hideTitle?: boolean; + children?: Property[]; +}; + +export type Inspector = { + title?: string; + properties: Property[]; }; export default () => { - const [properties, setProperties] = useState(null); + const [inspector, setInspector] = useState(null); useEffect(() => { - postMsg("getProperties"); + postMsg("getInspector"); }, []); const handleMessage = (e: MessageEvent) => { - if (e.data.action === "getProperties") { - setProperties(e.data.payload); + if (e.data.action === "getInspector") { + setInspector(e.data.payload); } }; @@ -38,6 +45,6 @@ export default () => { }, []); return { - properties, + inspector, }; }; diff --git a/src/extensions/visualizer/visualizer.ts b/src/extensions/visualizer/visualizer.ts index b2f7f6f..08e1c66 100644 --- a/src/extensions/visualizer/visualizer.ts +++ b/src/extensions/visualizer/visualizer.ts @@ -13,6 +13,8 @@ type VisualizationConfig = { type InspectorConfig = { display_fields?: string; + title_hidden_fields?: string; + title_field?: string; }; type WidgetProperty = { @@ -27,6 +29,8 @@ type ItemField = { type: string; value: unknown; name?: string; + group?: string; + children?: ItemField[]; }; type Item = { @@ -132,9 +136,13 @@ const generateGeoJSON = ( // Convert items to GeoJSON features const inspectorConfig = (reearth.extension?.widget?.property as WidgetProperty)?.inspector || {}; + const displayFields = inspectorConfig.display_fields ? inspectorConfig.display_fields.split(",").map((f) => f.trim()) : []; + const titleHiddenFields = inspectorConfig.title_hidden_fields + ? inspectorConfig.title_hidden_fields.split(",").map((f) => f.trim()) + : []; const features = items .map((item) => { @@ -146,8 +154,18 @@ const generateGeoJSON = ( properties[field.key] = field.value; }); - // Add filtered fields for inspector - properties.__inspector_fields = filterFields(item.fields, displayFields); + // Add property for inspector + properties.__inspector = { + title: inspectorConfig.title_field + ? item.fields.find((f) => f.key === inspectorConfig.title_field) + ?.value + : undefined, + properties: processProperties( + item.fields, + displayFields, + titleHiddenFields + ), + }; // Get location const coordinates = []; @@ -163,7 +181,7 @@ const generateGeoJSON = ( return null; } - coordinates.push(lng, lat); + coordinates.push(Number(lng), Number(lat)); } else if (config.location_type === "lng_lat_array_field") { const latLngArray = item.fields.find( (f) => f.key === config.longitude_latitude_array_field_key @@ -172,7 +190,7 @@ const generateGeoJSON = ( console.log(`Invalid Longitude & Latitude array for item ${item.id}`); return null; } - coordinates.push(latLngArray[0], latLngArray[1]); + coordinates.push(Number(latLngArray[0]), Number(latLngArray[1])); } else if (config.location_type === "geojson_field") { try { const geojson = item.fields.find( @@ -264,3 +282,37 @@ const filterFields = ( } return filteredFields; }; + +const processProperties = ( + originalFields: ItemField[], + displayFieldKeys: string[], + titleHiddenFieldKeys: string[] +) => { + const fieldsToProcess = filterFields(originalFields, displayFieldKeys).map( + (field) => ({ + ...field, + hideTitle: titleHiddenFieldKeys.includes(field.key), + }) + ); + + const result = []; + const byGroup = new Map(); + + for (const field of fieldsToProcess) { + if (field.group) { + const list = byGroup.get(field.group) || []; + list.push(field); + byGroup.set(field.group, list); + } else { + result.push(field); + } + } + + for (const field of result) { + if (field.type === "group") { + field.children = byGroup.get(field.value) ?? []; + } + } + + return result; +}; diff --git a/src/shared/global.css b/src/shared/global.css index 9bc2dbd..453fcf3 100644 --- a/src/shared/global.css +++ b/src/shared/global.css @@ -140,7 +140,7 @@ input[type="number"] { @layer utilities { .prose { @apply text-xs leading-relaxed max-w-none; - font-size: 12px; + font-size: 14px; } .prose p {