From 871373d78cc530dbb689d716c10d536986234dd3 Mon Sep 17 00:00:00 2001 From: kraysent Date: Wed, 12 Aug 2026 23:18:19 +0100 Subject: [PATCH] use monaco editor for every text field --- .../src/components/ExpressionWidget.test.tsx | 19 ++- frontend/src/components/ExpressionWidget.tsx | 130 ++++++++++-------- frontend/src/components/TaskPage.test.tsx | 1 - frontend/src/components/TaskPage.tsx | 24 +++- frontend/src/extractUiSchema.test.ts | 2 - tests/test_formula_tokens.py | 3 +- uploader/app/lib/formula/namespace.py | 1 - 7 files changed, 112 insertions(+), 68 deletions(-) diff --git a/frontend/src/components/ExpressionWidget.test.tsx b/frontend/src/components/ExpressionWidget.test.tsx index d953c57..eab71e5 100644 --- a/frontend/src/components/ExpressionWidget.test.tsx +++ b/frontend/src/components/ExpressionWidget.test.tsx @@ -1,7 +1,11 @@ import { render, screen } from "@testing-library/react"; import type { WidgetProps } from "@rjsf/utils"; import { describe, expect, it, vi } from "vitest"; -import { ExpressionWidget, findToken } from "./ExpressionWidget"; +import { + ExpressionWidget, + findToken, + type ExpressionToken, +} from "./ExpressionWidget"; const tokens = [ { @@ -18,14 +22,18 @@ const tokens = [ }, ]; -function renderWidget(onChange = vi.fn(), value = "") { +function renderWidget( + onChange = vi.fn(), + value = "", + fieldTokens: ExpressionToken[] = tokens, +) { const props = { id: "root_expression", name: "expression", label: "Designation expression", value, onChange, - options: { tokens }, + options: { tokens: fieldTokens }, schema: { type: "string" }, registry: { formContext: { @@ -52,6 +60,11 @@ describe("ExpressionWidget", () => { renderWidget(); expect(screen.getByLabelText("Designation expression")).toBeInTheDocument(); }); + + it("renders an editor without tokens", () => { + renderWidget(vi.fn(), "", []); + expect(screen.getByLabelText("Designation expression")).toBeInTheDocument(); + }); }); describe("findToken", () => { diff --git a/frontend/src/components/ExpressionWidget.tsx b/frontend/src/components/ExpressionWidget.tsx index 34e93d2..b6fa44b 100644 --- a/frontend/src/components/ExpressionWidget.tsx +++ b/frontend/src/components/ExpressionWidget.tsx @@ -1,5 +1,6 @@ import Editor, { type Monaco } from "@monaco-editor/react"; import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; import { useTheme } from "@mui/material/styles"; import type { WidgetProps } from "@rjsf/utils"; import type { editor, Position } from "monaco-editor"; @@ -157,6 +158,7 @@ function registerExpressionLanguage( export function ExpressionWidget(props: WidgetProps) { const theme = useTheme(); const tokens = readTokens(props.options); + const hasTokens = tokens.length > 0; const value = typeof props.value === "string" ? props.value : ""; const isDark = theme.palette.mode === "dark"; const editorRef = useRef(null); @@ -183,7 +185,9 @@ export function ExpressionWidget(props: WidgetProps) { }, [props.registry?.formContext, props.formContext, props.id]); function handleBeforeMount(monaco: Monaco) { - registerExpressionLanguage(monaco, tokens); + if (hasTokens) { + registerExpressionLanguage(monaco, tokens); + } } function handleMount( @@ -209,65 +213,75 @@ export function ExpressionWidget(props: WidgetProps) { } return ( - - { - const singleLine = (next ?? "").replace(/\r?\n/g, ""); - props.onChange(singleLine); - }} - beforeMount={handleBeforeMount} - onMount={handleMount} - options={{ - ariaLabel: props.label, - readOnly: props.disabled || props.readonly, - minimap: { enabled: false }, - lineNumbers: "off", - folding: false, - glyphMargin: false, - lineDecorationsWidth: 12, - lineNumbersMinChars: 0, - scrollBeyondLastLine: false, - wordWrap: "off", - fontSize: 14, - lineHeight: 22, - padding: { top: 10, bottom: 10 }, - overviewRulerLanes: 0, - hideCursorInOverviewRuler: true, - renderLineHighlight: "none", - scrollbar: { - vertical: "hidden", - horizontal: "auto", - alwaysConsumeMouseWheel: false, + + {!props.hideLabel && props.label ? ( + + {props.label} + {props.required ? " *" : ""} + + ) : null} + } - /> + > + { + const singleLine = (next ?? "").replace(/\r?\n/g, ""); + props.onChange(singleLine); + }} + beforeMount={handleBeforeMount} + onMount={handleMount} + options={{ + ariaLabel: props.label, + readOnly: props.disabled || props.readonly, + minimap: { enabled: false }, + lineNumbers: "off", + folding: false, + glyphMargin: false, + lineDecorationsWidth: 12, + lineNumbersMinChars: 0, + scrollBeyondLastLine: false, + wordWrap: "off", + fontSize: 14, + lineHeight: 22, + padding: { top: 10, bottom: 10 }, + overviewRulerLanes: 0, + hideCursorInOverviewRuler: true, + renderLineHighlight: "none", + scrollbar: { + vertical: "hidden", + horizontal: "auto", + alwaysConsumeMouseWheel: false, + }, + quickSuggestions: hasTokens + ? { other: true, comments: false, strings: true } + : false, + wordBasedSuggestions: "off", + suggestOnTriggerCharacters: hasTokens, + acceptSuggestionOnEnter: "off", + tabCompletion: hasTokens ? "on" : "off", + automaticLayout: true, + contextmenu: false, + fixedOverflowWidgets: true, + }} + loading={} + /> + ); } diff --git a/frontend/src/components/TaskPage.test.tsx b/frontend/src/components/TaskPage.test.tsx index 30f122d..83544f8 100644 --- a/frontend/src/components/TaskPage.test.tsx +++ b/frontend/src/components/TaskPage.test.tsx @@ -23,7 +23,6 @@ const fakeTaskSchema = { expression: { type: "string", title: "Designation expression", - "ui:widget": "expression", "ui:options": { tokens: [ { diff --git a/frontend/src/components/TaskPage.tsx b/frontend/src/components/TaskPage.tsx index 12a0f7c..88f725b 100644 --- a/frontend/src/components/TaskPage.tsx +++ b/frontend/src/components/TaskPage.tsx @@ -1,7 +1,11 @@ import { useEffect, useRef, useState } from "react"; import { useLocation, useParams } from "react-router-dom"; import Form from "@rjsf/mui"; -import type { RegistryWidgetsType } from "@rjsf/utils"; +import { + getTemplate, + type RegistryWidgetsType, + type WidgetProps, +} from "@rjsf/utils"; import validator from "@rjsf/validator-ajv8"; import InfoOutlined from "@mui/icons-material/InfoOutlined"; import Alert from "@mui/material/Alert"; @@ -22,7 +26,25 @@ import { FoldableObjectFieldTemplate } from "./FoldableObjectFieldTemplate"; import { Markdown } from "./Markdown"; import { ProgressView } from "./ProgressView"; +function isNumericSchema(schema: WidgetProps["schema"]): boolean { + const schemaType = schema.type; + return schemaType === "number" || schemaType === "integer"; +} + +function TextWidget(props: WidgetProps) { + if (isNumericSchema(props.schema)) { + const BaseInputTemplate = getTemplate( + "BaseInputTemplate", + props.registry, + props.options, + ); + return ; + } + return ; +} + const widgets: RegistryWidgetsType = { + TextWidget, expression: ExpressionWidget, }; diff --git a/frontend/src/extractUiSchema.test.ts b/frontend/src/extractUiSchema.test.ts index 088471b..4511b0e 100644 --- a/frontend/src/extractUiSchema.test.ts +++ b/frontend/src/extractUiSchema.test.ts @@ -12,7 +12,6 @@ describe("extractUiSchema", () => { }, expression: { type: "string", - "ui:widget": "expression", "ui:options": { tokens: [{ label: "col" }] }, }, advanced: { @@ -30,7 +29,6 @@ describe("extractUiSchema", () => { expect(extractUiSchema(schema)).toEqual({ password: { "ui:widget": "password" }, expression: { - "ui:widget": "expression", "ui:options": { tokens: [{ label: "col" }] }, }, advanced: { diff --git a/tests/test_formula_tokens.py b/tests/test_formula_tokens.py index 68b108e..0871ee6 100644 --- a/tests/test_formula_tokens.py +++ b/tests/test_formula_tokens.py @@ -7,14 +7,13 @@ def test_expression_tokens_include_language_names() -> None: assert labels >= {"col", "sin", "cos", "str", "to_deg", "unit", "pi", "deg", "arcsec", "mag"} -def test_designation_form_marks_expression_widget() -> None: +def test_designation_form_includes_expression_tokens() -> None: schema = StructuredDesignationForm.model_json_schema() properties = schema["properties"] assert isinstance(properties, dict) expression = properties["expression"] assert isinstance(expression, dict) extra = expression_json_schema_extra() - assert expression["ui:widget"] == extra["ui:widget"] options = expression["ui:options"] extra_options = extra["ui:options"] assert isinstance(options, dict) diff --git a/uploader/app/lib/formula/namespace.py b/uploader/app/lib/formula/namespace.py index 8e080de..961562c 100644 --- a/uploader/app/lib/formula/namespace.py +++ b/uploader/app/lib/formula/namespace.py @@ -159,7 +159,6 @@ def expression_tokens() -> list[ExpressionToken]: def expression_json_schema_extra() -> dict[str, Any]: return { - "ui:widget": "expression", "ui:options": {"tokens": expression_tokens()}, }