mirror of
https://github.com/maputnik/editor.git
synced 2026-07-19 12:27:26 +00:00
Replace default export with named exports (#1998)
## Launch Checklist See title, Also removed some "_" from some file names. This is a pure refactoring, no logic changes. - [x] Briefly describe the changes in this PR. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,383 @@
|
||||
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 { InputButton } from "./InputButton";
|
||||
import { InputSpec } from "./InputSpec";
|
||||
import { InputNumber } from "./InputNumber";
|
||||
import { InputString } from "./InputString";
|
||||
import { InputSelect } from "./InputSelect";
|
||||
import { Block } from "./Block";
|
||||
import { generateUniqueId as docUid } from "../libs/document-uid";
|
||||
import { sortNumerically } from "../libs/sort-numerically";
|
||||
import {findDefaultFromSpec} from "../libs/spec-helper";
|
||||
import { type WithTranslation, withTranslation } from "react-i18next";
|
||||
|
||||
import { labelFromFieldName } from "../libs/label-from-field-name";
|
||||
import { DeleteStopButton } from "./DeleteStopButton";
|
||||
import { type MappedLayerErrors } from "../libs/definitions";
|
||||
|
||||
|
||||
|
||||
function setStopRefs(props: DataPropertyInternalProps, state: DataPropertyState) {
|
||||
// This is initialised below only if required to improved performance.
|
||||
let newRefs: {[key: number]: string} | undefined;
|
||||
|
||||
if(props.value && props.value.stops) {
|
||||
props.value.stops.forEach((_val, idx) => {
|
||||
if(!Object.prototype.hasOwnProperty.call(state.refs, idx)) {
|
||||
if(!newRefs) {
|
||||
newRefs = {...state};
|
||||
}
|
||||
newRefs[idx] = docUid("stop-");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return newRefs;
|
||||
}
|
||||
|
||||
type DataPropertyInternalProps = {
|
||||
onChange?(fieldName: string, value: any): unknown
|
||||
onDeleteStop?(...args: unknown[]): unknown
|
||||
onAddStop?(...args: unknown[]): unknown
|
||||
onExpressionClick?(...args: unknown[]): unknown
|
||||
onChangeToZoomFunction?(...args: unknown[]): unknown
|
||||
fieldName: string
|
||||
fieldType?: string
|
||||
fieldSpec?: object
|
||||
value?: DataPropertyValue
|
||||
errors?: MappedLayerErrors
|
||||
} & WithTranslation;
|
||||
|
||||
type DataPropertyState = {
|
||||
refs: {[key: number]: string}
|
||||
};
|
||||
|
||||
type DataPropertyValue = {
|
||||
default?: any
|
||||
property?: string
|
||||
base?: number
|
||||
type?: string
|
||||
stops: Stop[]
|
||||
};
|
||||
|
||||
export type Stop = [{
|
||||
zoom: number
|
||||
value: number
|
||||
}, number];
|
||||
|
||||
class DataPropertyInternal extends React.Component<DataPropertyInternalProps, DataPropertyState> {
|
||||
state = {
|
||||
refs: {} as {[key: number]: string}
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
const newRefs = setStopRefs(this.props, this.state);
|
||||
|
||||
if(newRefs) {
|
||||
this.setState({
|
||||
refs: newRefs
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props: Readonly<DataPropertyInternalProps>, state: DataPropertyState) {
|
||||
const newRefs = setStopRefs(props, state);
|
||||
if(newRefs) {
|
||||
return {
|
||||
refs: newRefs
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getFieldFunctionType(fieldSpec: any) {
|
||||
if (fieldSpec.expression.interpolated) {
|
||||
return "exponential";
|
||||
}
|
||||
if (fieldSpec.type === "number") {
|
||||
return "interval";
|
||||
}
|
||||
return "categorical";
|
||||
}
|
||||
|
||||
getDataFunctionTypes(fieldSpec: any) {
|
||||
if (fieldSpec.expression.interpolated) {
|
||||
return ["interpolate", "categorical", "interval", "exponential", "identity"];
|
||||
}
|
||||
else {
|
||||
return ["categorical", "interval", "identity"];
|
||||
}
|
||||
}
|
||||
|
||||
// Order the stops altering the refs to reflect their new position.
|
||||
orderStopsByZoom(stops: Stop[]) {
|
||||
const mappedWithRef = stops
|
||||
.map((stop, idx) => {
|
||||
return {
|
||||
ref: this.state.refs[idx],
|
||||
data: stop
|
||||
};
|
||||
})
|
||||
// Sort by zoom
|
||||
.sort((a, b) => sortNumerically(a.data[0].zoom, b.data[0].zoom));
|
||||
|
||||
// Fetch the new position of the stops
|
||||
const newRefs = {} as {[key: number]: string};
|
||||
mappedWithRef
|
||||
.forEach((stop, idx) =>{
|
||||
newRefs[idx] = stop.ref;
|
||||
});
|
||||
|
||||
this.setState({
|
||||
refs: newRefs
|
||||
});
|
||||
|
||||
return mappedWithRef.map((item) => item.data);
|
||||
}
|
||||
|
||||
onChange = (fieldName: string, value: any) => {
|
||||
if (value.type === "identity") {
|
||||
value = {
|
||||
type: value.type,
|
||||
property: value.property,
|
||||
};
|
||||
}
|
||||
else {
|
||||
const stopValue = value.type === "categorical" ? "" : 0;
|
||||
value = {
|
||||
property: "",
|
||||
type: value.type,
|
||||
// Default props if they don't already exist.
|
||||
stops: [
|
||||
[{zoom: 6, value: stopValue}, findDefaultFromSpec(this.props.fieldSpec as any)],
|
||||
[{zoom: 10, value: stopValue}, findDefaultFromSpec(this.props.fieldSpec as any)]
|
||||
],
|
||||
...value,
|
||||
};
|
||||
}
|
||||
this.props.onChange!(fieldName, value);
|
||||
};
|
||||
|
||||
changeStop(changeIdx: number, stopData: { zoom: number | undefined, value: number }, value: number) {
|
||||
const stops = this.props.value?.stops.slice(0) || [];
|
||||
// const changedStop = stopData.zoom === undefined ? stopData.value : stopData
|
||||
stops[changeIdx] = [
|
||||
{
|
||||
value: stopData.value,
|
||||
zoom: (stopData.zoom === undefined) ? 0 : stopData.zoom,
|
||||
},
|
||||
value
|
||||
];
|
||||
|
||||
const orderedStops = this.orderStopsByZoom(stops);
|
||||
|
||||
const changedValue = {
|
||||
...this.props.value,
|
||||
stops: orderedStops,
|
||||
};
|
||||
this.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(propVal: string) {
|
||||
if (propVal === "interpolate" && this.props.onChangeToZoomFunction) {
|
||||
this.props.onChangeToZoomFunction();
|
||||
}
|
||||
else {
|
||||
this.onChange(this.props.fieldName, {
|
||||
...this.props.value,
|
||||
type: propVal,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
changeDataProperty(propName: "property" | "default", propVal: any) {
|
||||
if (propVal) {
|
||||
this.props.value![propName] = propVal;
|
||||
}
|
||||
else {
|
||||
delete this.props.value![propName];
|
||||
}
|
||||
this.onChange(this.props.fieldName, this.props.value);
|
||||
}
|
||||
|
||||
render() {
|
||||
const t = this.props.t;
|
||||
|
||||
if (typeof this.props.value?.type === "undefined") {
|
||||
this.props.value!.type = this.getFieldFunctionType(this.props.fieldSpec);
|
||||
}
|
||||
|
||||
let dataFields;
|
||||
if (this.props.value?.stops) {
|
||||
dataFields = this.props.value.stops.map((stop, idx) => {
|
||||
const zoomLevel = typeof stop[0] === "object" ? stop[0].zoom : undefined;
|
||||
const key = this.state.refs[idx];
|
||||
const dataLevel = typeof stop[0] === "object" ? stop[0].value : stop[0];
|
||||
const value = stop[1];
|
||||
const deleteStopBtn = <DeleteStopButton onClick={this.props.onDeleteStop?.bind(this, idx)} />;
|
||||
|
||||
const dataProps = {
|
||||
"aria-label": t("Input value"),
|
||||
label: t("Data value"),
|
||||
value: dataLevel as any,
|
||||
onChange: (newData: string | number | undefined) => this.changeStop(idx, { zoom: zoomLevel, value: newData as number }, value)
|
||||
};
|
||||
|
||||
let dataInput;
|
||||
if(this.props.value?.type === "categorical") {
|
||||
dataInput = <InputString {...dataProps} />;
|
||||
}
|
||||
else {
|
||||
dataInput = <InputNumber {...dataProps} />;
|
||||
}
|
||||
|
||||
let zoomInput = null;
|
||||
if(zoomLevel !== undefined) {
|
||||
zoomInput = <div>
|
||||
<InputNumber
|
||||
aria-label="Zoom"
|
||||
value={zoomLevel}
|
||||
onChange={newZoom => this.changeStop(idx, {zoom: newZoom, value: dataLevel}, value)}
|
||||
min={0}
|
||||
max={22}
|
||||
/>
|
||||
</div>;
|
||||
}
|
||||
|
||||
return <tr key={key}>
|
||||
<td>
|
||||
{zoomInput}
|
||||
</td>
|
||||
<td>
|
||||
{dataInput}
|
||||
</td>
|
||||
<td>
|
||||
<InputSpec
|
||||
aria-label={t("Output value")}
|
||||
fieldName={this.props.fieldName}
|
||||
fieldSpec={this.props.fieldSpec}
|
||||
value={value}
|
||||
onChange={(_, newValue) => this.changeStop(idx, {zoom: zoomLevel, value: dataLevel}, newValue as number)}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
{deleteStopBtn}
|
||||
</td>
|
||||
</tr>;
|
||||
});
|
||||
}
|
||||
|
||||
return <div className="maputnik-data-spec-block">
|
||||
<fieldset className="maputnik-data-spec-property">
|
||||
<legend>{labelFromFieldName(this.props.fieldName)}</legend>
|
||||
<div className="maputnik-data-fieldset-inner">
|
||||
<Block
|
||||
label={t("Function")}
|
||||
key="function"
|
||||
>
|
||||
<div className="maputnik-data-spec-property-input">
|
||||
<InputSelect
|
||||
value={this.props.value!.type}
|
||||
onChange={(propVal: string) => this.changeDataType(propVal)}
|
||||
title={t("Select a type of data scale (default is 'categorical').")}
|
||||
options={this.getDataFunctionTypes(this.props.fieldSpec)}
|
||||
/>
|
||||
</div>
|
||||
</Block>
|
||||
{this.props.value?.type !== "identity" &&
|
||||
<Block
|
||||
label={t("Base")}
|
||||
key="base"
|
||||
>
|
||||
<div className="maputnik-data-spec-property-input">
|
||||
<InputSpec
|
||||
fieldName={"base"}
|
||||
fieldSpec={latest.function.base as typeof latest.function.base & { type: "number" }}
|
||||
value={this.props.value?.base}
|
||||
onChange={(_, newValue) => this.changeBase(newValue as number)}
|
||||
/>
|
||||
</div>
|
||||
</Block>
|
||||
}
|
||||
<Block
|
||||
label={"Property"}
|
||||
key="property"
|
||||
>
|
||||
<div className="maputnik-data-spec-property-input">
|
||||
<InputString
|
||||
value={this.props.value?.property}
|
||||
title={t("Input a data property to base styles off of.")}
|
||||
onChange={propVal => this.changeDataProperty("property", propVal)}
|
||||
/>
|
||||
</div>
|
||||
</Block>
|
||||
{dataFields &&
|
||||
<Block
|
||||
label={t("Default")}
|
||||
key="default"
|
||||
>
|
||||
<InputSpec
|
||||
fieldName={this.props.fieldName}
|
||||
fieldSpec={this.props.fieldSpec}
|
||||
value={this.props.value?.default}
|
||||
onChange={(_, propVal) => this.changeDataProperty("default", propVal)}
|
||||
/>
|
||||
</Block>
|
||||
}
|
||||
{dataFields &&
|
||||
<div className="maputnik-function-stop">
|
||||
<table className="maputnik-function-stop-table">
|
||||
<caption>{t("Stops")}</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{t("Zoom")}</th>
|
||||
<th>{t("Input value")}</th>
|
||||
<th rowSpan={2}>{t("Output value")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{dataFields}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
<div className="maputnik-toolbox">
|
||||
{dataFields &&
|
||||
<InputButton
|
||||
className="maputnik-add-stop"
|
||||
onClick={this.props.onAddStop?.bind(this)}
|
||||
>
|
||||
<PiListPlusBold style={{ verticalAlign: "text-bottom" }} />
|
||||
{t("Add stop")}
|
||||
</InputButton>
|
||||
}
|
||||
<InputButton
|
||||
className="maputnik-add-stop"
|
||||
onClick={this.props.onExpressionClick?.bind(this)}
|
||||
>
|
||||
<TbMathFunction style={{ verticalAlign: "text-bottom" }} />
|
||||
{t("Convert to expression")}
|
||||
</InputButton>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export const DataProperty = withTranslation()(DataPropertyInternal);
|
||||
Reference in New Issue
Block a user