import React from "react"; import classnames from "classnames"; import { type WithTranslation, withTranslation } from "react-i18next"; import { type EditorView } from "@codemirror/view"; import stringifyPretty from "json-stringify-pretty-compact"; import {createEditor} from "../libs/codemirror-editor-factory"; import type { StylePropertySpecification } from "maplibre-gl"; import type { TransactionSpec } from "@codemirror/state"; export type InputJsonProps = { value: object className?: string onChange(object: object): void onFocus?(...args: unknown[]): unknown onBlur?(...args: unknown[]): unknown lintType: "layer" | "style" | "expression" | "json" spec?: StylePropertySpecification | undefined /** * When setting this and using search and replace, the editor will scroll to the selected text * Use this only when the editor is the only element in the page. */ withScroll?: boolean }; type InputJsonInternalProps = InputJsonProps & WithTranslation; type InputJsonState = { isEditing: boolean prevValue: string }; class InputJsonInternal extends React.Component { static defaultProps = { onFocus: () => {}, onBlur: () => {}, withScroll: false }; _view: EditorView | undefined; _el: HTMLDivElement | null = null; _cancelNextChange: boolean = false; constructor(props: InputJsonInternalProps) { super(props); this.state = { isEditing: false, prevValue: this.getPrettyJson(this.props.value), }; } getPrettyJson(data: any) { return stringifyPretty(data, {indent: 2, maxLength: 40}); } componentDidMount () { this._view = createEditor({ parent: this._el!, value: this.getPrettyJson(this.props.value), lintType: this.props.lintType || "layer", onChange: (value:string) => this.onChange(value), onFocus: () => this.onFocus(), onBlur: () => this.onBlur(), spec: this.props.spec }); } onFocus = () => { if (this.props.onFocus) this.props.onFocus(); this.setState({ isEditing: true, }); }; onBlur = () => { if (this.props.onBlur) this.props.onBlur(); this.setState({ isEditing: false, }); }; componentDidUpdate(prevProps: InputJsonProps) { if (!this.state.isEditing && prevProps.value !== this.props.value) { this._cancelNextChange = true; const transactionSpec: TransactionSpec = { changes: { from: 0, to: this._view!.state.doc.length, insert: this.getPrettyJson(this.props.value) } }; if (this.props.withScroll) { transactionSpec.selection = this._view!.state.selection; transactionSpec.scrollIntoView = true; } this._view!.dispatch(transactionSpec); } } onChange = (_e: unknown) => { if (this._cancelNextChange) { this._cancelNextChange = false; this.setState({ prevValue: this._view!.state.doc.toString(), }); return; } const newCode = this._view!.state.doc.toString(); if (this.state.prevValue !== newCode) { let parsedLayer, err; try { parsedLayer = JSON.parse(newCode); } catch(_err) { err = _err; console.warn(_err); } if (!err) { if (this.props.onChange) this.props.onChange(parsedLayer); } } this.setState({ prevValue: newCode, }); }; render() { return