Files
editor/src/components/InputSpec.tsx
Harel M 3c3fcadbb6 Added back errors panel (#1384)
## Launch Checklist

This PR adds back the error panel which was under the map for some
reason.
It also highlights problematic layers in the layers list (which already
worked).
It also highlights the field that has an error related to it.
It fixes the error types throughout the code.

Before:
<img width="1141" height="665" alt="image"
src="https://github.com/user-attachments/assets/c0593d6c-8f14-41b3-8a51-bc359446656d"
/>


After:
<img width="1141" height="665" alt="image"
src="https://github.com/user-attachments/assets/1ffeebb7-31ea-4ed5-97f4-fc5f907a6aea"
/>


 - [x] Briefly describe the changes in this PR.
- [x] Include before/after visuals or gifs if this PR includes visual
changes.
 - [x] Write tests for all new functionality.
 - [x] Add an entry to `CHANGELOG.md` under the `## main` section.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-09-16 15:42:07 +02:00

155 lines
5.4 KiB
TypeScript

import React, { type ReactElement } from "react";
import InputColor, { type InputColorProps } from "./InputColor";
import InputNumber, { type InputNumberProps } from "./InputNumber";
import InputCheckbox, { type InputCheckboxProps } from "./InputCheckbox";
import InputString, { type InputStringProps } from "./InputString";
import InputArray, { type InputArrayProps } from "./InputArray";
import InputDynamicArray, { type InputDynamicArrayProps } from "./InputDynamicArray";
import InputFont, { type InputFontProps } from "./InputFont";
import InputAutocomplete, { type InputAutocompleteProps } from "./InputAutocomplete";
import InputEnum, { type InputEnumProps } from "./InputEnum";
import capitalize from "lodash.capitalize";
const iconProperties = ["background-pattern", "fill-pattern", "line-pattern", "fill-extrusion-pattern", "icon-image"];
export type FieldSpecType = "number" | "enum" | "resolvedImage" | "formatted" | "string" | "color" | "boolean" | "array" | "numberArray" | "padding" | "colorArray" | "variableAnchorOffsetCollection";
export type InputSpecProps = {
onChange?(fieldName: string | undefined, value: number | undefined | (string | number | undefined)[]): unknown
fieldName?: string
fieldSpec?: {
default?: unknown
type?: FieldSpecType
minimum?: number
maximum?: number
values?: unknown[]
length?: number
value?: string
}
value?: string | number | unknown[] | boolean
/** Override the style of the field */
style?: object
"aria-label"?: string
label?: string
action?: ReactElement
};
/** Display any field from the Maplibre GL style spec and
* choose the correct field component based on the @{fieldSpec}
* to display @{value}. */
export default class InputSpec extends React.Component<InputSpecProps> {
childNodes() {
const commonProps = {
fieldSpec: this.props.fieldSpec,
label: this.props.label,
action: this.props.action,
style: this.props.style,
value: this.props.value,
default: this.props.fieldSpec?.default,
name: this.props.fieldName,
"data-wd-key": "spec-field-input:" + this.props.fieldName,
onChange: (newValue: number | undefined | (string | number | undefined)[]) => this.props.onChange!(this.props.fieldName, newValue),
"aria-label": this.props["aria-label"],
};
switch(this.props.fieldSpec?.type) {
case "number": return (
<InputNumber
{...commonProps as InputNumberProps}
min={this.props.fieldSpec.minimum}
max={this.props.fieldSpec.maximum}
/>
);
case "enum": {
const options = Object.keys(this.props.fieldSpec.values || []).map(v => [v, capitalize(v)]);
return <InputEnum
{...commonProps as Omit<InputEnumProps, "options">}
options={options}
/>;
}
case "resolvedImage":
case "formatted":
case "string":
if (iconProperties.indexOf(this.props.fieldName!) >= 0) {
const options = this.props.fieldSpec.values || [];
return <InputAutocomplete
{...commonProps as Omit<InputAutocompleteProps, "options">}
options={options.map(f => [f, f])}
/>;
} else {
return <InputString
{...commonProps as InputStringProps}
/>;
}
case "color": return (
<InputColor
{...commonProps as InputColorProps}
/>
);
case "boolean": return (
<InputCheckbox
{...commonProps as InputCheckboxProps}
/>
);
case "array":
if(this.props.fieldName === "text-font") {
return <InputFont
{...commonProps as InputFontProps}
fonts={this.props.fieldSpec.values}
/>;
} else {
if (this.props.fieldSpec.length) {
return <InputArray
{...commonProps as InputArrayProps}
type={this.props.fieldSpec.value}
length={this.props.fieldSpec.length}
/>;
} else {
return <InputDynamicArray
{...commonProps as InputDynamicArrayProps}
fieldSpec={this.props.fieldSpec}
type={this.props.fieldSpec.value as InputDynamicArrayProps["type"]}
/>;
}
}
case "numberArray": return (
<InputDynamicArray
{...commonProps as InputDynamicArrayProps}
fieldSpec={this.props.fieldSpec}
type="number"
value={(Array.isArray(this.props.value) ? this.props.value : [this.props.value]) as (string | number | undefined)[]}
/>
);
case "colorArray": return (
<InputDynamicArray
{...commonProps as InputDynamicArrayProps}
fieldSpec={this.props.fieldSpec}
type="color"
value={(Array.isArray(this.props.value) ? this.props.value : [this.props.value]) as (string | number | undefined)[]}
/>
);
case "padding": return (
<InputArray
{...commonProps as InputArrayProps}
type="number"
value={(Array.isArray(this.props.value) ? this.props.value : [this.props.value]) as (string | number | undefined)[]}
length={4}
/>
);
default:
console.warn(`No proper field input for ${this.props.fieldName} type: ${this.props.fieldSpec?.type}`);
return null;
}
}
render() {
return (
<div data-wd-key={"spec-field:"+this.props.fieldName}>
{this.childNodes()}
</div>
);
}
}