import React from "react"; import { PiListPlusBold } from "react-icons/pi"; import { TbMathFunction } from "react-icons/tb"; import latest from "@maplibre/maplibre-gl-style-spec/dist/latest.json"; import { type WithTranslation, withTranslation } from "react-i18next"; import InputButton from "./InputButton"; import InputSpec from "./InputSpec"; import InputNumber from "./InputNumber"; import InputSelect from "./InputSelect"; import Block from "./Block"; import DeleteStopButton from "./_DeleteStopButton"; import labelFromFieldName from "../libs/label-from-field-name"; import docUid from "../libs/document-uid"; import sortNumerically from "../libs/sort-numerically"; import { type MappedLayerErrors } from "../libs/definitions"; /** * We cache a reference for each stop by its index. * * When the stops are reordered the references are also updated (see this.orderStops) this allows React to use the same key for the element and keep keyboard focus. */ function setStopRefs(props: ZoomPropertyInternalProps, state: ZoomPropertyState) { // This is initialsed below only if required to improved performance. let newRefs: {[key: number]: string} = {}; if(props.value && (props.value as ZoomWithStops).stops) { (props.value as ZoomWithStops).stops.forEach((_val, idx: number) => { if(Object.prototype.hasOwnProperty.call(!state.refs, idx)) { if(!newRefs) { newRefs = {...state}; } newRefs[idx] = docUid("stop-"); } else { newRefs[idx] = state.refs[idx]; } }); } return newRefs; } type ZoomWithStops = { stops: [number | undefined, number][] base?: number }; type ZoomPropertyInternalProps = { onChange?(...args: unknown[]): unknown onChangeToDataFunction?(...args: unknown[]): unknown onDeleteStop?(...args: unknown[]): unknown onAddStop?(...args: unknown[]): unknown onExpressionClick?(...args: unknown[]): unknown fieldType?: string fieldName: string fieldSpec?: { "property-type"?: string "function-type"?: string } errors?: MappedLayerErrors value?: ZoomWithStops } & WithTranslation; type ZoomPropertyState = { refs: {[key: number]: string} }; class ZoomPropertyInternal extends React.Component { static defaultProps = { errors: {}, }; state = { refs: {} as {[key: number]: string} }; componentDidMount() { const newRefs = setStopRefs(this.props, this.state); if(newRefs) { this.setState({ refs: newRefs }); } } static getDerivedStateFromProps(props: Readonly, state: ZoomPropertyState) { const newRefs = setStopRefs(props, state); if(newRefs) { return { refs: newRefs }; } return null; } // Order the stops altering the refs to reflect their new position. orderStopsByZoom(stops: ZoomWithStops["stops"]) { const mappedWithRef = stops .map((stop, idx) => { return { ref: this.state.refs[idx], data: stop }; }) // Sort by zoom .sort((a, b) => sortNumerically(a.data[0]!, b.data[0]!)); // Fetch the new position of the stops const newRefs: {[key:number]: string} = {}; mappedWithRef .forEach((stop, idx) =>{ newRefs[idx] = stop.ref; }); this.setState({ refs: newRefs }); return mappedWithRef.map((item) => item.data); } changeZoomStop(changeIdx: number, stopData: number | undefined, value: number) { const stops = (this.props.value as ZoomWithStops).stops.slice(0); stops[changeIdx] = [stopData, value]; const orderedStops = this.orderStopsByZoom(stops); const changedValue = { ...this.props.value as ZoomWithStops, stops: orderedStops }; this.props.onChange!(this.props.fieldName, changedValue); } changeBase(newValue: number | undefined) { const changedValue = { ...this.props.value, base: newValue }; if (changedValue.base === undefined) { delete changedValue["base"]; } this.props.onChange!(this.props.fieldName, changedValue); } changeDataType = (type: string) => { if (type !== "interpolate" && this.props.onChangeToDataFunction) { this.props.onChangeToDataFunction(type); } }; render() { const t = this.props.t; const zoomFields = this.props.value?.stops.map((stop, idx) => { const zoomLevel = stop[0]; const value = stop[1]; const deleteStopBtn = ; return this.changeZoomStop(idx, changedStop, value)} min={0} max={22} /> this.changeZoomStop(idx, zoomLevel, newValue as number)} /> {deleteStopBtn} ; }); // return
return
{labelFromFieldName(this.props.fieldName)}
this.changeDataType(propVal)} title={t("Select a type of data scale (default is 'categorical').")} options={this.getDataFunctionTypes(this.props.fieldSpec!)} />
this.changeBase(newValue as number | undefined)} />
{zoomFields}
{t("Stops")}
{t("Zoom")} {t("Output value")}
{t("Add stop")} {t("Convert to expression")}
; } getDataFunctionTypes(fieldSpec: { "property-type"?: string "function-type"?: string }) { if (fieldSpec["property-type"] === "data-driven") { return ["interpolate", "categorical", "interval", "exponential", "identity"]; } else { return ["interpolate"]; } } } const ZoomProperty = withTranslation()(ZoomPropertyInternal); export default ZoomProperty;