Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions frontend/src/components/ExpressionWidget.test.tsx
Original file line number Diff line number Diff line change
@@ -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 = [
{
Expand All @@ -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: {
Expand All @@ -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", () => {
Expand Down
130 changes: 72 additions & 58 deletions frontend/src/components/ExpressionWidget.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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<editor.IStandaloneCodeEditor | null>(null);
Expand All @@ -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(
Expand All @@ -209,65 +213,75 @@ export function ExpressionWidget(props: WidgetProps) {
}

return (
<Box
sx={{
border: 1,
borderColor: "divider",
borderRadius: 1,
overflow: "hidden",
bgcolor: isDark ? "#1e1e1e" : "#ffffff",
"&:hover": {
borderColor: "text.primary",
},
"&:focus-within": {
borderColor: "primary.main",
},
}}
>
<Editor
height="42px"
language={LANGUAGE_ID}
theme={isDark ? "vs-dark" : "light"}
value={value.replace(/\r?\n/g, "")}
onChange={(next) => {
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,
<Box>
{!props.hideLabel && props.label ? (
<Typography variant="body2" color="text.secondary" sx={{ mb: 0.5 }}>
{props.label}
{props.required ? " *" : ""}
</Typography>
) : null}
<Box
sx={{
border: 1,
borderColor: "divider",
borderRadius: 1,
overflow: "hidden",
bgcolor: isDark ? "#1e1e1e" : "#ffffff",
"&:hover": {
borderColor: "text.primary",
},
"&:focus-within": {
borderColor: "primary.main",
},
quickSuggestions: { other: true, comments: false, strings: true },
wordBasedSuggestions: "off",
suggestOnTriggerCharacters: true,
acceptSuggestionOnEnter: "off",
tabCompletion: "on",
automaticLayout: true,
contextmenu: false,
fixedOverflowWidgets: true,
}}
loading={<Box sx={{ height: 42 }} />}
/>
>
<Editor
height="42px"
language={hasTokens ? LANGUAGE_ID : "plaintext"}
theme={isDark ? "vs-dark" : "light"}
value={value.replace(/\r?\n/g, "")}
onChange={(next) => {
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={<Box sx={{ height: 42 }} />}
/>
</Box>
</Box>
);
}
1 change: 0 additions & 1 deletion frontend/src/components/TaskPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ const fakeTaskSchema = {
expression: {
type: "string",
title: "Designation expression",
"ui:widget": "expression",
"ui:options": {
tokens: [
{
Expand Down
24 changes: 23 additions & 1 deletion frontend/src/components/TaskPage.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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 <BaseInputTemplate {...props} />;
}
return <ExpressionWidget {...props} />;
}

const widgets: RegistryWidgetsType = {
TextWidget,
expression: ExpressionWidget,
};

Expand Down
2 changes: 0 additions & 2 deletions frontend/src/extractUiSchema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ describe("extractUiSchema", () => {
},
expression: {
type: "string",
"ui:widget": "expression",
"ui:options": { tokens: [{ label: "col" }] },
},
advanced: {
Expand All @@ -30,7 +29,6 @@ describe("extractUiSchema", () => {
expect(extractUiSchema(schema)).toEqual({
password: { "ui:widget": "password" },
expression: {
"ui:widget": "expression",
"ui:options": { tokens: [{ label: "col" }] },
},
advanced: {
Expand Down
3 changes: 1 addition & 2 deletions tests/test_formula_tokens.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 0 additions & 1 deletion uploader/app/lib/formula/namespace.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()},
}

Expand Down
Loading