From 176e1e4ae81d79090cd302f3bf1388b035480dab Mon Sep 17 00:00:00 2001 From: HarelM Date: Wed, 17 Sep 2025 00:12:14 +0300 Subject: [PATCH] Final fixes --- src/components/Block.tsx | 1 + src/components/FilterEditor.tsx | 6 +- src/components/InputJson.tsx | 88 ++++++---------------- src/components/_ExpressionProperty.tsx | 8 +- src/libs/codemirror-mgl.ts | 35 ++++++--- src/styles/_codemirror.scss | 100 ------------------------- src/styles/index.scss | 1 - 7 files changed, 61 insertions(+), 178 deletions(-) delete mode 100644 src/styles/_codemirror.scss diff --git a/src/components/Block.tsx b/src/components/Block.tsx index 1510a8d9..e83e4705 100644 --- a/src/components/Block.tsx +++ b/src/components/Block.tsx @@ -11,6 +11,7 @@ export type BlockProps = PropsWithChildren & { onChange?(...args: unknown[]): unknown fieldSpec?: object wideMode?: boolean + error?: {message: string} }; type BlockState = { diff --git a/src/components/FilterEditor.tsx b/src/components/FilterEditor.tsx index 68d87282..1a7ec9ef 100644 --- a/src/components/FilterEditor.tsx +++ b/src/components/FilterEditor.tsx @@ -3,7 +3,8 @@ import { TbMathFunction } from "react-icons/tb"; import { PiListPlusBold } from "react-icons/pi"; import {isEqual} from "lodash"; import {type ExpressionSpecification, type LegacyFilterSpecification} from "maplibre-gl"; -import {latest, migrate, convertFilter} from "@maplibre/maplibre-gl-style-spec"; +import {migrate, convertFilter} from "@maplibre/maplibre-gl-style-spec"; +import latest from "@maplibre/maplibre-gl-style-spec/dist/latest.json"; import {combiningFilterOps} from "../libs/filterops"; import InputSelect from "./InputSelect"; @@ -96,7 +97,7 @@ type FilterEditorInternalProps = { properties?: {[key:string]: any} filter?: any[] errors?: MappedLayerErrors - onChange(value: LegacyFilterSpecification | ExpressionSpecification): unknown + onChange(value: LegacyFilterSpecification | ExpressionSpecification): void } & WithTranslation; type FilterEditorState = { @@ -293,6 +294,7 @@ class FilterEditorInternal extends React.Component { static defaultProps = { - lineNumbers: true, - lineWrapping: false, - gutters: ["CodeMirror-lint-markers"], onFocus: () => {}, onBlur: () => {}, - onJSONInvalid: () => {}, - onJSONValid: () => {}, }; _keyEvent: string; - _doc: EditorView | undefined; + _view: EditorView | undefined; _el: HTMLDivElement | null = null; _cancelNextChange: boolean = false; @@ -59,37 +49,15 @@ class InputJsonInternal extends React.Component this.props.onChange(value ? JSON.parse(value) : {}), - this.props.spec - ); - /*CodeMirror(this._el!, { - value: this.props.getValue!(this.props.layer), - mode: this.props.mode || { - name: "mgl", - }, - lineWrapping: this.props.lineWrapping, - tabSize: 2, - theme: "maputnik", - viewportMargin: Infinity, - lineNumbers: this.props.lineNumbers, - lint: this.props.lint || { - context: "layer" - }, - matchBrackets: true, - gutters: this.props.gutters, - scrollbarStyle: "null", + 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 }); - - - this._doc.on("change", this.onChange); - this._doc.on("focus", this.onFocus); - this._doc.on("blur", this.onBlur); - */ - this._doc.dispatch(); } onPointerDown = () => { @@ -100,7 +68,6 @@ class InputJsonInternal extends React.Component { - /* if (this._cancelNextChange) { this._cancelNextChange = false; this.setState({ - prevValue: this._doc!.getValue(), + prevValue: this._view!.state.doc.toString(), }); return; } - const newCode = this._doc!.getValue(); + const newCode = this._view!.state.doc.toString(); if (this.state.prevValue !== newCode) { let parsedLayer, err; @@ -148,19 +111,14 @@ class InputJsonInternal extends React.Component ); - + let error = undefined; + if (this.props.errors) { + const fieldKey = this.props.fieldType ? this.props.fieldType + "." + this.props.fieldName : this.props.fieldName; + error = this.props.errors[fieldKey]; + } return void, + onFocus: () => void, + onBlur: () => void, spec?: StylePropertySpecification, -): EditorView { +}): EditorView { let specificLinter: (view: EditorView) => Diagnostic[] = () => []; - switch (lintType) { + switch (props.lintType) { case "style": specificLinter = createMaplibreStyleLinter(); break; @@ -166,26 +169,40 @@ export function createEditor( specificLinter = createMaplibreLayerLinter(); break; case "expression": - if (!spec) { + if (!props.spec) { throw new Error("spec is required for expression mode"); } - specificLinter = createMaplibreExpressionLinter(spec); + specificLinter = createMaplibreExpressionLinter(props.spec); break; case "json": specificLinter = () => []; break; } + return new EditorView({ - doc: value, + doc: props.value, extensions: [ basicSetup, json(), - oneDark, + oneDark, + new Compartment().of(EditorState.tabSize.of(2)), + EditorView.theme({ + "&": { + fontSize: "9pt" + } + }), EditorView.updateListener.of((update) => { if (update.docChanged) { const doc = update.state.doc; const value = doc.toString(); - onChange(value); + props.onChange(value); + } + if (update.focusChanged) { + if (update.view.hasFocus) { + props.onFocus(); + } else { + props.onBlur(); + } } }), lintGutter(), @@ -197,6 +214,6 @@ export function createEditor( return specificLinter(view); }) ], - parent, + parent: props.parent, }); } \ No newline at end of file diff --git a/src/styles/_codemirror.scss b/src/styles/_codemirror.scss deleted file mode 100644 index 680ddc35..00000000 --- a/src/styles/_codemirror.scss +++ /dev/null @@ -1,100 +0,0 @@ -@use "vars"; - -.CodeMirror-lint-tooltip { - z-index: 2000 !important; -} - -.codemirror-container { - max-width: 100%; - position: relative; - overflow: auto; -} - -.cm-s-maputnik.CodeMirror { - height: 100%; - font-size: 12px; - background: transparent; -} - -.cm-s-maputnik.CodeMirror, .cm-s-maputnik .CodeMirror-gutters { - color: #8e8e8e; - border: none; -} - -.cm-s-maputnik .CodeMirror-gutters { - background: #212328; -} - -.cm-s-maputnik .CodeMirror-cursor { - border-left: solid thin #f0f0f0 !important; -} - -.cm-s-maputnik.CodeMirror-focused div.CodeMirror-selected { - background: rgba(255, 255, 255, 0.10); -} - -.cm-s-maputnik .CodeMirror-line::selection, -.cm-s-maputnik .CodeMirror-line > span::selection, -.cm-s-maputnik .CodeMirror-line > span > span::selection { - background: rgba(255, 255, 255, 0.10); -} - -.cm-s-maputnik .CodeMirror-line::-moz-selection, -.cm-s-maputnik .CodeMirror-line > span::-moz-selection, -.cm-s-maputnik .CodeMirror-line > span > span::-moz-selection { - background: rgba(255, 255, 255, 0.10); -} - -.cm-s-maputnik span.cm-string, .cm-s-maputnik span.cm-string-2 { - color: #8f9d6a; -} -.cm-s-maputnik span.cm-number { color: #91675f; } -.cm-s-maputnik span.cm-property { color: #b8a077; } - -.cm-s-maputnik .CodeMirror-activeline-background { - background: rgba(255,255,255,0.1); -} - -.cm-s-maputnik .CodeMirror-matchingbracket { - background: hsla(223, 12%, 35%, 1); - color: vars.$color-white !important; -} - -.cm-s-maputnik .CodeMirror-nonmatchingbracket { - background-color: #bb0000; - color: white !important; -} - -@keyframes JSONEditor__animation-fade { - from { - opacity: 1; - } - to { - opacity: 0; - } -} - -.JSONEditor__message { - position: absolute; - right: 0; - font-size: 0.85em; - z-index: 99999; - padding: 0.3em 0.5em; - background: hsla(0, 0%, 0%, 0.3); - color: vars.$color-lowgray; - border-bottom-left-radius: 2px; - transition: opacity 320ms ease; - opacity: 0; - pointer-events: none; - - &--on { - opacity: 1; - animation: 320ms ease 0s JSONEditor__animation-fade; - animation-delay: 2000ms; - animation-fill-mode: forwards; - } - - kbd { - font-family: monospace; - } -} diff --git a/src/styles/index.scss b/src/styles/index.scss index 7f9ade17..89ff5d78 100644 --- a/src/styles/index.scss +++ b/src/styles/index.scss @@ -15,7 +15,6 @@ @use 'zoomproperty'; @use 'popup'; @use 'map'; -@use 'codemirror'; @use 'react-collapse'; .maputnik-layout {